/******************************************************************************************************************************************************
start jester and dependent libraries, required for spiceworks plugins
******************************************************************************************************************************************************/
// Jester version 1.5
// Released October 25th, 2007

// Compatible, tested with Prototype 1.6.0.2

// Copyright 2007, thoughtbot, inc.
// Released under the MIT License.

// Minified wtih http://fmarcia.info/jsmin/test.html
Jester={};
Jester.Resource=function(){};Jester.Constructor=function(model){return(function CONSTRUCTOR(){this.klass=CONSTRUCTOR;this.initialize.apply(this,arguments);this.after_initialization.apply(this,arguments);}).toString().replace(/CONSTRUCTOR/g,model);}
var jesterCallback=null;Object.extend(Jester.Resource,{model:function(model,options)
{var new_model=null;new_model=eval(model+" = "+Jester.Constructor(model));new_model.prototype=new Jester.Resource();Object.extend(new_model,Jester.Resource);if(!Jester.Tree){Jester.Tree=new XML.ObjTree();Jester.Tree.attr_prefix="@";}
if(!options)options={};var default_options={format:"xml",singular:model.underscore(),name:model}
options=Object.extend(default_options,options);options.format=options.format.toLowerCase();options.plural=options.singular.pluralize(options.plural);options.singular_xml=options.singular.replace(/_/g,"-");options.plural_xml=options.plural.replace(/_/g,"-");options.remote=false;var default_prefix=window.location.protocol+"//"+window.location.hostname+(window.location.port?":"+window.location.port:"");if(options.prefix&&options.prefix.match(/^https?:/))
options.remote=true;if(!options.prefix)
options.prefix=default_prefix;if(!options.prefix.match(/^(https?|file):/))
options.prefix=default_prefix+(options.prefix.match(/^\//)?"":"/")+options.prefix;options.prefix=options.prefix.replace(/\b\/+$/,"");options.urls=Object.extend(this._default_urls(options),options.urls);new_model.name=model;new_model.options=options;for(var opt in options)
new_model["_"+opt]=options[opt];for(var url in options.urls)
eval('new_model._'+url+'_url = function(params) {return this._url_for("'+url+'", params);}');if(options.checkNew)
this.buildAttributes(new_model,options);if(window)
window[model]=new_model;return new_model;},buildAttributes:function(model,options){model=model||this;var async=options.asynchronous;if(async==null)
async=true;var buildWork=bind(model,function(doc){if(this._format=="json")
this._attributes=this._attributesFromJSON(doc);else
this._attributes=this._attributesFromTree(doc[this._singular_xml]);});model.requestAndParse(options.format,buildWork,model._new_url(),{asynchronous:async});},loadRemoteJSON:function(url,callback,user_callback){if(typeof(user_callback)=="function")
jesterCallback=function(doc){user_callback(callback(doc));}
else
jesterCallback=callback;var script=document.createElement("script");script.type="text/javascript";if(url.indexOf("?")==-1)
url+="?";else
url+="&";url+="callback=jesterCallback";script.src=url;document.firstChild.appendChild(script);},requestAndParse:function(format,callback,url,options,user_callback,remote){if(remote&&format=="json"&&user_callback)
return this.loadRemoteJSON(url,callback,user_callback)
parse_and_callback=null;if(format.toLowerCase()=="json"){parse_and_callback=function(transport){if(transport.status==500)return callback(null);eval("var attributes = "+transport.responseText);return callback(attributes);}}else{parse_and_callback=function(transport){if(transport.status==500)return callback(null);return callback(Jester.Tree.parseXML(transport.responseText));}}
if(!(options.postBody||options.parameters||options.postbody||options.method=="post")){options.method="get";}
return this.request(parse_and_callback,url,options,user_callback);},request:function(callback,url,options,user_callback){if(user_callback){options.asynchronous=true;if(typeof(user_callback)=="object"){for(var x in user_callback)
options[x]=user_callback[x];user_callback=options.onComplete;}}
else
user_callback=function(arg){return arg;}
if(options.asynchronous){options.onComplete=function(transport,json){user_callback(callback(transport),json);}
return new Ajax.Request(url,options).transport;}
else
{options.asynchronous=false;return callback(new Ajax.Request(url,options).transport);}},find:function(id,params,callback){if(!callback&&typeof(params)=="function"){callback=params;params=null;}
var findAllWork=bind(this,function(doc){if(!doc)return null;var collection=this._loadCollection(doc);if(!collection)return null;if(id=="first")
return collection[0];return collection;});var findOneWork=bind(this,function(doc){if(!doc)return null;var base=this._loadSingle(doc);if(!base||base._properties.length==0)return null;if(!base._properties.include("id"))base._setAttribute("id",parseInt(id))
return base;});if(id=="first"||id=="all"){var url=this._list_url(params);return this.requestAndParse(this._format,findAllWork,url,{},callback,this._remote);}
else{if(isNaN(parseInt(id)))return null;if(!params)params={};params.id=id;var url=this._show_url(params);return this.requestAndParse(this._format,findOneWork,url,{},callback,this._remote);}},build:function(attributes){return new this(attributes);},create:function(attributes,callback){var base=new this(attributes);createWork=bind(this,function(saved){return callback(base);});if(callback){return base.save(createWork);}
else{base.save();return base;}},destroy:function(params,callback){if(typeof(params)=="function"){callback=params;params=null;}
if(typeof(params)=="number"){params={id:params};}
params.id=params.id||this.id;if(!params.id)return false;var destroyWork=bind(this,function(transport){if(transport.status==200){if(!params.id||this.id==params.id)
this.id=null;return this;}
else
return false;});return this.request(destroyWork,this._destroy_url(params),{method:"delete"},callback);},_interpolate:function(string,params){if(!params)return string;var result=string;params.each(function(pair){var re=new RegExp(":"+pair.key,"g");if(result.match(re)){result=result.replace(re,pair.value);params.unset(pair.key);}});return result;},_url_for:function(action,params){if(!this._urls[action])return"";if(typeof(params)=="number")params={id:params}
if(params)params=$H(params);var url=this._interpolate(this._prefix+this._urls[action],params)
return url+(params&&params.any()?"?"+params.toQueryString():"");},_default_urls:function(options){urls={'show':"/"+options.plural+"/:id."+options.format,'list':"/"+options.plural+"."+options.format,'new':"/"+options.plural+"/new."+options.format}
urls.create=urls.list;urls.destroy=urls.update=urls.show;return urls;},_attributesFromJSON:function(json){if(!json||json.constructor!=Object)return false;if(json.attributes)json=json.attributes;var attributes={};var i=0;for(var attr in json){var value=json[attr];if(attr=="id")
value=parseInt(value);else if(attr.match(/(created_at|created_on|updated_at|updated_on)/)){var date=Date.parse(value);if(date&&!isNaN(date))value=date;}
attributes[attr]=value;i+=1;}
if(i==0)return false;return attributes;},_attributesFromTree:function(elements){var attributes={}
for(var attr in elements){var value=elements[attr];if(elements[attr]&&elements[attr]["@type"]){if(elements[attr]["#text"])
value=elements[attr]["#text"];else
value=undefined;}
if(!value){}
else if(typeof(value)=="string"){if(elements[attr]["@type"]=="integer"){var num=parseInt(value);if(!isNaN(num))value=num;}
else if(elements[attr]["@type"]=="boolean")
value=(value=="true");else if(elements[attr]["@type"]=="datetime"){var date=Date.parse(value);if(!isNaN(date))value=date;}}
else{var relation=value;var i=0;var singular=null;var has_many=false;for(var val in relation){if(i==0)
singular=val;i+=1;}
if(relation[singular]&&typeof(relation[singular])=="object"&&i==1){var value=[];var plural=attr;var name=singular.camelize().capitalize();if(!(elements[plural][singular].length>0))
elements[plural][singular]=[elements[plural][singular]];elements[plural][singular].each(bind(this,function(single){if(eval("typeof("+name+")")=="undefined"){Jester.Resource.model(name,{prefix:this._prefix,singular:singular,plural:plural,format:this._format});}
var base=eval(name+".build(this._attributesFromTree(single))");value.push(base);}));}
else{singular=attr;var name=singular.capitalize();if(eval("typeof("+name+")")=="undefined"){Jester.Resource.model(name,{prefix:this._prefix,singular:singular,format:this._format});}
value=eval(name+".build(this._attributesFromTree(value))");}}
attribute=attr.replace(/-/g,"_");attributes[attribute]=value;}
return attributes;},_loadSingle:function(doc){var attributes;if(this._format=="json")
attributes=this._attributesFromJSON(doc);else
attributes=this._attributesFromTree(doc[this._singular_xml]);return this.build(attributes);},_loadCollection:function(doc){var collection;if(this._format=="json"){collection=doc.map(bind(this,function(item){return this.build(this._attributesFromJSON(item));}));}
else{if(!Jester.Resource.elementHasMany(doc[this._plural_xml]))
doc[this._plural_xml][this._singular_xml]=[doc[this._plural_xml][this._singular_xml]];collection=doc[this._plural_xml][this._singular_xml].map(bind(this,function(elem){return this.build(this._attributesFromTree(elem));}));}
return collection;}});Object.extend(Jester.Resource.prototype,{initialize:function(attributes){this._properties=[];this._associations=[];this.setAttributes(this.klass._attributes||{});this.setAttributes(attributes);this.errors=[];for(var url in this.klass._urls)
eval('this._'+url+'_url = function(params) {return this._url_for("'+url+'", params);}');},after_initialization:function(){},new_record:function(){return!(this.id);},valid:function(){return!this.errors.any();},reload:function(callback){var reloadWork=bind(this,function(copy){this._resetAttributes(copy.attributes(true));if(callback)
return callback(this);else
return this;});if(this.id){if(callback)
return this.klass.find(this.id,{},reloadWork);else
return reloadWork(this.klass.find(this.id));}
else
return this;},destroy:function(params,callback){if(params===undefined){params={};}
if(typeof(params)=="function"){callback=params;params={};}
if(typeof(params)=="number"){params={id:params};}
if(!params.id){params.id=this.id;}
if(!params.id)return false;if(this._properties!==undefined){(this._properties).each(bind(this,function(value,i){if(params[value]===undefined){params[value]=this[value];}}));}
var destroyWork=bind(this,function(transport){if(transport.status==200){if(!params.id||this.id==params.id)
this.id=null;return this;}
else
return false;});return this.klass.request(destroyWork,this._destroy_url(params),{method:"delete"},callback);},save:function(callback){var saveWork=bind(this,function(transport){var saved=false;if(transport.responseText&&(transport.responseText.strip()!="")){var errors=this._errorsFrom(transport.responseText);if(errors)
this._setErrors(errors);else{var attributes;if(this.klass._format=="json"){attributes=this._attributesFromJSON(transport.responseText);}
else{var doc=Jester.Tree.parseXML(transport.responseText);if(doc[this.klass._singular_xml])
attributes=this._attributesFromTree(doc[this.klass._singular_xml]);}
if(attributes)
this._resetAttributes(attributes);}}
if(this.new_record()&&transport.status==201){loc=transport.getResponseHeader("location");if(loc){id=parseInt(loc.match(/\/([^\/]*?)(\.\w+)?$/)[1]);if(!isNaN(id))
this._setProperty("id",id)}}
return(transport.status>=200&&transport.status<300&&this.errors.length==0);});this._setErrors([]);var url=null;var method=null;var params={};var urlParams={};(this._properties).each(bind(this,function(value,i){params[this.klass._singular+"["+value+"]"]=this[value];urlParams[value]=this[value];}));if(this.new_record()){url=this._create_url(urlParams);method="post";}
else{url=this._update_url(urlParams);method="put";}
return this.klass.request(saveWork,url,{parameters:params,method:method},callback);},setAttributes:function(attributes)
{$H(attributes).each(bind(this,function(attr){this._setAttribute(attr.key,attr.value)}));return attributes;},updateAttributes:function(attributes,callback)
{this.setAttributes(attributes);return this.save(callback);},attributes:function(include_associations){var attributes={}
for(var i=0;i<this._properties.length;i++)
attributes[this._properties[i]]=this[this._properties[i]];if(include_associations){for(var i=0;i<this._associations.length;i++)
attributes[this._associations[i]]=this[this._associations[i]];}
return attributes;},_attributesFromJSON:function()
{return this.klass._attributesFromJSON.apply(this.klass,arguments);},_attributesFromTree:function()
{return this.klass._attributesFromTree.apply(this.klass,arguments);},_errorsFrom:function(raw){if(this.klass._format=="json")
return this._errorsFromJSON(raw);else
return this._errorsFromXML(raw);},_errorsFromJSON:function(json){try{json=eval(json);}catch(e){return false;}
if(!(json&&json.constructor==Array&&json[0]&&json[0].constructor==Array))return false;return json.map(function(pair){return pair[0].capitalize()+" "+pair[1];});},_errorsFromXML:function(xml){if(!xml)return false;var doc=Jester.Tree.parseXML(xml);if(doc&&doc.errors){var errors=[];if(typeof(doc.errors.error)=="string")
doc.errors.error=[doc.errors.error];doc.errors.error.each(function(value,index){errors.push(value);});return errors;}
else return false;},_setErrors:function(errors){this.errors=errors;},_resetAttributes:function(attributes){this._clear();for(var attr in attributes)
this._setAttribute(attr,attributes[attr]);},_setAttribute:function(attribute,value){if(value&&typeof(value)=="object"&&value.constructor!=Date)
this._setAssociation(attribute,value);else
this._setProperty(attribute,value);},_setProperties:function(properties){this._clearProperties();for(var prop in properties)
this._setProperty(prop,properties[prop])},_setAssociations:function(associations){this._clearAssociations();for(var assoc in associations)
this._setAssociation(assoc,associations[assoc])},_setProperty:function(property,value){this[property]=value;if(!(this._properties.include(property)))
this._properties.push(property);},_setAssociation:function(association,value){this[association]=value;if(!(this._associations.include(association)))
this._associations.push(association);},_clear:function(){this._clearProperties();this._clearAssociations();},_clearProperties:function(){for(var i=0;i<this._properties.length;i++)
this[this._properties[i]]=null;this._properties=[];},_clearAssociations:function(){for(var i=0;i<this._associations.length;i++)
this[this._associations[i]]=null;this._associations=[];},_url_for:function(action,params){if(!params)params=this.id;if(typeof(params)=="object"&&!params.id)
params.id=this.id;return this.klass._url_for(action,params);}});Jester.Resource.elementHasMany=function(element){var i=0;var singular=null;var has_many=false;for(var val in element){if(i==0)
singular=val;i+=1;}
return(element[singular]&&typeof(element[singular])=="object"&&element[singular].length!=null&&i==1);}
function bind(context,func){var __method=func,args=$A(func.arguments),object=context;return function(){return __method.apply(object,args.concat($A(arguments)));}}
if(typeof(Resource)=="undefined")
Resource=Jester.Resource;if(!String.prototype.pluralize)String.prototype.pluralize=function(plural){var str=this;if(plural)str=plural;else{var uncountable_words=['equipment','information','rice','money','species','series','fish','sheep','moose'];var uncountable=false;for(var x=0;!uncountable&&x<uncountable_words.length;x++)uncountable=(uncountable_words[x].toLowerCase()==str.toLowerCase());if(!uncountable){var rules=[[new RegExp('(m)an$','gi'),'$1en'],[new RegExp('(pe)rson$','gi'),'$1ople'],[new RegExp('(child)$','gi'),'$1ren'],[new RegExp('(ax|test)is$','gi'),'$1es'],[new RegExp('(octop|vir)us$','gi'),'$1i'],[new RegExp('(alias|status)$','gi'),'$1es'],[new RegExp('(bu)s$','gi'),'$1ses'],[new RegExp('(buffal|tomat)o$','gi'),'$1oes'],[new RegExp('([ti])um$','gi'),'$1a'],[new RegExp('sis$','gi'),'ses'],[new RegExp('(?:([^f])fe|([lr])f)$','gi'),'$1$2ves'],[new RegExp('(hive)$','gi'),'$1s'],[new RegExp('([^aeiouy]|qu)y$','gi'),'$1ies'],[new RegExp('(x|ch|ss|sh)$','gi'),'$1es'],[new RegExp('(matr|vert|ind)ix|ex$','gi'),'$1ices'],[new RegExp('([m|l])ouse$','gi'),'$1ice'],[new RegExp('^(ox)$','gi'),'$1en'],[new RegExp('(quiz)$','gi'),'$1zes'],[new RegExp('s$','gi'),'s'],[new RegExp('$','gi'),'s']];var matched=false;for(var x=0;!matched&&x<=rules.length;x++){matched=str.match(rules[x][0]);if(matched)str=str.replace(rules[x][0],rules[x][1]);}}}
return str;};

// XML.ObjTree -- XML source code from/to JavaScript object like E4X
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('5(p(o)==\'w\')o=v(){};o.r=v(){m 9};o.r.1i="0.1b";o.r.u.14=\'<?L 1s="1.0" 1o="1n-8" ?>\\n\';o.r.u.Y=\'-\';o.r.u.1c=\'1a/L\';o.r.u.N=v(a){6 b;5(W.U){6 c=K U();6 d=c.1r(a,"1p/L");5(!d)m;b=d.A}q 5(W.10){c=K 10(\'1k.1h\');c.1g=z;c.1e(a);b=c.A}5(!b)m;m 9.E(b)};o.r.u.1d=v(c,d,e){6 f={};y(6 g 19 d){f[g]=d[g]}5(!f.M){5(p(f.18)=="w"&&p(f.17)=="w"&&p(f.16)=="w"){f.M="15"}q{f.M="13"}}5(e){f.X=V;6 h=9;6 i=e;6 j=f.T;f.T=v(a){6 b;5(a&&a.x&&a.x.A){b=h.E(a.x.A)}q 5(a&&a.J){b=h.N(a.J)}i(b,a);5(j)j(a)}}q{f.X=z}6 k;5(p(S)!="w"&&S.I){f.1q=c;6 l=K S.I(f);5(l)k=l.12}q 5(p(Q)!="w"&&Q.I){6 l=K Q.I(c,f);5(l)k=l.12}5(e)m k;5(k&&k.x&&k.x.A){m 9.E(k.x.A)}q 5(k&&k.J){m 9.N(k.J)}};o.r.u.E=v(a){5(!a)m;9.H={};5(9.P){y(6 i=0;i<9.P.t;i++){9.H[9.P[i]]=1}}6 b=9.O(a);5(9.H[a.F]){b=[b]}5(a.B!=11){6 c={};c[a.F]=b;b=c}m b};o.r.u.O=v(a){5(a.B==7){m}5(a.B==3||a.B==4){6 b=a.G.1j(/[^\\1f-\\1l]/);5(b==1m)m z;m a.G}6 c;6 d={};5(a.D&&a.D.t){c={};y(6 i=0;i<a.D.t;i++){6 e=a.D[i].F;5(p(e)!="Z")C;6 f=a.D[i].G;5(!f)C;e=9.Y+e;5(p(d[e])=="w")d[e]=0;d[e]++;9.R(c,e,d[e],f)}}5(a.s&&a.s.t){6 g=V;5(c)g=z;y(6 i=0;i<a.s.t&&g;i++){6 h=a.s[i].B;5(h==3||h==4)C;g=z}5(g){5(!c)c="";y(6 i=0;i<a.s.t;i++){c+=a.s[i].G}}q{5(!c)c={};y(6 i=0;i<a.s.t;i++){6 e=a.s[i].F;5(p(e)!="Z")C;6 f=9.O(a.s[i]);5(f==z)C;5(p(d[e])=="w")d[e]=0;d[e]++;9.R(c,e,d[e],f)}}}m c};o.r.u.R=v(a,b,c,d){5(9.H[b]){5(c==1)a[b]=[];a[b][a[b].t]=d}q 5(c==1){a[b]=d}q 5(c==2){a[b]=[a[b],d]}q{a[b][a[b].t]=d}};',62,91,'|||||if|var|||this|||||||||||||return||XML|typeof|else|ObjTree|childNodes|length|prototype|function|undefined|responseXML|for|false|documentElement|nodeType|continue|attributes|parseDOM|nodeName|nodeValue|__force_array|Request|responseText|new|xml|method|parseXML|parseElement|force_array|Ajax|addNode|HTTP|onComplete|DOMParser|true|window|asynchronous|attr_prefix|string|ActiveXObject||transport|post|xmlDecl|get|parameters|postbody|postBody|in|text|24|overrideMimeType|parseHTTP|loadXML|x00|async|XMLDOM|VERSION|match|Microsoft|x20|null|UTF|encoding|application|uri|parseFromString|version'.split('|'),0,{}))
// This is a Date parsing library by Nicholas Barthelemy, packed to keep jester.js light.
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('N.q.F||(N.q.F=t(a){o u.1d().F(a)});O.q.F||(O.q.F=t(a){o\'0\'.1H(a-u.K)+u});O.q.1H||(O.q.1H=t(a){v s=\'\',i=0;2k(i++<a){s+=u}o s});N.q.1j||(N.q.1j=t(){o u.1d().1j()});O.q.1j||(O.q.1j=t(){v n=u,l=n.K,i=-1;2k(i++<l){u.20(i,i+1)==0?n=n.20(1,n.K):i=l}o n});k.1m="2H 2F 2z 2y 2x 2u 2r 3q 3n 3k 3i 3d".1x(" ");k.1o="38 35 2Y 2U 2Q 2O 2M".1x(" ");k.2K="31 28 31 30 31 30 31 31 30 31 30 31".1x(" ");k.1A={2G:"%Y-%m-%d %H:%M:%S",2w:"%Y-%m-%2v%H:%M:%S%T",2s:"%a, %d %b %Y %H:%M:%S %Z",3p:"%d %b %H:%M",3o:"%B %d, %Y %H:%M"};k.3l=-1;k.3j=-2;(t(){v d=k;d["3h"]=1;d["2i"]=1t;d["2h"]=d["2i"]*19;d["2e"]=d["2h"]*19;d["P"]=d["2e"]*24;d["37"]=d["P"]*7;d["34"]=d["P"]*31;d["1q"]=d["P"]*2X;d["2W"]=d["1q"]*10;d["2R"]=d["1q"]*23;d["2P"]=d["1q"]*1t})();k.q.1D||(k.q.1D=t(){o D k(u.1k())});k.q.26||(k.q.26=t(a,b){u.1F(u.1k()+((a||k.P)*(b||1)));o u});k.q.2a||(k.q.2a=t(a,b){u.1F(u.1k()-((a||k.P)*(b||1)));o u});k.q.1Z||(k.q.1Z=t(){u.1Y(0);u.1X(0);u.1U(0);u.1T(0);o u});k.q.1I||(k.q.1I=t(a,b){C(1i a==\'1p\')a=k.1J(a);o 18.2l((u.1k()-a.1k())/(b|k.P))});k.q.1N||(k.q.1N=k.q.1I);k.q.2n||(k.q.2n=t(){d=O(u);o d.1f(-(18.1y(d.K,2)))>3&&d.1f(-(18.1y(d.K,2)))<21?"V":["V","17","16","1a","V"][18.1y(N(d)%10,4)]});k.q.1w||(k.q.1w=t(){v f=(D k(u.1h(),0,1)).1e();o 18.2t((u.1n()+(f>3?f-4:f+3))/7)});k.q.1M=t(){o u.1d().1v(/^.*? ([A-Z]{3}) [0-9]{4}.*$/,"$1").1v(/^.*?\\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\\)$/,"$1$2$3")};k.q.2p=t(){o(u.1u()>0?"-":"+")+O(18.2l(u.1u()/19)).F(2)+O(u.1u()%19,2,"0").F(2)};k.q.1n||(k.q.1n=t(){o((k.2o(u.1h(),u.1c(),u.1b()+1,0,0,0)-k.2o(u.1h(),0,1,0,0,0))/k.P)});k.q.2m||(k.q.2m=t(){v a=u.1D();a.15(a.1c()+1);a.L(0);o a.1b()});k.2j||(k.2j=t(a,b){a=(a+12)%12;C(k.1K(b)&&a==1)o 29;o k.3g.3f[a]});k.1K||(k.1K=t(a){o(((a%4)==0)&&((a%23)!=0)||((a%3e)==0))});k.q.1B||(k.q.1B=t(c){C(!u.3c())o\'&3b;\';v d=u;C(k.1A[c.2g()])c=k.1A[c.2g()];o c.1v(/\\%([3a])/g,t(a,b){39(b){E\'a\':o k.1l(d.1e()).1f(0,3);E\'A\':o k.1l(d.1e());E\'b\':o k.13(d.1c()).1f(0,3);E\'B\':o k.13(d.1c());E\'c\':o d.1d();E\'d\':o d.1b().F(2);E\'H\':o d.1G().F(2);E\'I\':o((h=d.1G()%12)?h:12).F(2);E\'j\':o d.1n().F(3);E\'m\':o(d.1c()+1).F(2);E\'M\':o d.36().F(2);E\'p\':o d.1G()<12?\'33\':\'32\';E\'S\':o d.2Z().F(2);E\'U\':o d.1w().F(2);E\'W\':R Q("%W 2V 2T 2S 25");E\'w\':o d.1e();E\'x\':o d.1r("%m/%d/%Y");E\'X\':o d.1r("%I:%M%p");E\'y\':o d.1h().1d().1f(2);E\'Y\':o d.1h();E\'T\':o d.2p();E\'Z\':o d.1M()}})});k.q.1r||(k.q.1r=k.q.1B);k.22=k.1J;k.1J=t(a){C(1i a!=\'1p\')o a;C(a.K==0||(/^\\s+$/).1E(a))o;2N(v i=0;i<k.1g.K;i++){v r=k.1g[i].J.2L(a);C(r)o k.1g[i].G(r)}o D k(k.22(a))};k.13||(k.13=t(c){v d=-1;C(1i c==\'2J\'){o k.1m[c.1c()]}2I C(1i c==\'27\'){d=c-1;C(d<0||d>11)R D Q("1s 1C 2b 2q 1W 1V 2d 1 2c 12:"+d);o k.1m[d]}v m=k.1m.1S(t(a,b){C(D 1O("^"+c,"i").1E(a)){d=b;o 1R}o 2f});C(m.K==0)R D Q("1s 1C 1p");C(m.K>1)R D Q("1Q 1C");o k.1m[d]});k.1l||(k.1l=t(c){v d=-1;C(1i c==\'27\'){d=c-1;C(d<0||d>6)R D Q("1s 1z 2b 2q 1W 1V 2d 1 2c 7");o k.1o[d]}v m=k.1o.1S(t(a,b){C(D 1O("^"+c,"i").1E(a)){d=b;o 1R}o 2f});C(m.K==0)R D Q("1s 1z 1p");C(m.K>1)R D Q("1Q 1z");o k.1o[d]});k.1g||(k.1g=[{J:/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{2,4})/,G:t(a){v d=D k();d.1L(a[3]);d.L(14(a[2],10));d.15(14(a[1],10)-1);o d}},{J:/(\\d{4})(?:-?(\\d{2})(?:-?(\\d{2})(?:[T ](\\d{2})(?::?(\\d{2})(?::?(\\d{2})(?:\\.(\\d+))?)?)?(?:Z|(?:([-+])(\\d{2})(?::?(\\d{2}))?)?)?)?)?)?/,G:t(a){v b=0;v d=D k(a[1],0,1);C(a[2])d.15(a[2]-1);C(a[3])d.L(a[3]);C(a[4])d.1Y(a[4]);C(a[5])d.1X(a[5]);C(a[6])d.1U(a[6]);C(a[7])d.1T(N("0."+a[7])*1t);C(a[9]){b=(N(a[9])*19)+N(a[10]);b*=((a[8]==\'-\')?1:-1)}b-=d.1u();1P=(N(d)+(b*19*1t));d.1F(N(1P));o d}},{J:/^2E/i,G:t(){o D k()}},{J:/^2D/i,G:t(){v d=D k();d.L(d.1b()+1);o d}},{J:/^2C/i,G:t(){v d=D k();d.L(d.1b()-1);o d}},{J:/^(\\d{1,2})(17|16|1a|V)?$/i,G:t(a){v d=D k();d.L(14(a[1],10));o d}},{J:/^(\\d{1,2})(?:17|16|1a|V)? (\\w+)$/i,G:t(a){v d=D k();d.L(14(a[1],10));d.15(k.13(a[2]));o d}},{J:/^(\\d{1,2})(?:17|16|1a|V)? (\\w+),? (\\d{4})$/i,G:t(a){v d=D k();d.L(14(a[1],10));d.15(k.13(a[2]));d.1L(a[3]);o d}},{J:/^(\\w+) (\\d{1,2})(?:17|16|1a|V)?$/i,G:t(a){v d=D k();d.L(14(a[2],10));d.15(k.13(a[1]));o d}},{J:/^(\\w+) (\\d{1,2})(?:17|16|1a|V)?,? (\\d{4})$/i,G:t(a){v d=D k();d.L(14(a[2],10));d.15(k.13(a[1]));d.1L(a[3]);o d}},{J:/^3m (\\w+)$/i,G:t(a){v d=D k();v b=d.1e();v c=k.1l(a[1]);v e=c-b;C(c<=b){e+=7}d.L(d.1b()+e);o d}},{J:/^2B (\\w+)$/i,G:t(a){R D Q("2A 25 3r");}}]);',62,214,'||||||||||||||||||||Date||||return||prototype|||function|this|var|||||||if|new|case|zf|handler|||re|length|setDate||Number|String|DAY|Error|throw||||th||||||||parseMonth|parseInt|setMonth|nd|st|Math|60|rd|getDate|getMonth|toString|getDay|substr|__PARSE_PATTERNS|getFullYear|typeof|rz|getTime|parseDay|MONTH_NAMES|getDayOfYear|DAY_NAMES|string|YEAR|format|Invalid|1000|getTimezoneOffset|replace|getWeek|split|min|day|FORMATS|strftime|month|clone|test|setTime|getHours|str|diff|parse|isLeapYear|setYear|getTimezone|compare|RegExp|time|Ambiguous|true|findAll|setMilliseconds|setSeconds|be|must|setMinutes|setHours|clearTime|substring||__native_parse|100||yet|increment|number|||decrement|index|and|between|HOUR|false|toLowerCase|MINUTE|SECOND|daysInMonth|while|floor|lastDayOfMonth|getOrdinal|UTC|getGMTOffset|value|July|rfc822|round|June|dT|iso8601|May|April|March|Not|last|yes|tom|tod|February|db|January|else|object|DAYS_PER_MONTH|exec|Saturday|for|Friday|MILLENNIUM|Thursday|CENTURY|supported|not|Wednesday|is|DECADE|365|Tuesday|getSeconds|||PM|AM|MONTH|Monday|getMinutes|WEEK|Sunday|switch|aAbBcdHIjmMpSUWwxXyYTZ|nbsp|valueOf|December|400|DAYS_IN_MONTH|Convensions|MILLISECOND|November|ERA|October|EPOCH|next|September|long|short|August|implemented'.split('|'),0,{}))
/******************************************************************************************************************************************************
end jester and dependent libraries, required for spiceworks plugins
******************************************************************************************************************************************************/


/* Some Jester Overrides */
// interpolate wasn't handling parameters like 'name[]' on IE.  Escaping the [] seems to work.
Jester.Resource._interpolate = (function (string, params) {
  if (!params) return string;

  var result = string;
  params.each(function(pair) {
    var re = new RegExp(":" + pair.key.gsub('[]','\\[\\]'), "g");
    if (result.match(re)) {
      result = result.replace(re, pair.value);
      params.unset(pair.key);
    }
  });
  return result;
});

// override the request method so that we can tack on an additional request parameter to let the server know that this request is coming from Jester
// we do this because Jester expects all resultsets to be in the form of an array, which is incompatible when passing a single object
Jester.Resource.request = function(callback, url, options, user_callback) {
  if (user_callback) {
    options.asynchronous = true;
    // if an options hash was given instead of a callback
    if (typeof(user_callback) == "object") {
      for (var x in user_callback)
      options[x] = user_callback[x];
      user_callback = options.onComplete;
    }
  }
  else
    user_callback = function(arg){return arg;}

  if (options.method == 'get') url += (url.indexOf('?') > -1 ? '&jester=t' : '?jester=t')
  else if (options.parameters) {
    if (typeof options.parameters == 'object') options.parameters.jester = 't';
    else options.parameters += '&jester=t';
  }

  if (options.asynchronous) {
    options.onComplete = function(transport, json) {user_callback(callback(transport), json);}
    return new Ajax.Request(url, options).transport;
  }
  else
  {
    options.asynchronous = false; // Make sure it's set, to avoid being overridden.
    return callback(new Ajax.Request(url, options).transport);
  }
};


/**************
 * Global Namespace for all spiceworks stuff
 */
var SPICEWORKS = {};

if (document.location.toString().match(/logEvents/)) {
  SPICEWORKS.logEvents = true;  
}

// JSLINT - The following statement will allow this file to pass the jslint tests by adding a couple of global variables
/*global Element, $, $$, Event, GMap2, Ajax */


// Create a closure, passing in the global namespace (SPICEWORKS) to a local variable (spiceworks)
(function (spiceworks) {
  
  // Spiceworks Events
  // Register for events that might be occuring
  // Passes through to the browser event code (prototype) for now, but by change in the future
  spiceworks.observe = function (event, func) {
    Event.observe(document, 'spiceworks:' + event, func);
  };
  
  // catch the loaded event so we'll know that we're ready to go.
  spiceworks.isReady = false;
  
  // Fire an event within the spiceworks infrastructure.
  // the event name is currently attacted to the DOM.  
  // "spiceworks:" will be prepended to all events
  // immediate will force the event to fire even if the page is not "ready" (dom fully loaded).
  // By default, events will be queued up until the document is fully loaded
  // there is (currently) no guarentee on the order that events will be fired once added (FF and IE are different)
  spiceworks.fire = function (event, memo) {
    memo = memo || {};
        
    if (event == 'ready') {
      spiceworks.isReady = true;
    }
    
    // if the page is not ready, then try again once the page is ready.
    if (spiceworks.isReady || memo.immediate) {
      
      if (spiceworks.logEvents){
        console.log(' [Fire] ' + event + '  ' + Object.toJSON(memo));
      }
      document.fire('spiceworks:' + event, memo);
    } else {
      if (spiceworks.logEvents){
        console.log('[Delay] ' + event + '  ' + Object.toJSON(memo));
      }
      spiceworks.ready(function () { spiceworks.fire(event, memo); });
    }
  };

  // This is called once all plugins are given the chance to load.  Use this if you depend on something in another plugin.
  // If this app is already loaded, then the function will be called immediately (unless onLoadOnly is passed as true).
  spiceworks.ready = function (func, loadOnly) {
    if (spiceworks.isReady && !loadOnly) {
      func(); // app already initialized, so let's just call the method.
    } else {
      spiceworks.observe('ready', func);      
    }
  };  

  // Create an object which derives from another object.
  // See: http://javascript.crockford.com/prototypal.html
  if (typeof Object.create !== 'function') {
      Object.create = function (o) {
          function F() {}
          F.prototype = o;
          return new F();
      };
  }
  
  
  // == Plugins Namespace ==
  // Manages the spiceworks plugins.
  spiceworks.plugin = function () {
    var plugins = [],
        that;
    that = {
      add: function (plugin) {
        /* Verify Attributes */
        if (!plugin.name) { 
          throw "Plugin name is required"; 
        }
        if (!plugin.version) { 
          throw "Plugin version is required"; 
        }
        if (!plugin.initialize) { 
          throw "Plugin initialize function is required"; 
        }

        /* A method for the plugin writer to define user settings*/
        plugin.configure = function (conf) {
          plugin.settingDefinitions = conf.settingDefinitions;
          if (!plugin.settings) {
            plugin.settings = {};
          }
          plugin.settingDefinitions.each( function (settingDefinition) {
            if (!plugin.settings[settingDefinition.name]){
              plugin.settings[settingDefinition.name] = settingDefinition.defaultValue;
            }
          });
        };
        
        /* A helper method for storing data for this plugin */
        plugin.store = function (key, data, callback) {
          spiceworks.persistence.store(plugin.guid + '_' + key, data, callback);
        };
        /* A helper method for loading data for this plugin*/
        plugin.load = function (key, callback, defaultValue) {
          spiceworks.persistence.load(plugin.guid + '_' + key, callback, defaultValue);
        };
        /* A helper method for removing data from the system*/
        plugin.remove = function (key, callback) {
          spiceworks.persistence.remove(plugin.guid + '_' + key, callback);
        };
        /* A method to store the settings for this plugin.  Settings are loaded by default. */
        plugin.storeSettings = function (settings, callback) {
          plugin.store( 'settings', settings, callback );
          plugin.settings = settings;
        };

        /* Capture the plugin and return a reference to it. */
        plugins.push(plugin);
        return plugin;
      },

      initializePlugins: function () {
        plugins.each(function (plugin) {
          try {
            if(!plugin.hasBeenInitialized) {
              plugin.initialize(plugin);
              plugin.hasBeenInitialized = true;
              spiceworks.fire('plugin:initialized', {immediate: true, pluginName:plugin.name});
            }
            // Fire off an event to tell the world that we've initialized.
          } catch (e) {
            // there was an error initializing a plugin...
            // Ignoring this for now.
          }
        });
        spiceworks.fire('plugins:initialized', {immediate: true});
      },

      getPlugins: function () {
        return plugins;
      },
      
      getPlugin: function (guid) {
        var plugin = plugins.find(function (plugin) { 
          return (plugin.guid === guid); 
        });
        return plugin;
      }
    };

    return that;
  }();

  Event.observe(document, 'dom:loaded', function (e) {    
    spiceworks.plugin.initializePlugins();
    spiceworks.fire('ready');
  });



  // == App Namespace ==
  spiceworks.app = function () {
    var that, userMethods;
    
    that = {
      ready: function (func) {
        spiceworks.observe('app:ready', func);
      },
      
      isShowing: function () {
        return $(document.body).hasClassName('spiceworks_application');        
      },
      
      setUser: function (user) {
        that.user = user;
      }      
    };
    return that;
  }();
  
  // == SPICEWORKS.portal ==
  spiceworks.portal = function () {
    var that = {
      ready: function (callback) {
        spiceworks.observe('portal:ready', callback);
      },
      
      isShowing: function () {
        return $(document.body).hasClassName('spiceworks_portal');
      }
    };
    return that;  
  }();
  
  // == SPICEWORKS.portalv2 ==
  spiceworks.portalv2 = function () {
    var that = {
      ready: function (callback) {
        spiceworks.observe('portalv2:ready', callback);
      },
      
      isShowing: function () {
        return $(document.body).hasClassName('spiceworks-portalv2');
      }
    };
    return that;  
  }();
  
  
  // == SPICEWORKS.app.dashboard ==
  // Add Widget Types and tabs here.  A plugin should not add a widget to the user's page.
  spiceworks.app.dashboard = function () {
    var widgetTypes = [],
        tabs = [],
        that; // TODO

    that = {
      // Create a new type of widget
      addWidgetType: function (widgetType) {
        var imgsrc, widgetList, li;
        
        if (!widgetType.name) { 
          throw 'WidgetType name required'; 
        }
        if (!widgetType.label) { 
          throw 'WidgetType label required'; 
        }
        if (!widgetType.update) { 
          throw 'WidgetType update method required'; 
        }
        
        /* Support either settingDefinitions or prefs for the preferences. */
        if (widgetType.prefs && !widgetType.settingDefinitions) {
          widgetType.settingDefinitions = widgetType.prefs;
        }
        
        /* Default the settingDefinitions to be an empty array if none were specified. */
        if( !widgetType.settingDefinitions ){
          widgetType.settingDefinitions = [];
        }
        
        widgetTypes.push(widgetType);

        /* Only do the following if we're on the dashboard */
        if (that.isShowing()) { 
          imgsrc = widgetType.icon || '/images/icons/small/gear.png';
        
          widgetList = $$('#dashboard_quickform ul').first();
          if (widgetList) {
            li = new Element('li', {'id': widgetType.name + '_small', 'class': 'module-small classname-javascript'});
            li.update('<a href="#"><img src="' + imgsrc + '"></img><span class="title">' + widgetType.label + '</span></a>');
            widgetList.insert(li);
          }
        }
      },

      getWidgetTypes: function () {
        return widgetTypes;
      },
      
      getWidgetType: function (name) {
        var widgetType = widgetTypes.find(function (widgetType) { 
          return (widgetType.name === name); 
        });
        
        if (widgetType == null) {
          widgetType = {
            name: name,
            label: 'Missing Definition',
            settingDefinitions:[],
            update: function (element, settings) {
              element.innerHTML = 'The definition of this widget is missing.  This is probably due to a plugin being disabled or removed.';
            }
          };
        }
        
        return widgetType;
      },
      
      isShowing: function () {
        return $(document.body).hasClassName('dashboard');
      },
      
      ready: function (callback) {
        spiceworks.observe('app:dashboard:ready', callback);
      }
    };
    
    return that;
  }();
  
  
  // == SPICEWORKS.app.navigation ==
  // define new tools (i.e. pages) that will be shown when the user clicks on them.
  spiceworks.app.navigation = function () {
    var items = [],
        menuItems = [],
        that;

    that = {
      addItem: function (item) {
        var linkElem = null;

        items.push(item);

        if (!spiceworks.app.isShowing()) { 
          return; 
        } // do nothing unless the app is showing.
        
        item.element = new Element('dd', {});
        linkElem = new Element('a', {'href': '/tools/' + item.name, 'id': item.name});
        linkElem.update(item.label);
        item.element.insert(linkElem);

        $('navigation_my_stuff').insert(item.element);
      },

      // Show the specified tool
      showItem: function (name) {
        var content, item;        
        item = items.find(function (item) { 
          return (item.name === name); 
        });

        $$('dd.current').each(function (e) {
          e.removeClassName('current');
        });
        item.element.addClassName('current');
        content = $('content');
        content.innerHTML = '';

        item.update(content);
        document.title = 'Spiceworks - ' + item.label;
      },

      getItems: function () {
        return items;
      },
      
      addMenuItem: function (spec) {
        menuItems.push(spec);
      },
      
      addMenuItems: function (spec) {
        that.addMenuItem(spec);
      },
      
      // Add a menu item once the page is loaded.
      dynamicAddMenuItem: function (spec) {
        $$(spec.selector).each( function ( itemElemOrig ) {
          var menuItem, itemElem, items;
          
          // Find the DD
          if( itemElemOrig.nodeName != 'DD'){
            itemElem = itemElemOrig.up('DD');              
          }else{
            itemElem = itemElemOrig;
          }
          
          spec = Object.clone(spec);
          spec.itemElemOrig = itemElemOrig;
          
          SPICEWORKS.ui.sideMenu( itemElem, spec );

        });
      },
      
      updateNavMenus: function () {
        menuItems.each( function (spec) {
          that.dynamicAddMenuItem(spec);
        });        
      }
    };
    
    // Attach the menu items once the app is ready.
    // This is also done in _customizable_sections.html.erb
    // It's fine to call this twice, it figures out what to do.
    spiceworks.app.ready(that.updateNavMenus);

    return that;
  }();
  
  spiceworks.ui = function () {
    
    return {
      /*
       * Ex Usage: 
       *  SUI.sideMenu(destElement, {
       *    items:[
       *      '<a href="http://foo.com">Foo</a>',
       *      {label: 'Bar', itemName:'bar', href:'http://bar.com'},
       *      {separator: true, itemName: 'sep'},
       *      {label: 'Baz', itemName:'baz', onclick: function () { alert('baz'); } }
       *    ]
       *  })
       */
      sideMenu: function (itemElem, spec) {
        
        var items,
            keepOpen = false,
            menuTimeout = null,
            openerTimeout = null,
            showMenuTimeout = null,
            target = $(spec.target || itemElem);
            sideMenu = null;
            
        if (Browser.ie6 && !spec.shown) {
          spec.openerUsesCss = false;
        }
        
        // == START HELPER FUNCTIONS
        function addItem(itemSpec) {
          if (!itemSpec) {
            return; // itemSpec is null, so do nothing!
          }
          
          var menuItem;
          var icon = (spec.icons === false ? '' : 'icon '); 
          
          
          //if it's just a string, then render the link without checking anything else.
          if (typeof itemSpec == 'string') {
            itemElem.menu.insert("<li>" + itemSpec + "</li>");
            return;
          }
          
          // Renamed className to itemName... keep support for plugin writers if possible.
          if(itemSpec.className && !itemSpec.itemName){
            itemSpec.itemName = itemSpec.className;
          }

          if (!itemSpec.itemName) {
            throw 'Menu item specification must include itemName.';
          }

          if (!itemElem.menu.down('.' + itemSpec.itemName)) {

            if (itemSpec.separator) {
              itemElem.menu.insert(new Element('li').insert(new Element('div', {'class':'separator ' + icon + itemSpec.itemName}).update('&nbsp;')));
            } else if (itemSpec.rawLink) {
              itemElem.menu.insert("<li>" + itemSpec.rawLink + "</li>");
            } else {
              menuItem = new Element('a', {href:itemSpec.href || '#', 'class': icon + itemSpec.itemName, 'target': (itemSpec.target || '_top')}).update(itemSpec.label);
              if (itemSpec.onclick) {
                Event.observe( menuItem, 'click', function (e) {
                  Event.stop(e);
                  hideMenuAndOpener();
                  itemSpec.onclick( spec.itemElemOrig || itemElem );
                });                  
              }
              itemElem.menu.insert(new Element('li').update(menuItem));                            
            }
          }
        }
            
        function showOpener() {
          if (!spec.openerUsesCss) {
            if (!itemElem.hasClassName('editing') && !itemElem.down('input')){
              itemElem.opener.style.display='block';
            }else{
              hideOpener();
            }              
          }
        }
        function hideOpener() {
          itemElem.opener.removeClassName('on');
          if (!spec.openerUsesCss) {
            itemElem.opener.style.display='none';              
          }
        }
        function hideMenu() {
          itemElem.opener.removeClassName('on');
          itemElem.pivotable.hide();
          itemElem.opener.style.zIndex='';
          itemElem.style.zIndex='';
          itemElem.pivotable.style.zIndex='';
          var dl = itemElem.up('dl,li,div');
          if(dl){
            dl.style.zIndex='';
          }
        }
        
        function showMenu() {
          if(!itemElem.pivotable.visible()) {
            itemElem.pivotable.show();
            itemElem.pivotable.clonePosition(itemElem.opener, {
              setHeight:false,
              setWidth:false,
              offsetTop: (itemElem.opener.offsetHeight - 2),
              offsetLeft: -(itemElem.pivotable.offsetWidth - itemElem.opener.offsetWidth - 6)
            });
            
            //itemElem.pivotable.style.right= spec.openerText ? '-6px' : '-1px';
          
            itemElem.style.zIndex=500;
            itemElem.opener.style.zIndex=301;
            itemElem.pivotable.style.zIndex=300;
            itemElem.opener.addClassName('on');
            var dl = itemElem.up('dl,li,div');
            if (dl) {
              dl.style.zIndex=500;
            }
          }
        }
        
        function hideMenuAndOpener() {
          hideMenu();
          hideOpener();
        }
        
        function itemOver(event) {
          Event.stop(event);
          clearTimeout(openerTimeout);
          clearTimeout(showMenuTimeout);
          openerTimeout = setTimeout( showOpener, 100);
        }
        function itemOut(event) {
          Event.stop(event);
          clearTimeout(openerTimeout);
          openerTimeout = setTimeout( hideMenuAndOpener, 100);
        }
        
        function openerOver(event) {
          Event.stop(event);
          clearTimeout(menuTimeout);
          clearTimeout(openerTimeout);
          showMenuTimeout = setTimeout(showMenu, 200);
        }
        function openerOut(event) {
          Event.stop(event);
          clearTimeout(menuTimeout);
          clearTimeout(openerTimeout);
          clearTimeout(showMenuTimeout);
          menuTimeout = setTimeout( hideMenu, 100);
        }
        
        function menuOver(event) {
          Event.stop(event);
          clearTimeout(openerTimeout);
          clearTimeout(menuTimeout);
        }
        
        function menuOut(event) {
          Event.stop(event);
          clearTimeout(menuTimeout);
          menuTimeout = setTimeout(hideMenu, 100);
          openerTimeout = setTimeout(hideOpener, 100);
        }
        // == END HELPER FUNCTIONS
        
        
        // == BUILD OUT MENU AND ADD MENU ITEMS
        // Build the menu ONLY if needed.
        if (!itemElem.menu) {
          itemElem.wrap = new Element('div', {'class':'sw-menu-wrap'});
          itemElem.insert({top:itemElem.wrap});
          itemElem.pivotable = new Element('div', {style: 'display:none; zoom:1; width:' + (spec.width || '130px'), 'class':'pivotable ' + spec.pivotableClass});
          itemElem.menu = new Element('ul', {'class':'nav_menu'});
          itemElem.pivotable.insert(itemElem.menu);
          itemElem.opener = new Element('a',{'class': 'edit sw-menu-opener', 'style':'cursor:pointer'});
          // optionally, an opener can be text based instead of an arrow.
          if(spec.openerText) {
            itemElem.opener.addClassName('sw-menu-opener-text');
            itemElem.opener.update(spec.openerText);
          }
          
          // itemElem.keepOpen = false;
          
          // insert the menu helper
          itemElem.wrap.insert({top: itemElem.opener});
          hideOpener();
          
          itemElem.wrap.insert({bottom: itemElem.pivotable});
          
          if (!spec.openerUsesCss) {
            Event.observe(target, 'mouseover', itemOver);
            Event.observe(target, 'mouseout', itemOut);
          }
          
          Event.observe(itemElem.opener, 'mouseover', openerOver);
          Event.observe(itemElem.opener, 'mouseout', openerOut);
          
          Event.observe(itemElem.pivotable, 'mouseover', menuOver);
          Event.observe(itemElem.pivotable, 'mouseout', menuOut);
        }
        
        // Add the new item(s) to the menu.          
        // determine if multiple items have been passed in or not.
        if (spec.items) {
          items = spec.items;
        } else {
          items = [spec];
        }
        
        items.each(addItem);
        
        // This is the actual side menu object.  We can add things to it using .addItem({})
        sideMenu = {
          hide: hideMenu,
          show: showMenu,
          addItem: addItem
        };
        
        return sideMenu;
      }
    };
  }();
  
  // name: 'ok', 'medium/delete', 'medium/cancel', etc...
  // callback: function callback for this button
  spiceworks.ui.button = function(name, callback) {
    var button = new Element('img', {'src':'/images/forms/buttons/' + name + '.gif'});
    button.observe('mouseover', function (event) {
      Event.stop(event);
      button.src = '/images/forms/buttons/' + name + '_hover.gif'
    });
    button.observe('mouseout', function (event) {
      Event.stop(event);
      button.src = '/images/forms/buttons/' + name + '.gif'
    });
    button.observe('click', callback)
    
    return button;
  }
  
  // spec = {title: '', body:''}
  spiceworks.ui.popup = function (spec) {
    var popup = {},
        height, 
        docBody;
    
    docBody = $(document.body);
    
    height = [docBody.getHeight(), 'px'].join('');
    
    popup.darkbox = new Element('div', {'class':'darkbox'});
    popup.darkbox.setStyle({'height': height});
    docBody.insert(popup.darkbox);

    popup.lightbox = new Element('div', {'class':'lightbox'});
    popup.lightbox.setStyle({'height':height});
    docBody.insert(popup.lightbox);
    
    popup.content = new Element('div', {'class':'content', 'id':spec.id});
    popup.lightbox.insert(popup.content);
    
    popup.title = new Element('h1');
    popup.content.insert(popup.title);
    
    popup.body = new Element('div');
    popup.content.insert(popup.body);
    
    // Helpers
    popup.setTitle = function (title) {
      popup.title.update(title);
    }
    popup.setBody = function (body) {
      popup.body.update(body);
    }
    popup.close = function () {
      if(popup.darkbox) {
        new Effect.BlindUp(popup.darkbox);
        new Effect.BlindUp(popup.lightbox);
        setTimeout( function () {
          popup.darkbox.remove();
          popup.lightbox.remove();
        }, 2000);
      }
    }

    // Initialize if needed
    if (spec.title) {
      popup.setTitle(spec.title);
    }
    if (spec.body) {
      popup.setBody(spec.body);
    }
    
    return popup;
  };
  
  spiceworks.ui.clearfix = "<div style='clear:both'></div>";
  spiceworks.ui.voidLink = "javascript:void(0)";
  
  
  // Create a toolbar in the specified element
  spiceworks.ui.toolbar = function(element,spec) {
    var actions = {},
        toolbar,
        toolbarRight;
    
    toolbar = new Element('div', {'class':'sui-toolbar', 'style':'display:none'}); // hidden by default
    element.insert(toolbar);
    
    // toolbarRight
    spec.actions = spec.actions || [];
    spec.actions.each(addToolbarAction);
    
    // Add an action to the toolbar.
    function addToolbarAction(actionSpec) {
      toolbar.show();
      actionSpec.action = new Element('a', {'href':actionSpec.href || SUI.voidLink, 'class':'sui-toolbar-action ' + (actionSpec.className || '')}).update(actionSpec.label);
      toolbar.insert(actionSpec.action);
      
      if (actionSpec.onclick) {
        Event.observe( actionSpec.action, 'click', function () {
          performAction(actionSpec.id);
        });
      }
      
      actions[actionSpec.id] = actionSpec;
    }
    
    function performAction(actionId) {
      actions[actionId].onclick(actionId);
    }
    
    return {
      actions:actions,
      element:toolbar,
      performAction:performAction
    };
  };
  
  // Encapsulation of the concept of a filter bar.  Requires a "content" area to scan
  // element is the parent to add the bar to
  // spec requires one object: {content: 'some_element_or_id'}
  spiceworks.ui.filterbar = function(element, spec) {
    var filters = {},
        content,
        filterbar,
        filterObject;
        
    element = $(element);
    content = $(spec.content);
    
    filterbar = new Element('div', {'class':'sui-filterbar'});
    element.insert(filterbar);
    
    function setObject(object) {
      // figure out filters!
      filterbar.update('');
      filters = {};
      tempFilters = {};
          
      if (!content) { 
        filterbar.hide();
        return; 
      }    
          
      addFilter('all', true);
            
      content.select('.filterable').each(function (filterable) {
        filterable.className.split(' ').each(function (className) {
          if (className.indexOf('filter-') == 0 && !tempFilters[className.substring(7)]) {
            tempFilters[className.substring(7)] = true;
          }
        });
      });
      
      Object.keys(tempFilters).sort().each(function (filtername) {
        addFilter(filtername);
      });
      
      if (Object.keys(filters).size() == 1) {
        filterbar.hide();
      } else {
        filterbar.show();
      }
    }
    
    function addFilter(filtername, active) {
      if (!filters[filtername]) {
        var filter = new Element('a', {'href': SUI.voidLink, 'class':'sui-filter ' + (active ? 'active' : '')}).update(filtername);
        filterbar.insert(filter);
        filters[filtername] = filter;
        Event.observe(filter, 'click', function (event) {
          Event.stop(event);
          setFilter(filtername);
        });        
      }
    }
    
    function setFilter(filtername) {
      var filter = filters[filtername];
      filterbar.select('.sui-filter').each( function (filter) {
        filter.removeClassName('active');
      });
      filter.addClassName('active');
      
      if (filtername === 'all') {
        content.select('.filterable').each(Element.show);
      } else {
        content.select('.filterable').each(Element.hide);
        content.select('.filterable.filter-' + filtername).each(Element.show);
      }
    }
    
    filterObject = {
      setObject: setObject,
      element: filterbar,
      addFilter: addFilter,
      setFilter: setFilter
    };
    
    spiceworks.ui.filterbars.push(filterObject);
    
    return filterObject;
  };
  
  spiceworks.ui.filterbars = [];
  
  spiceworks.ui.refreshAllFilterbars = function () {
    spiceworks.ui.filterbars.each( function (filterbar) {
      filterbar.setFilter("all");
      filterbar.setObject();
    });
  };
  
  // Button bar
  spiceworks.ui.togglebar = function(element, spec) {
    var buttons = {}, content, loadingMsg;
        
    element = $(element);
    content = $(spec.content);
    
    loadingMsg = Object.extend({ message: 'Loading&hellip;', matchHeight: true, 'class': 'loading' }, spec.loadingMessage || {});   
    
    function setObject(object) {
      element.select('.sui-toggle').each(function (button) {
        Event.observe(button, 'click', function (event) {
          setButton(button);
        });
      });
    }
    
    function clearButtons(section) {
      if (!section) { section = element; }
      
      section.select('.sui-toggle').each(function (button) {
        button.removeClassName("active");
      });
    }
    
    function setButton(button) {
      clearButtons(button.up());
      if (content) {
        LoadingMessage.set(content, loadingMsg.message, loadingMsg);
      }
      button.addClassName('active');
    }
    
    return {
      setObject: setObject,
      element: element,
      setButton: setButton,
      clearButtons: clearButtons
    };
  };
  
  spiceworks.ui.choicePill = function(element, spec) {
    var wrapper,
        hiddenInput,
        choiceElems,
        first = true;
        
    element = $(element);
    spec.options = spec.options || {};
    
    
    // remove the element with this id if it already exists...
    // if (spec.options.id || $(spec.options.id)) {
    //   $(spec.options.id).up('.sui-choice-pill').remove();
    // }
    
    choiceElems = [];
    
    wrapper = new Element('span', {'class':'sui-choice-pill'});
    hiddenInput = new Element('input', {'type':'hidden', 'name':spec.name, 'value':spec.value, 'id':spec.options.id || ''});
    wrapper.insert(hiddenInput);
    element.insert(wrapper);
    
    spec.choices.each( function (choice) {
      var name, value, choiceElem;
      name = choice[1];
      value = choice[0];
      options = choice[2] || {};
      choiceElem = new Element('a', {'class':'sui-pill ' + options['class'], 'href':SUI.voidLink, 'id': options['id']});
      if (first) {
        choiceElem.addClassName('sui-pill-first');
        first=false;
      }
      choiceElem.setAttribute('pillvalue', value);
      choiceElem.update(name);
      choiceElem.observe('click', function (event) {
        selectChoice(choiceElem);
      });
      choiceElems.push(choiceElem);
      wrapper.insert(choiceElem);
      
      if(value == spec.value){
        selectChoice(choiceElem);
      }
    });

    function selectChoice(choiceElem) {
      choiceElems.each( function(otherChoiceElem) {
        otherChoiceElem.removeClassName('sui-pill-selected');
      });
      choiceElem.addClassName('sui-pill-selected');
      hiddenInput.value = choiceElem.getAttribute('pillvalue');
      hiddenInput.fire('choice-pill:clicked');
    }
  };
  
  
  spiceworks.ui.simplemenu = function(activator, menu, spec) {
    // A menu system that should work for many purposes
    // Pass ids to the already-created activator and menu along with any of the following options
    
    // When menu is activated, activator gets the class "active" added to it
    
    // activateOn: by default 'mouseover'
    // update: optional callback that will get called with the menu object before the menu shows 
    // alignment: 'left', 'right' 
    // align: a callback that will get called with the menu and the activator
    // closeButton: one or more selectors that will close the window if clicked
    
    // FREE ADVICE:  Make the activator and the menu siblings, and your life will be easier in IE7
    
    var menuTimeout = null, showMenuTimeout = null, simpleMenu;
    activator = $(activator);
    menu = $(menu);
    
    if ((!menu) || (!activator)) { return; }
    
    spec = Object.extend({
      alignment: 'right',
      offsetLeft: 0, 
      offsetTop: 0,
      activateOn: 'mouseover',
      activatorZIndex: 500,
      menuZIndex:450,
      closeButton: [],
      update: function(menu) {}      
    }, spec || {});
    
    spec = Object.extend({
      align: function(menu, activator) {
        var offsetLeft, offsetTop;
        
        if (spec.alignment == "left") { offsetLeft = spec.offsetLeft; }          
        else { offsetLeft =  -($(menu).getWidth() - activator.getWidth() - 6 + spec.offsetLeft); }

        offsetTop = spec.offsetTop;
        
        menu.clonePosition(activator, {
          setHeight:false,
          setWidth:false,
          offsetTop: (activator.getHeight() - 3) + offsetTop,
          offsetLeft: offsetLeft
        });
      }
    }, spec);
    
    function unload() {
      Event.stopObserving(activator);
      Event.stopObserving(menu);
      document.stopObserving("simplemenu:show", menuShownEvent);
      
      menu.select(spec.closeButton).each(function(button) {
        button.observe('click', hideMenu);
      });
    }
    
    function hideMenu() {
      $(menu).hide();      
      activator.style.zIndex=0;
      activator.removeClassName("active");
      activator.blur();
      document.fire("simplemenu:hide", {id: menu.id});
    }
    
    function showMenu() {
      spec.update(menu);
      document.fire("simplemenu:show", {id: menu.id});
      $(menu).show();
      // we need to show the menu before aligning it so that offsetWidth is valid
      spec.align(menu, activator);
      activator.style.zIndex=spec.activatorZIndex;
      menu.style.zIndex=spec.menuZIndex;
      
      if ((activator.getStyle('position') != "absolute")) {
        activator.style.position = "relative";
      }
      
      activator.addClassName("active");
      activator.blur();
    }

    function activatorOver(event) {
      clearTimeout(showMenuTimeout);
      showMenuTimeout = setTimeout(showMenu, 200);
    }
    function activatorOut(event) {
      clearTimeout(showMenuTimeout);
      showMenuTimeout = setTimeout( hideMenu, 100);
    }
    
    function menuOver(event) {
      clearTimeout(showMenuTimeout);
      clearTimeout(menuTimeout);
    }
    
    function menuOut(event) {
      clearTimeout(menuTimeout);
      menuTimeout = setTimeout(hideMenu, 100);
    }

    function menuClicked(event) { 
      var el = event.element();      
      if (menu.select(spec.closeButton).include(el)) {
        hideMenu();
      }
    }
    
    function toggleMenuVisibility(event) {
      if ($(menu).visible()) { hideMenu(); }
      else { showMenu(); }
    }
    
    function menuShownEvent(event) {
      // This could be from any menu
      if (menu.id != event.memo.id) {
        hideMenu();
      }
    }
      
    if (spec.activateOn == "mouseover") {
      activator.observe('mouseover', activatorOver);
      activator.observe('mouseout', activatorOut);
      menu.observe('mouseover', menuOver);
      menu.observe('mouseout', menuOut);
      menu.observe('click', menuClicked);
    }
    else if (spec.activateOn == "click") {
      activator.observe('click', toggleMenuVisibility);
      menu.observe('click', menuClicked);
    }
      
    document.observe("simplemenu:show", menuShownEvent);

    simpleMenu = {
      activator: activator,
      menu: menu,
      show: showMenu,
      hide: hideMenu,
      unload: unload
    };
    
    return simpleMenu;
  };
  
  spiceworks.ui.elements =  {
    primary: {
      area: function() { return $("primary"); },
      header: function() {  return $("content").down("div.sui-header"); },
      breadcrumbs: function() { return $$("#content > div.sui-header > h1 > span.crumb"); },
      toolbar: function() { return $("primary").down("div.sui-toolbar"); }
    },
    secondary: {
      area: function() { return $("secondary"); },
      header: function() {  return $("secondary").down("div.sui-header"); },
      breadcrumbs: function() {  return $$("#secondary div.sui-header span.crumb"); },
      toolbar: function() { return $("secondary").down("div.sui-toolbar"); },
      tabbedBox: function() { return $("secondary").down("div.sui-tabbed-box"); }
    }
  };
  
  

  // This is used by the tabbed_player helper in layout helper
  // spec requires defaultSize: {width: int, height: int} to calculate aspect ratio for resizing
  // pass in autoResize to resize on page resize and on tab change
  spiceworks.ui.tabbedplayer = function(playerContainer, videoContainer, spec) {
    var tabs, video, videoHeight, containerHeight, videoWidth, playerContent, aspectRatio;
    
    spec.defaultSize = spec.defaultSize || { width: 400, height:267 };
    spec.unloadOn = spec.unloadOn || ['secondary:unload', 'tab:unload'];
    
    autoResize = spec.autoResize || false;
    aspectRatio = spec.defaultSize.width / spec.defaultSize.height;

    playerContainer = $(playerContainer);
    playerContent = playerContainer.down('div');

    tabs = playerContainer.down("ul");

    tabs.select("li > a").each(function(a) {
      a.observe("click", function(e) { setVideo(a); });
    });

    if (autoResize) { 
      videoWidth = "100%";
      Event.observe((document.onresize ? document : window), "resize", setHeight);
    }
    else {
      videoWidth = spec.width || spec.defaultSize.width;
      videoHeight = spec.height || spec.defaultSize.height;
    }

    function setObject(object) {
      setVideo(tabs.down("li.active a") || tabs.down("li a"));
    }

    function setHeight() {
      if (!videoContainer) { return; }
      
      videoHeight = ($(videoContainer).up(".video_column").getWidth() / aspectRatio);
      if (spec.defaultSize.height > videoHeight) { videoHeight = spec.defaultSize.height; }
      $(videoContainer).setAttribute('height', videoHeight);
      playerContent.setStyle('height: ' +  videoHeight + 'px');
    }

    function setVideo(videoLink) {
      if (autoResize) { setHeight(); }

      var link = videoLink.getAttribute('href');
      videoLink.up("li").siblings().invoke("removeClassName", "active");
      videoLink.up("li").addClassName("active");
      
      $(videoContainer).setStyle({width: videoWidth, height:videoHeight});
      
      swfobject.embedSWF(link, videoContainer, videoWidth, videoHeight, "9.0.0", "/flash/expressinstall.swf", {}, {wmode: 'transparent', allowfullscreen: 'true'});
    }
    
    function unload() {
      tabs.select("li > a").invoke("stopObserving");
      Event.stopObserving((document.onresize ? document : window), "resize", setHeight);
    }
    
    SPICEWORKS.utils.unloader(spec.unloadOn, unload);

    playerObject = {
      setObject: setObject,
      setVideo: setVideo,
      playerContainer: playerContainer,
      videoContainer: videoContainer
    };
    
    return playerObject;
  };
  
  spiceworks.ui.tabbedbox = function(tabbedBox, spec) {
    tabbedBox = $(tabbedBox);
    
    var ul = tabbedBox.down("div.sui-tabs ul"), 
    firstTab = tabbedBox.down("div.sui-tabs ul li"),
    moreActivator = tabbedBox.down("div.sui-tabs div.sui-tab-more"),
    sheet = tabbedBox.down('div.sui-sheet'),
    state = {}, o = {}, li, simpleMenu, showMore, events = $A();
    
    spec = Object.extend({
      requestClass: 'secondary/tab',
      uriKey: 'tab',
      updateURI: true, 
      unloadOn: 'secondary:unload'
    }, spec || {});
    
    spec = Object.extend({
      afterSelect: function(s) {
        if (s.activeTabName !== null) { // if this is null, the page will jump to the top in IE
          o[spec.uriKey] = s.activeTabName;
          Application.updateState(o, {updateUri: spec.updateURI});
        }          
      }
    }, spec);
    

    function tabSelected(event) {
      li = event.findElement("li");
      if (!li) { return; }
      
      selectTab(li);
      event.element().blur();
    }
    
    function selectTab(li, callback) {
      li = $(li);
      if (!li) { return; }

      document.fire('tab:unload');
      ul.select('li').invoke("removeClassName", "active");
      li.addClassName("active");
      loadContent(li.down('a').getAttribute('click_url'), callback);

      updateActiveState(li);
      spec.afterSelect(state);
      
      document.fire('tab:selected', object);
    }
    
    function loadContent(url, callback) {      
      LoadingMessage.set(sheet, "Loading &hellip;");      
      
      new Ajax.Request(url, {
        asynchronous:true, 
        evalScripts: true, 
        parameters:{requestClass: spec.requestClass},
        method: 'get',
        onFailure:function(request){
          StatusMessage.set(sheet, "Unable to load tab, please try again");
        },
        onComplete: function(request){
          if (callback) callback(request);
        }
      });
    }
    
    function updateActiveState(li) {
      if (!$(li)) { return; }
      
      state.activeTab = li;      
      state.activeTabName = (li.getAttribute("id") || "").split("_tab")[0];  
    }
      
    function unload() {
      Event.stopObserving(ul);
      $(moreMenuContent).select("li").invoke("stopObserving");
      simpleMenu.unload();
            
      document.stopObserving("secondary:unload", unload);
      Event.stopObserving((document.onresize ? document : window), "resize", updateMoreVisibility);
    }
    
    function updateMoreVisibility(){
      showMore = false;
      tabbedBox.select("div.sui-tabs ul li").reverse().each(function(tab) {
        if (Math.abs(firstTab.cumulativeOffset()['top'] - tab.cumulativeOffset()['top']) > 5) {
          showMore = true;
          throw $break;
        }
      });
      moreActivator[showMore ? 'show' : 'hide']();
    }
    
    SPICEWORKS.utils.unloader(spec.unloadOn, unload);    
    Event.observe((document.onresize ? document : window), "resize", updateMoreVisibility);
    Event.observe(ul, "click", tabSelected);
        
    // Initialize internal state registry, and passed the currently selected tab to Application.
    // We need this on load since ocassionally we request the tab when submitting forms, etc
    updateActiveState(tabbedBox.down("div.sui-tabs > ul > li.active"));
    if (state.activeTabName !== null) {
      o[spec.uriKey] = state.activeTabName;
      // Don't update the URL on load, as IE freaks out and refreshes on anchor param changes, sometimes.
      Application.updateState(o, {updateUri: false});
    }
    
    // If the anchor param in the url is different the currently loaded tab, jump to it      
    if (Application.getUriAnchorParams().get(spec.uriKey) != state.activeTabName) {
       selectTab(Application.getUriAnchorParams().get(spec.uriKey) + "_tab");
    }
    
    // Set up more menu 
    updateMoreVisibility();
    
    var moreMenuContent = new Element('ul', {'class': 'menu'});
    tabbedBox.select("div.sui-tabs ul li a").each( function(a) {
      var li = new Element('li').update(a.clone().update(a.innerHTML));
      li.observe('click', function() { selectTab(a.up()); } );            
      moreMenuContent.insert(li);      
    });
    
    var contentID = "tab_more_content_" + (Math.floor(Math.random()*1000+1)).toString();
    $(tabbedBox).insert(new Element('div', {id: contentID, 'class': 'tab-more-menu simple-menu', style: 'display:none;'} ).update(moreMenuContent));
    
    var simpleMenu = SUI.simplemenu(moreActivator.down('a'), contentID, {alignment: 'right', update: function(content) {
      var items = $(content).select("ul.menu li");
      if (moreActivator) {
        items.invoke("show");
        items.invoke("removeClassName", "active");
        tabbedBox.select("div.sui-tabs ul li").each(function(tab, index) {
          if (tab.hasClassName('active')) { items[index].addClassName('active'); }
          items[index][(Math.abs(firstTab.cumulativeOffset()['top'] - tab.cumulativeOffset()['top']) > 5) ?  'show' : 'hide']();
        });
      }
    }});
    
    Object.extend(tabbedBox, {
      selectTab: selectTab,
      state: state
    });
    
    var object = {
      tabbedBox: tabbedBox,
      selectTab: selectTab,
      moreMenu: simpleMenu,
      state: state,
      unload: unload
    };
    
    return object;
  };
  
  
  // A nice shortcut for building Spiceworks UI elements.
  window.SUI = spiceworks.ui;
  
  // == SPICEWORKS.app.helpdesk ==
  // Currently, we just have stuff in here to help with events
  spiceworks.app.helpdesk = function () {
    var that = {
      ready: ready_func('app:helpdesk:ready'),
      newTicket: {
        ready: ready_func('app:helpdesk:new_ticket:ready')
      },
      ticket: {
        ready: ready_func('app:helpdesk:ticket:ready'),
        closed: ready_func('app:helpdesk:ticket:closed'),
        comments:{
          ready: ready_func('app:helpdesk:ticket:comments:ready')
        },
        purchase_list:{
          ready: ready_func('app:helpdesk:ticket:purchase_list:ready')
        },
        editor:{
          ready: ready_func('app:helpdesk:ticket:editor:ready')
        },
        overview: {
          ready: ready_func('app:helpdesk:ticket:overview:ready')
        }
      }
    };
    return that;
  }();
  
  
  // == SPICEWORKS.app.inventory ==
  // Currently, we just have stuff in here to help with events
  spiceworks.app.inventory = function () {
    var that = {
      ready: ready_func('app:inventory:ready'),
      
      environment: {
        ready: ready_func('app:inventory:environment_summary:ready')
      },

      group: {
        ready: ready_func('app:inventory:group:ready'),
        device: {
          ready: ready_func('app:inventory:group:device:ready')
        },
        item: {
          ready: ready_func('app:inventory:group:item:ready')
        },
        environment: {
          ready: ready_func('app:inventory:group:environment_summary:ready')
        }
      },      
      
      softwareGroup: {
        ready: ready_func('app:inventory:software_group:ready'),
        
        environment: {
          ready: ready_func('app:inventory:software_group:environment_summary:ready')
        },

        software:{
          ready: ready_func('app:inventory:software_group:software:ready')
        },
        service:{
          ready: ready_func('app:inventory:software_group:services:ready')
        },
        hotfix:{
          ready: ready_func('app:inventory:software_group:hotfixes:ready')
        }        
      }
    };
    return that;
  }();
    
  // == SPICEWORKS.app.messaging ==
  // Allow users to push and pop messages into the header messaging area
  // This is a fairly thin wrapper around the current messaging infrastructure.
  spiceworks.app.messaging = {
    push: function (messageText, options) {
      var message = Object.extend({
        dismissable: false, // give the message a clickable element to remove it
        selfRemoving: false, // make the message remove itself after timeSeconds have lapsed
        timeoutSeconds: 5, // when selfRemoving is true, this is time duration the message is displayed
        id: 'p_' + (new Date()).getTime()
      }, options || {});
      
      Messaging.push( message.id, messageText, message );
            
      return message.id;
    },
    
    pop: function (messageId) {
      Messaging.pop(messageId);
    }
  };
  
  // == SPICEWORKS.app.settings ==
  spiceworks.app.settings = function () {
    var that = {
      ready: ready_func('app:settings:ready')
    };
    
    // Setup ready functions for each othe settings panes.
    ['help_desk', 'monitors', 'network', 'email', 'accounts', 'spicemeter', 'events', 'categories', 'backup', 'advanced'].each(function (settingsPane) {
      that[settingsPane] = {
        ready: ready_func('app:settings:' + settingsPane + ':ready')
      };
    }); 
    
    that.users = that.accounts;   
    
    return that;
  }();
  
  
  // == SPICEWORKS.app.settings.plugin ==
  // Some helpers for extending the plugins page.  Currently, there's only support here for
  // adding example code to the "Insert Code" dropdown.
  spiceworks.app.settings.plugin = function () {
    var codeExamples = [],
        that;
    
    that = {
      
      ready: ready_func( 'app:settings:plugin:ready' ),
      
      viewer: {
        ready: ready_func('app:settings:plugin:viewer:ready')
      },
      
      editor: {
        ready: ready_func( 'app:settings:plugin:editor:ready' ),

        // spec needs {label: '', code: ''}
        addCodeExample: function (spec) {
          codeExamples.push(spec);
        },
        
        getCodeExamples: function () {
          return codeExamples;
        },
        
        insertCode: function (code) {
          // IE doesn't like it when the pre tag doesn't have focus and you try to insert code.
          if (Browser.ie6 || Browser.ie7) {
            plugin_content.editor.body.getElementsByTagName('pre')[0].focus();
            code = code.gsub(/\n/, '\n\n'); // IE doesn't deal with new lines properly for some reason.
          }

          plugin_content.editor.insertCode(code);
          plugin_content.editor.syntaxHighlight();
        },
        
        setCode: function (code) {
          // IE doesn't like it when the pre tag doesn't have focus and you try to insert code.
          if (Browser.ie6 || Browser.ie7) {
            plugin_content.editor.body.getElementsByTagName('pre')[0].focus();
            code = code.gsub(/\n/, '\n\n'); // IE doesn't deal with new lines properly for some reason.
          }

          plugin_content.editor.setCode(code);
          plugin_content.editor.syntaxHighlight();          
        }
        
      }
    };
    
    
    // Now watch for this event and actually plug in the new code.
    that.editor.ready( function () {
      var elem = $('insert_code_list'),
          link, li;

      codeExamples.each( function (example) {
        li = new Element('li');
        link = new Element('a', {href: '#'} );
        li.insert(link);
        link.update( example.label );
        
        Event.observe( link, 'click', function (e) {
          Event.stop(e);
          that.editor.insertCode( example.code );
        });
        
        elem.insert(li);
      });
    });
    
    return that;
  }();
  
  // == SPICEWORKS.field ==
  // Datatypes that will be used to render editors
  // These objects are delegates that will handle rendering different types of values in the configuration sections of widgets
  // Field builders are expected to return an object with a set() and get() method.  They can return more than this, but these two
  // are required for them to work with widgets and the dashboard.
  spiceworks.field = {
    // creation helper
    // fieldDesc => { name:'foo', type:'string', label:'Foo', defaultValue:'Life is good' }
    createField: function (element, fieldDef, value) {
      var field = (spiceworks.field[fieldDef.type] || spiceworks.field.string)(element, fieldDef);
      if (value) { 
        field.set(value); 
      }
      return field;
    },
    
    // Create a set of fields within the element passed using the options passed.
    // options should be like widget options (including an id)
    createFieldSet: function (element, fieldDefs) {
      var fields = {},
          nsField = spiceworks.field,  // temp namespace
          fieldset;
          
      fieldset = {
        // return all the values
        get: function () {
          var options = {};
          Object.keys(fields).each(function (fieldname) {
            options[fieldname] = fields[fieldname].get();
          });
          return options;
        },
        
        set: function (options) {
          Object.keys(fields).each(function (fieldname) {
            fields[fieldname].set(options[fieldname]);
          });
        },
        fields: fields      
      };
      
      // Initialize the fields
      fieldDefs.each(function (field) {
        var fieldElement = new Element('div', {'class': field.name + ' setting', 'id':field.name + "_" + (Math.floor(Math.random()*1000+1))});
        element.insert(fieldElement);
        fields[field.name] = nsField.createField(fieldElement, field);
        //if( options[field.name] ){  fields[field.name].set( options[field.name] ); }
      });
      
      return fieldset;
    },
    

    // TYPES
    // TODO: add validation...

    // Hidden value, accessible by Spiceworks on the backend, but not visible to the end user.
    hidden: function (element, fieldDef) {
      return {
        get: function () {
          fieldDef.defaultValue;
        },
        set: function (value) {
          // Do nothing!
        }
      }
    },

    string: function (element, fieldDef) {
      var id = element.id + "_value";
      element.insert("<label for='" + id + "'>" + fieldDef.label + "</label>");
      element.insert("<input id='" + id + "' name='" + id + "' type='string' class='text' value='" + (fieldDef.defaultValue ? fieldDef.defaultValue : '') + "'></input>");
      if (fieldDef.example) {
        element.insert(" <span class='example'>" + fieldDef.example + "</span>");
      }
      return {
        get: function () {
          return element.down('input').value;
        },
        set: function (value) {
          element.down('input').value = value;
        }
      };
    },
    
    enumeration: function (element, fieldDef) {
      var select, 
          id = element.id + "_value";
          
      element.insert("<label for='" + id + "'>" + fieldDef.label + "</label>");
      
      select = new Element('select', {'id': id, 'name': id});
      element.insert(select);
      
      if (fieldDef.options) {
        fieldDef.options.each( function (option) {
          if(typeof(option)==='string'){
            select.insert(new Element('option').update(option));            
          }else{
            select.insert(new Element('option', {'value':option[1]}).update(option[0]));
          }
        });
      }
      
      if (fieldDef.example) {
        element.insert(" <span class='example'>" + fieldDef.example + "</span>");
      }
      
      return {
        get: function () {
          return $F(select);
        },
        set: function (value) {
          for(index = 0; index < select.options.length; index++) {
            if ((select.options[index].value || select.options[index].text) === value) {
              select.selectedIndex = index;
            }
          }
        }
      };
    },

    textarea: function (element, fieldDef) {
      var textarea,
          id = element.id + "_value";   
      element.insert("<label for='" + id + "'>" + fieldDef.label + "</label>");  
      if (fieldDef.example) {
        element.insert("<div style='float:right;width: 210px'><span  class='example' style='vertical-align:top'>" + fieldDef.example + "</span></div>");
      }
      textarea = new Element('textarea', {'id': id, 'name': id, 'style':''});
      element.insert(textarea);

      return {
        get: function () {
          return textarea.value;
        },
        set: function (value) {
          textarea.value = value;
        }
      };
    },
    
    checkbox: function (element, fieldDef) {
      var checkbox,
          id = element.id + "_value";
      element.insert("<label for='" + id + "'>" + fieldDef.label + "</label>");  
      checkbox = new Element('input', {'id':id, 'type':'checkbox'});
      element.insert(checkbox);
      
      if (fieldDef.example) {
        element.insert(" <span class='example'>" + fieldDef.example + "</span>");
      }
      
      return {
        get: function () {
          return checkbox.checked;
        },
        set: function(value) {
          checkbox.checked = value;
        }
      };
          
    },
    
    date: function (element, fieldDef) {
      var trigger, input,  id = element.id + "_value";
      element.insert("<label for='" + id + "'>" + fieldDef.label + "</label>");
      
      input = new Element('input', {'id':id, 'name':id, 'type':'string', 'class':'text date_field'});
      element.insert(input);
      
      trigger = new Element('a', {'id': id + '_trigger', 'href':'javascript:void(0)', 'class':'calendar_trigger'} ).update('<img title="Choose a date from the calendar" src="/images/icons/calendar.png" alt="Calendar"/>');
      element.insert(trigger);
      
      CalendarPopup.setup(input, trigger);
      
      if (fieldDef.example) {
        element.insert(" <span class='example'>" + fieldDef.example + "</span>");
      }
      
      return {
        get: function () {
          return input.value;
        },
        set: function (value) {
          input.value = value.gsub('/','-');
        }
      };
    }
    
  };
  
  // Aliases for enumeration and string to make them more like the elements that they're creating.
  spiceworks.field.select = spiceworks.field.enumeration;
  spiceworks.field.textbox = spiceworks.field.string;



  // == SPICEWORKS.data ==
  spiceworks.data = function () {
    var tempStore = {},
        that;
    
    that = {
      
      // Models
      // This is very alpha right now.  Check here for status updates as we fix this.
      // See Jester Docs for more detail on how to use these: http://thoughtbot.com/projects/jester
      Ticket:       Jester.Resource.model('Ticket', {prefix: '/api', format: 'json'}),
      Device:       Jester.Resource.model('Device', {prefix: '/api', format: 'json'}),
      Alert:        Jester.Resource.model('Alert', {prefix: '/api', format:'json'}),
      DataMonitor:  Jester.Resource.model('DataMonitor', {prefix: '/api', format: 'json'}),
      Activity:     Jester.Resource.model('Activity', {format: 'json'}),
      Agreement:    Jester.Resource.model('Agreement', {format: 'json'}),

      // Following need more testing
      Group:        Jester.Resource.model('Group', {prefix: '/api', format:'json'}),
      Software:     Jester.Resource.model('Software', {prefix: '/api', format:'json', plural:'software'}),
      Service:      Jester.Resource.model('Service', {prefix: '/api', format:'json', plural:'services'}),
      Hotfix:       Jester.Resource.model('Hotfix', {prefix: '/api', format:'json', plural:'hotfixes'}),
      User:         Jester.Resource.model('User', {prefix: '/api', format:'json' }),
      Report:       Jester.Resource.model('Report', {prefix: '/api', format:'json' }),
      

      // Run a report and pass the resulting JSON to the callback...
      // callback => function(json, transport){}
      // TODO: get report name working as well as id
      runReport: function (report_id, callback) {
        (new Ajax.Request('/reports/show/' + report_id + '.json', {
          method: 'get',
          onSuccess: function (transport) {
            var json = transport.responseText.evalJSON();
            callback(json, transport);
          },
          onFailure: function (transport) {
            callback({ items:[], columns: [], error: transport.responseText });
          }
        }));
      }
    };

    return that;
  }();

  // == SPICEWORKS.persistence ==
  // Store documents (hashes) in a specified location for later retrieval.
  // Currently, just a key/value store.  Document can be a hash, an array, or just a string.
  spiceworks.persistence = function () {
    // helper method to process results from calls
    function wrap(callback) {
      if (callback) {
        return function (transport) {
          var result = transport.responseText;
          if (result.isJSON()) {
            result = result.evalJSON();
          }
          callback(result, transport);
        };        
      } else {
        return function () {};
      }
    }
    
    var that = {      
      // callback is optional
      store: function (key, doc, callback) {
        (new Ajax.Request('/documents/' + key + '.json', {
          method: 'put',
          parameters: { 'doc': Object.toJSON(doc) },
          onSuccess: wrap(callback)
        }));
      },
            
      load: function (key, callback, defaultValue) {
        (new Ajax.Request('/documents/' + key + '.json', {
          method: 'get',
          onSuccess: wrap(callback),
          onFailure: function (transport) {
            callback(defaultValue,transport);
          }
        }));
      },
      
      loadAll: function (prefix, callback, defaultValue) {
        if (prefix && prefix !== '') {
          (new Ajax.Request('/documents.json?prefix=' + prefix, {
            method: 'get',
            onSuccess: wrap(callback),
            onFailure: function (transport) {
              callback(defaultValue,transport);
            }
          }));          
        }
      },
      
      remove: function (key, callback) {
        (new Ajax.Request('/documents/' + key + '.json', {
          method: 'delete',
          onSuccess: wrap(callback)
        }));
      }
    };
    
    return that;
  }();
  
  
  // == SPICEWORKS.configuration ==
  // Store documents (hashes) in a specified location for later retrieval.
  // Currently, just a key/value store.  Configuration can be a hash, an array, or just a string.
  spiceworks.configuration = function () {
    // helper method to process results from calls
    function wrap(callback) {
      if (callback) {
        return function (transport) {
          var result = transport.responseText;
          if (result.isJSON()) {
            result = result.evalJSON();
          }
          callback(result, transport);
        };        
      } else {
        return function () {};
      }
    }
    
    var that = {      
      // callback is optional
      store: function (key, doc, callback) {
        (new Ajax.Request('/configuration/' + key + '.json', {
          method: 'put',
          parameters: { 'value': Object.toJSON(doc) },
          onSuccess: wrap(callback)
        }));
      },
            
      load: function (key, callback, defaultValue) {
        (new Ajax.Request('/configuration/' + key + '.json', {
          method: 'get',
          onSuccess: wrap(callback),
          onFailure: function (transport) {
            callback(defaultValue,transport);
          }
        }));
      },
      
      loadAll: function (prefix, callback, defaultValue) {
        if (prefix && prefix !== '') {
          (new Ajax.Request('/configuration.json?prefix=' + prefix, {
            method: 'get',
            onSuccess: wrap(callback),
            onFailure: function (transport) {
              callback(defaultValue,transport);
            }
          }));          
        }
      },
      
      remove: function (key, callback) {
        (new Ajax.Request('/configuration/' + key + '.json', {
          method: 'delete',
          onSuccess: wrap(callback),
          onFailure: wrap(callback)
        }));
      }
    };
    
    return that;
  }();
  
  // == SPICEWORKS.community ==
  // Namespace for several APIs to pull down community content as JSON
  spiceworks.community = function () {
    var that;
    var communityHost;
    function addDefaultParams(params, callback){
      return Object.extend(params, {
        'uuid':Application.uuid,
        'ehash':Application.ehash,
        'callback':callback
      });
    }
    that = {
      loadJSON: function(path, params, callback){
        spiceworks.utils.include('http://' + that.host + path + '?' + Object.toQueryString(addDefaultParams(params, callback)));
      },
      setHost: function(host){
        that.host = host;
      },
      url: function(path){
        return 'http://' + this.host + path;
      },
      linkTo: function(path){
        return new Element('a', {
          'href': '/community/login?redirect=#{path}'.interpolate({'path': encodeURIComponent(path)}),
          'onclick': 'Community.go(#{path})'.interpolate({'path': path.toJSON()})
        });
      }
    };
    return that;
  }();
  
  
  // == SPICEWORKS.community.products ==
  // Get product information and community content by sending up model and manufacturer
  spiceworks.community.products = function () {
    var that;
    that = {
      getProductData: function(params, callback){
        spiceworks.community.loadJSON('/api/products/product_data.json', params, callback);
      }
    };
    return that;
  }();

  // == SPICEWORKS.config ==
  // placeholder for configuration options
  // see _script_includes.html.erb for more info on how this gets setup.
  spiceworks.config = {};
  
  
  // == SPICEWORKS.version ==
  // see _script_includes.html.erb for more info on how this gets setup.
  // if( SPICEWORKS.version && SPICEWORKS.version.full_version)
  spiceworks.version = function () {
    function pad(str, length){
      str = new String(str);
      while (str.length < length) { 
        str = '0' + str; 
      }
      return str;
    }
    function normalize(major, minor, revision) {
      var normalized = [pad(major, 2), pad(minor,2), pad(revision, 10)].join('');
      return parseInt(normalized, 10);
    }
    
    var version = {
      pad: pad,
      normalize: normalize,
      setVersionInfo: function (versionInfo) {
        Object.keys(versionInfo).each(function (key) {
          version[key.sub(/_vers?ion|svn_/, '')] = versionInfo[key];
        });
      },
      
      toString: function () {
        return version.full;
      },
      
      // A helper function to figure out what version is running.
      // EX: version.is('>= 4')
      // Parameter Ex: >= 4, >= 4.1, = 4.5.12345
      is: function (check) {
        var match, operator,
            major, minor, revision,
            curMajor, curMinor, curRevision,
            check;
        
        match = check.match(/(\>|\<|\={1,2}|\>\=|\<\=)\s?(\d+)\.?(\d*)\.?(\d*)/);
        
        if (!match) {
          throw "Expects a string in the form '[operator] [version]'.  Ex: SPICEWORKS.version.is('>= 4.0')";
        }
        
        operator = match[1];
        major = match[2];
        minor = match[3];
        revision = match[4];
        
        curMajor = version.major;
        curMinor = version.minor;
        curRevision = version.revision;

        if (operator == '=') {
          operator = '==';
        }
        if (minor == '') {
          curMinor = 0;
        }
        if (revision == '') {
          curRevision = 0;
        }
        
        check = normalize(curMajor, curMinor, curRevision) + 
                operator + 
                normalize(major, minor, revision);
        
        return eval(check);
      }
    };
    
    return version;
  }();


  // == SPICEWORKS.utils ==
  // A namespace for things that don't fit anywhere else currently.  These are mostly related to integration
  // with external services and the like.
  spiceworks.utils = {};
  
  // Include an external script
  // EX:
  //   SPICEWORKS.utils.include('http://myserver/my.js');
  spiceworks.utils.include = function (script_filename, callback) {
    var head = document.getElementsByTagName('head')[0], 
        script = document.createElement('script');
        
    script.type = 'text/javascript';
    script.src = script_filename;

    if (callback){
      var done = false;
      script.onload = script.onreadystatechange = function(){
        if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
          done = true;
          callback();

          // Handle memory leak in IE
          script.onload = script.onreadystatechange = null;
          script.parentNode.removeChild(script);
        }
      };
    }

    head.appendChild(script);
  };
  
  // Quickly add some style to the document.  Any valid Stylesheet style string works here.
  // EX:  SPICEWORKS.utils.addStyle("body { background-color: red }");
  spiceworks.utils.addStyle = function (cssText) {
    var styleNode = document.createElement('style');
    styleNode.setAttribute("type", "text/css");
    if (styleNode.styleSheet) { // workaround for IE
      styleNode.styleSheet.cssText = cssText;
    } else if (Prototype.Browser.WebKit) { 
      styleNode.innerText = cssText;
    } else { // DOM
      styleNode.update(cssText);
    }
    $$('head').first().appendChild(styleNode);
  };
  
  
  // == SPICEWORKS.utils.google ==
  // Namespace for all sorts of nice google goodies
  // Users will need to set their own "key" by setting 
  //    SPICEWORKS.utils.google.key = 'mykey';
  spiceworks.utils.google = function () {
    var googleLoaded = false,
        google = null,
        that;
    
    that = {
      key: 'ABQIAAAAXU7eNwZ9M4Sc9cn16StyDBRFChKT55K5FMfS9iKz1mkixBESPBSb4vJEgLxDzIfGJkiGB3x3myIrcQ',
      
      withGoogle: function (callback) {
        if (!googleLoaded) {
          spiceworks.utils.include("http://www.google.com/jsapi?key=" + that.key, function () {
            google = window.google; // google is global, just grab for now.
            googleLoaded = true;
            
            callback(google);
          });
        } else {
          callback(google);
        }
      },
      
      withMaps: function (callback) {
        that.withGoogle(function (google) {
          google.load('maps', '2.x', {callback: function () {
            callback(GMap2);
          }});
        });
      },
      
      withVisualizations: function (packages, callback) {
        that.withGoogle(function (google) {
          google.load("visualization", "1", {packages: packages, callback: function () {
            callback(google.visualization);
          }});
        });        
      }
    };
    
    return that;
  }();
  
  
  spiceworks.utils.unloader = function(events, callback) {
    events = $A([events]).flatten();    
    events.each(function(eventName) {
      var unload = function(){
        callback();
        document.stopObserving(eventName, unload);
      };
      
      document.observe(eventName, unload);
    });
  };
  
  
  // ------- HELPER FUNCTIONS ------
  // Helper function to define spiceworks observer functions
  function ready_func(event) {
    return function( callback ) {
      spiceworks.observe(event, callback);
    };
  }
  
})(SPICEWORKS);


// =============================================================
// Using SPICEWORKS to configure areas of the product
// =============================================================

// Exmaple Plugin Editor Code
SPICEWORKS.app.settings.plugin.editor.addCodeExample({
  label: 'Widget Type',
  code: "\nSPICEWORKS.app.dashboard.addWidgetType({\n  name: 'my_widget',\n  label: 'My Widget',\n  settingDefinitions: [\n    {name: 'text', label: 'Widget Text Option', type: 'string', defaultValue: 'Hello World'}\n  ],\n\n  update: function (element, settings) {\n    element.innerHTML = settings.text;\n  }\n});\n"
});

SPICEWORKS.app.settings.plugin.editor.addCodeExample({
  label: 'Navigation Item',
  code: "\nSPICEWORKS.app.navigation.addItem({\n  name: 'my_page',\n  label: 'My Page',\n  update: function (element) {\n    /*update the element with information*/\n    element.innerHTML = 'Hello World';\n  }\n});\n"
});

SPICEWORKS.app.settings.plugin.editor.addCodeExample({
  label: 'Run Report',
  code: "\nSPICEWORKS.data.runReport( 'Report Name or ID', function (response) {\n  if (response.error) { /* Error occured, deal with it here */ }\n  \n  response.items.each(function (item) {\n    response.columns.each(function (column) {\n      /* process each cell of the table */\n      item[column];\n    });\n  });\n});\n"
});

SPICEWORKS.app.settings.plugin.editor.addCodeExample({
  label: 'Add Message',
  code: "\nvar mID = SPICEWORKS.app.messaging.push('Message Text');\n/* SPICEWORKS.app.messaging.pop(mID); */\n"
});

SPICEWORKS.app.settings.plugin.editor.addCodeExample({
  label: 'Plugin Configuration',
  code: "plugin.configure({\n  settingDefinitions:[\n    { name:'company', label:'Company', type:'string', defaultValue:'Spiceworks'}\n  ]\n});\n"
});


// == Make sure we've offered to install new types of plugins ==
// If a widget is not in the list of those that have already been installed,
// We need to notify the user and let them install the widget optionally.
SPICEWORKS.app.settings.plugin.ready(function () {
  SPICEWORKS.utils.addStyle("\
  ul.widgets_to_install{ margin: 0 40px;  border-top:1px solid #EEE }\
  ul.widgets_to_install li{ text-align:left; margin: 2px 0; padding:2px 5px; border-bottom:1px solid #EEE }\
  a.add-widget-button {display:block; height: 20px; text-decoration:none; padding: 1px 0 0 20px; background: transparent url(/images/icons/small/add_content.png) no-repeat 0 0}\
  a.add-widget-button:hover {text-decoration:underline}\
  div.add-widget {font-size:.9em; float:right}\
  ");
    
  var body, darkbox, lightbox, content, height, newContent, newWidgets, installedWidgets,
      widgetsInstalledlKey = 'SPICEWORKS_installed_widgets';
  
  window.resetWidgetInstalls = function () {
    SPICEWORKS.persistence.remove(widgetsInstalledlKey);
  };
  
  
  function offerToInstallNewWidgets(widgetsDocument) {
    installedWidgets = widgetsDocument;
    newWidgets = findNewWidgets(installedWidgets);
    if (newWidgets.length > 0) {
      showWidgetInstallationPopup(newWidgets);
    }
  }
  
  function showWidgetInstallationPopup(widgets) {
    newContent = [];
    body = $(document.body);
    var closeLink = null;
    
    height = [body.getHeight(), 'px'].join('');
    
    darkbox = new Element('div', {'class':'darkbox'});
    darkbox.setStyle({'height': height});
    body.insert(darkbox);

    lightbox = new Element('div', {'class':'lightbox'});
    lightbox.setStyle({'height':height});
    body.insert(lightbox);
    
    content = new Element('div', {'class':'content'});
    lightbox.insert(content);
    
    newContent.push("<h1>New Dashboard Content Detected</h1>");
    newContent.push("<p>Would you like to add content to your <a href='/dashboard?tab=home'>Dashboard</a> now?  (You can always do this later from the Dashboard toolbar)</p>");
    newContent.push("<ul class='widgets_to_install'>");
    
    widgets.each(function (widget) {
      newContent.push("<li>");
      newContent.push("<div class='add-widget'><a class='add-widget-button' widget='"+widget.name+"' href=''>Add to Dashboard</a></div>");
      newContent.push("<h2>" + widget.label + "</h2>");
      if (widget.installMessage) {
        newContent.push("<div style='clear:both'>" + widget.installMessage + "</div>");
      }
      newContent.push("</li>");
    });
    
    newContent.push("</ul>");
    newContent.push("<p class='btn'><a class='close-link' href='" + SUI.voidLink + "'><img src='/images/forms/buttons/medium/close.gif' border='0'/></a></p>");
    
    content.update(newContent.join(''));
    
    closeLink = content.down('.close-link');
    closeLink.observe('click', closeWidgetsPopup);
    closeLink.observe('mouseover', function () {
      closeLink.down('img').src='/images/forms/buttons/medium/close_hover.gif';
    });
    closeLink.observe('mouseout', function () {
      closeLink.down('img').src='/images/forms/buttons/medium/close.gif';
    });
    
    $$('a.add-widget-button').each( function (addWidgetButton) {
      addWidgetButton.observe('click', function (event) {
        Event.stop(event);
        var name = addWidgetButton.getAttribute('widget');
        addWidgetInstance(name, addWidgetButton);
      });
    });
  }
  
  // Add an instance of the named widget to the dashboard in the background.
  function addWidgetInstance(name, addWidgetButton) {
    var currentWidgets = SPICEWORKS.app.user.options.my_spiceworks.tabs[0].widgets;
    var id = name + '_1';
    var widget = SPICEWORKS.app.dashboard.getWidgetType(name);
    var defaultValues = widget.settingDefinitions.inject({}, function (h,v) { 
      h[v.name]=v.defaultValue; return h;
    });
    
    currentWidgets[id] = Object.extend({
      'id':id, 'order':-1, 'position':'left', 'title': widget.label,
      'name':name, "updateInterval": 90000, "classname": "javascript", "type": "javascript"
    }, defaultValues);
    
    SPICEWORKS.app.user.options.my_spiceworks.tabs[0].widgets = currentWidgets;
    
    var json = Object.toJSON(currentWidgets);
    new Ajax.Request('/dashboard/remember_state', { method: 'post', parameters:{'tab': 'home', 'widgets': json},
      onSuccess: function () {
        addWidgetButton.up('div.add-widget').update('added!').highlight();
      }
    });
  }
  
  function closeWidgetsPopup() {
    if(darkbox) {
      new Effect.BlindUp(darkbox);
      new Effect.BlindUp(lightbox);
      setTimeout( function () {
        darkbox.remove();
        lightbox.remove();
      }, 2000);
    }
    
    // Note the new widgets we've seen...
    newWidgets.each(function (widget) {
      installedWidgets.push(widget.name);
    });
    
    SPICEWORKS.persistence.store(widgetsInstalledlKey, installedWidgets);
  }
  
  function findNewWidgets(installedWidgets) {
    var newWidgets = [];
    var widgets = SPICEWORKS.app.dashboard.getWidgetTypes();
    widgets.each(function (widget) {
      if (!installedWidgets.member(widget.name)) {
        newWidgets.push( widget );
      }
    });
    return newWidgets;
  }
  
  SPICEWORKS.persistence.load(widgetsInstalledlKey, offerToInstallNewWidgets, ['it_pros_activity', 'spiceworks_tv']);
});


// == NAVIGATION MENUS ==


// DASHBOARD PAGE MENUS
SPICEWORKS.app.navigation.addMenuItem({
  selector: 'div#navigation dl a.dashboard_page_link',
  label: "Rename...", 
  itemName: 'rename',
  onclick: function (linkElem) {
    var tabId = linkElem.getAttribute('dashboard_id'),
        input = new Element('input', {type:'text', value: linkElem.innerHTML, 'class':'text'});
    input.setStyle("position:relative;z-index:500;margin-bottom:2px;margin-left:20px;margin-top:2px;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;width:120px;");
    linkElem.up().insert({top:input});
    linkElem.hide();
    Event.observe( input, 'keypress', function (event) {
      if (Event.KEY_RETURN == event.keyCode) {
        new Ajax.Request("/dashboard/rename_tab", {parameters: {'tab_name': input.value, 'tab_id': tabId}});
        linkElem.update(input.value);
      }
      if (Event.KEY_RETURN == event.keyCode || Event.KEY_ESC == event.keyCode){
        linkElem.show();
        input.remove();
      }
    });
    Event.observe( input, 'blur', function (event) {
      linkElem.show();
      input.remove();        
    });
    input.focus();
  }
});
SPICEWORKS.app.navigation.addMenuItem({
  selector: 'div#navigation dl a.dashboard_page_link.deletable',
  label: "Delete...", 
  itemName: 'delete',
  onclick: function (linkElem) {
    var tabId = linkElem.getAttribute('dashboard_id');
    var dd;
    if (confirm('Are you sure you want to delete "' + linkElem.innerHTML + '"?')) {
      dd = linkElem.up('dd');
      if(dd.hasClassName('current')){
        document.location.href = '/dashboard/delete_tab?tab=' + tabId;
      }else{
        new Ajax.Request('/dashboard/delete_tab', {parameters: {tab: tabId}});
      }
      new Effect.Fade(dd, {duration:0.5});
      setTimeout( function () { dd.remove(); }, 600 );
    }
  }
});


// MY TOOLS MENUS
SPICEWORKS.app.navigation.addMenuItems({
  selector: 'div#navigation dl dd.custom',
  items: [
    { // RENAME MENU ITEM
      label: "Rename...",
      itemName: 'rename',
      onclick: function (navItem) {
        var linkElem = navItem.down('a.portal_link'),
            toolId = linkElem.id.match( /custom_nav_(\d+)/ )[1],
            input = new Element('input', {type:'text', value: linkElem.innerHTML, 'class':'text'});

        input.setStyle("position:relative;z-index:500;margin-bottom:2px;margin-left:20px;margin-top:2px;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;width:120px;");
        linkElem.up().insert({top:input});
        linkElem.hide();
        Event.observe( input, 'keypress', function (event) {
          if (Event.KEY_RETURN == event.keyCode) {
            new Ajax.Request('/web_portal/update/' + toolId, {asynchronous:true, evalScripts:true, parameters:{'navigation[name]': input.value}});
            linkElem.update(input.value); // even though this will get updated automatically!
          }
          if (Event.KEY_RETURN == event.keyCode || Event.KEY_ESC == event.keyCode){
            linkElem.show();
            if (input) {
              input.remove();
              input = null;            
            }
          }
        });
        Event.observe( input, 'blur', function (event) {
          linkElem.show();
          if (input) {
            input.remove();
            input = null;            
          }
        });
        input.focus();
      }
    }, {  // EDIT DETAILS MENU ITEM
      label: "Edit Details...",
      itemName: 'edit_details',
      onclick: function (navItem) {
        var linkElem = navItem.down('a.portal_link'),
            toolId = linkElem.id.match( /custom_nav_(\d+)/ )[1];
        new Ajax.Request('/web_portal/edit/' + toolId, {asynchronous:true, evalScripts:true});
      }
    }, {  // DELETE MENU ITEM
      label: "Delete...",
      itemName: 'delete',
      onclick: function (navItem) {
        var linkElem = navItem.down('a.portal_link');
        var toolId = linkElem.id.match( /custom_nav_(\d+)/ )[1];
        if (confirm('Are you sure you want to delete "' + linkElem.innerHTML + '"?')) {        
          new Ajax.Request('/web_portal/destroy/'+toolId, {asynchronous:true, evalScripts:true});
          navItem.remove();
        }
      }
    }
  ] // end items
});

// HELP DESK MENUS
SPICEWORKS.app.navigation.addMenuItems({
  selector: 'div#navigation dl dd#nav_help_desk',
  items: [
    {
      label: "New Ticket...",
      itemName: 'new_ticket',
      onclick: function (navItem) {
        new Ajax.Request('/tickets/new', {method: 'post'});        
      }
    },{
      label: "User Portal",
      itemName: 'user_portal',
      href: '/helpdesk', 
      target: '_blank'
    },{
      itemName: 'ticket_separator', separator: true
    },{
      label: "Settings",
      itemName: 'help_desk_settings',
      href: '/settings/help_desk'
    },{
      label: "Email Settings",
      itemName: 'email_settings',
      href: '/settings/email'
    }
  ]
});  


// INVENTORY MENUS 
SPICEWORKS.app.navigation.addMenuItem({
  selector: 'div#navigation dl dd#nav_inventory',
  items: [
    {
      label: "New Asset",
      itemName: 'new_asset',
      onclick: function (navItem){
        new Ajax.Request('/asset/new', {method: 'post'}); 
      }
    },{
      itemName: 'inventory_separator', separator: true
    },{
      label: "Edit Groups",
      itemName: 'category_settings',
      href: '/settings/categories'
    },{
      label: "Scan Settings",
      itemName: 'scan_settings',
      href: '/settings/network'
    },{
      label: "Monitor Settings",
      itemName: 'monitor_settings',
      href: '/settings/monitors'
    }
  ]
});

// REPORT MENUS
SPICEWORKS.app.navigation.addMenuItem({
  selector: 'div#navigation dl dd#nav_reporting',
  items: [
    {
      label: "New Report",
      itemName: 'new_report',
      href: '/reports/new'
    },{
      label: "Shared Reports",
      itemName: 'shared_reports',
      onclick: function (navItem) {
        Community.go("/reports");
        return false;
      }        
    }
  ]
});

// ADD PAGE/TOOL LEFT NAV OPTIONS
// This is not in a nice Plugin-y API form yet.  Still sort of a manual process.
SPICEWORKS.app.ready( function () {
  // ADD PAGE
  var add_page = $('add_page');
  if(add_page){
    add_page.title = 'Add a new Dashboard Page';
    add_page.observe('click', function () {
      if($('add_page_form')){
        return; // do nothing if it's already there.
      }
      var dd = new Element('dd', {'class':'editing', 'style':'display:none', 'id':'add_page_form'});
      var form = new Element('form', {'class': 'simple', 'style':''});
      var input = new Element( 'input', {name: 'new_tab_name', 'class':'text', value:'Page Name' });
      input.setStyle("margin-bottom:2px;margin-top:2px;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;width:120px;");
      var saveBtn = new Element('input', {'name':'save', 'id':'add_page_save', 'class': 'image_button', 'type': 'image', 'src': '/images/forms/buttons/small/save.gif', 'alt':'Save'});
      var cancelBtn = new Element('input', {'name':'cancel', 'id':'add_page_cancel', 'class': 'image_button', 'type': 'image', 'src': '/images/forms/buttons/small/cancel.gif', 'alt':'Cancel'});
      var p = new Element('p', {'class':'btn','style':''});
      p.insert("<h1>New Dashboard Page</h1>"). insert(input).insert('<br/>').insert(saveBtn).insert(' ').insert(cancelBtn);
      form.insert(p);
    
      dd.insert(form);
      $('my_network_header').insert( {after: dd} );
      new Effect.BlindDown(dd,{duration:0.5});
    
      function hideAndRemove(event){
        var localdd = dd;
        if (event){
          Event.stop(event);
        }
        if (dd) {
          dd = null;
          new Effect.BlindUp(localdd,{duration:0.5}); 
          setTimeout( function () {
            localdd.remove();
            input = null;
          }, 500);
        }       
        return false; 
      }
      function save() {
        document.location = '/dashboard/add_tab?new_tab_name=' + encodeURIComponent( input.value );
      }
    
      Event.observe( input, 'keypress', function (event) {
        if (Event.KEY_RETURN == event.keyCode) {
          save();
        }
        if (Event.KEY_RETURN == event.keyCode || Event.KEY_ESC == event.keyCode){
          hideAndRemove(event);
        }
      });
      Event.observe(cancelBtn, 'click', hideAndRemove);
      Event.observe(saveBtn, 'click', function (event) {
        save();
        hideAndRemove(event);
        return false;
      });
      Event.observe(form, 'submit', function (event) {
        Event.stop(event);
        return false;
      });
    
      setTimeout(function () { input.focus(); }, 600);
    });
  }
  
  // ADD TOOL
  var add_tool = $('add_tool');
  if(add_tool){
    add_tool.title = 'Add an External Tool';
    add_tool.observe('click', function () {
      if (NavigationManager.shouldAllowAddNewClick()) { 
        new Ajax.Request('/web_portal/new', {asynchronous:true, evalScripts:true}); 
      } 
      return false;
    });      
  }
});


/* == PLUGIN CONFIGURATION == */
SPICEWORKS.app.settings.plugin.ready( function () {
  SPICEWORKS.plugin.getPlugins().each( function (plugin) {
    if (plugin.settingDefinitions) {
      var row = $$$('tr#' + plugin.guid ), actions, link;
      var configRow, configCell, configDiv, configFields, saveBtn, cancelBtn, btnDiv;

      actions = row.down('td.actions');
      link = new Element('a', {href:'javascript:void(0)', 'class':'configure_link'}).update('configure');
      // actions.down('.disabled_configure_link').remove();
      actions.insert(link);
      
      function showConfig(){
        configRow = new Element('tr', {'class':'edit-row'});
        configCell = new Element('td', {colSpan: row.cells.length});
        configForm = new Element('form', {style: 'display:none', 'class':'field_set'});
        configForm.insert('<h3>Configure ' + plugin.name + '</h3>');
        configForm.insert('<p>' + plugin.description + '</p>');
        
        saveBtn = new Element('input', {type:'image', src:'/images/forms/buttons/save.gif'});
        cancelBtn = new Element('input', {type:'image', src:'/images/forms/buttons/cancel.gif'});
        btnP = new Element('p', {'class':'btn'}).insert(saveBtn).insert(cancelBtn);
        
        configFields = SPICEWORKS.field.createFieldSet( configForm, plugin.settingDefinitions );
        configForm.insert(btnP);
        
        configCell.insert(configForm);
        configRow.insert(configCell);
        row.insert({after: configRow});

        configFields.set( plugin.settings );

        configForm.show();
        row.hide();
        
        Event.observe(cancelBtn, 'click', hideConfig);
        Event.observe(saveBtn, 'click', function () {
          plugin.storeSettings(configFields.get(), hideConfig);
        });
        Event.observe(configForm, 'submit', function (event) {
          Event.stop(event);
          return false;
        });
      }
      function hideConfig() {
        Event.stopObserving(saveBtn);
        Event.stopObserving(cancelBtn);
        row.show();
        configRow.remove();
        configRow = null;
      }
      function toggleConfig() {
        if( !configRow ){
          showConfig();
        } else {
          hideConfig();
        }
      }
      Event.observe(link, 'click', toggleConfig);
    }
  });
  
  // Hide the loading sign and show the table.
  new Effect.Fade('plugins_loading');
});

// =============================================================
// Frozen plugins from the community
// =============================================================
SPICEWORKS.ready(function () {
  
  SPICEWORKS.app.dashboard.addWidgetType({
    'name': 'it_pros_activity',
    'label': 'IT Pros Activity',
    'icon': '/images/icons/small/user.png',
    'prefs': [],

    'update': function (element, options) {
      if(Prototype.Browser.IE){
        element.insert('<p>Sorry, this widget does not yet work in Internet Explorer.</p>');
      } else {
        element.setStyle({'padding': '0', 'width': '338px'});

        // Build an <iframe>
        var iframe = new Element('iframe', {
          'src': '/community/popup?redirect=' + encodeURIComponent('/activity_widget'),
          'frameborder': 0,
          'scrolling': 'no',
          'style': 'border:none; margin:0; padding:0; height:291px; width:338px'
        });

        // Insert the <iframe> into the Spiceworks dashboard widget
        element.insert(iframe);
      }
    }
  });
  
  
  SPICEWORKS.app.dashboard.addWidgetType({
    'name': 'spiceworks_tv',
    'label': 'Spiceworks TV',
    'icon': '/images/icons/small/spiceworks_icon.png',
    'prefs': [],

    'update': function (element, options) {
      // Remove the padding so that the iframe goes to the very edge. IE needs a width attribute.
      element.setStyle({'padding': '0'});

      embedURL = "http://www.spiceworks.com/tv/widget/";

      // Build an <iframe>
      var iframe = new Element('iframe', {
        'src': embedURL,
        'frameborder': 0,
        'scrolling': 'no',
        'style': 'border:none; margin:0; padding:0; height:454px; width:100%'
      });

      // Insert the <iframe> into the Spiceworks dashboard widget
      element.insert(iframe, {align: 'center'});
    }
  });  

});


// =============================================================
// Template Editor
// =============================================================
SPICEWORKS.app.settings.help_desk.ready(function () {
    
    $$('.ticket-notifier-radio').each(function (radio) {
      radio.observe('click', function (event) {
        var useEventStyle = $F('ticket_notifier_templates_event');
        new Ajax.Request('/utility/set_trait/default_ticket_notifier', {
          onSuccess: function (request) {
            event.element().up('p').highlight();
          }, 
          parameters:'value=' + (useEventStyle ? 'true' : 'false')
        });
      });
    });
    
    
    
    $('ticket_notifier_template_opener').observe('click', function (event) {
      Effect.toggle('ticket_notifier_templates', 'blind', {duration:0.5});
    });
    
    // Hook up tabs
    $$('.sui-sheet-switcher').each(function (sheetSwitcher) {
      
      sheetSwitcher.observe('click', function (event) {
        Event.stop(event);
        sheetSwitcher.blur();

        $$('.sui-sheet').each(Element.hide);
        Element.show(sheetSwitcher.getAttribute('show_sheet'))

        sheetSwitcher.up('ul').select('li').each( function (li) {
          li.removeClassName('active');
        });

        sheetSwitcher.up('li').addClassName('active');
      });
      
    });
    
    // Register Buttons
    
    // PREVIEW
    $('ticket_notifier_preview_link').observe('click', function () {
      
      if (!subjectIsValid()) {
        return;
      }
      
      $('send_ticket_notifier_templates').show();
      $('ticket_notifier_preview_link').hide();
      
      
      var values = getTemplates();
      
      new Ajax.Request( "/settings/help_desk/send_ticket_notifier_preview", {
        parameters: {
          'ticket_id': $F('ticket_notifier_ticket_id'), 
          'event': $F('ticket_notifier_event'),
          'ticket_notifier_templates': Object.toJSON(values)
        },
        onSuccess: function (transport) {
          $('send_ticket_notifier_templates').hide();
          $('ticket_notifier_preview_link').show();
          showMessage('Preview emails sent to ' + SPICEWORKS.app.user.email);
        },
        onFailure: function (transport) {
          $('send_ticket_notifier_templates').hide();
          $('ticket_notifier_preview_link').show();
          showMessage("Error sending Preview!  " + transport.responseJSON.message, true);
        }
      });
    });
    
    // SAVE
    $('save_ticket_notifier_templates').observe('click', function () {
      
      if (!subjectIsValid()) {
        return;
      }
      // get Values and save them on the server
      var values = getTemplates();
      
      new Ajax.Request( "/settings/help_desk/send_ticket_notifier_preview", {
        parameters: {
          'ticket_id': $F('ticket_notifier_ticket_id'), 
          'event': $F('ticket_notifier_event'),
          'ticket_notifier_templates': Object.toJSON(values),
          'test_only':true
        },
        onSuccess: function (transport) {
          SPICEWORKS.configuration.store( 'ticket_notifier_templates', values, function (templates) {
            showMessage('Templates Saved');
          });
        },
        onFailure: function (transport) {
          showMessage(transport.responseJSON.message, true);
        }
      });
    });
    
    // CANCEL
    $('cancel_ticket_notifier_templates').observe('click', function () {
      Effect.toggle('ticket_notifier_templates', 'blind');      
    });
    
    // RESET
    $('reset_ticket_notifier_templates').observe('click', function (event) {
      if (confirm("Revert to the default templates?  This operation cannot be undone.")) {
        SPICEWORKS.configuration.remove('ticket_notifier_templates', function (templates) {
          var loc = "http://" + document.location.host + "/settings/help_desk?show_ticket_notifier_templates=true#beta_ticket_notification_templates";
          document.location = loc;
        });
      }
    });
    
    // Returns true or false...
    function subjectIsValid() {
      if ($F('template_editor_subject').match(/\{\{\s*ticket\.ref\s*\}\}/)) {
        return true;
      } else {
        showMessage('Subject is required to contain "{{ticket.ref}}"',true);
      }
    }
    
    function showMessage(msg, keepAround) {
      $('ticket_notifier_template_info_message').update(msg).highlight();
      
      if (!$('ticket_notifier_template_info_message_wrap').visible()) {
        (new Effect.BlindDown('ticket_notifier_template_info_message_wrap', { duration: .20 }));        
      }
      
      if (!keepAround) {
        setTimeout(function () {
          (new Effect.BlindUp('ticket_notifier_template_info_message_wrap', { duration: .20 }));
        }, 5000);        
      }
    }
    
    function getTemplates() {
      return {
        'subject': $F('template_editor_subject'),
        'text/html': $F('template_editor_text_html'),
        'text/plain': $F('template_editor_text_plain')
      };
    }
    
    
});


// END USER MANAGEMENT
SPICEWORKS.app.settings.users.ready(function () {
  
  function handleVisibilityChange(checkbox) {
    var userId = checkbox.id.split('_').last();
    var li = checkbox.up('li');
    
    new Ajax.Request('/settings/users/update/'+userId, {
      parameters: 'user[visible]=' + checkbox.checked,
      onSuccess: function (transport) {
        li.highlight();
        if (checkbox.checked) {
          li.addClassName('visible');
        } else {
          li.removeClassName('visible');
        }
      },
      onFailure: function (transport) {
      }
    });
    
  }
  
  $('end_users').observe('click', function (event) {
    var element = event.element();
    if (element.hasClassName('end-user-visible')) {
      handleVisibilityChange(element);
    }
  });
});
