/* Script: Core.js MooTools - My Object Oriented JavaScript Tools. License: MIT-style license. Copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/). Code & Documentation: [The MooTools production team](http://mootools.net/developers/). Inspiration: - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) */ var MooTools = { 'version': '1.2.1', 'build': '0d4845aab3d9a4fdee2f0d4a6dd59210e4b697cf' }; var Native = function(options){ options = options || {}; var name = options.name; var legacy = options.legacy; var protect = options.protect; var methods = options.implement; var generics = options.generics; var initialize = options.initialize; var afterImplement = options.afterImplement || function(){}; var object = initialize || legacy; generics = generics !== false; object.constructor = Native; object.$family = {name: 'native'}; if (legacy && initialize) object.prototype = legacy.prototype; object.prototype.constructor = object; if (name){ var family = name.toLowerCase(); object.prototype.$family = {name: family}; Native.typize(object, family); } var add = function(obj, name, method, force){ if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method; if (generics) Native.genericize(obj, name, protect); afterImplement.call(obj, name, method); return obj; }; object.alias = function(a1, a2, a3){ if (typeof a1 == 'string'){ if ((a1 = this.prototype[a1])) return add(this, a2, a1, a3); } for (var a in a1) this.alias(a, a1[a], a2); return this; }; object.implement = function(a1, a2, a3){ if (typeof a1 == 'string') return add(this, a1, a2, a3); for (var p in a1) add(this, p, a1[p], a2); return this; }; if (methods) object.implement(methods); return object; }; Native.genericize = function(object, property, check){ if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){ var args = Array.prototype.slice.call(arguments); return object.prototype[property].apply(args.shift(), args); }; }; Native.implement = function(objects, properties){ for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties); }; Native.typize = function(object, family){ if (!object.type) object.type = function(item){ return ($type(item) === family); }; }; (function(){ var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String}; for (var n in natives) new Native({name: n, initialize: natives[n], protect: true}); var types = {'boolean': Boolean, 'native': Native, 'object': Object}; for (var t in types) Native.typize(types[t], t); var generics = { 'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"], 'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"] }; for (var g in generics){ for (var i = generics[g].length; i--;) Native.genericize(window[g], generics[g][i], true); }; })(); var Hash = new Native({ name: 'Hash', initialize: function(object){ if ($type(object) == 'hash') object = $unlink(object.getClean()); for (var key in object) this[key] = object[key]; return this; } }); Hash.implement({ forEach: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this); } }, getClean: function(){ var clean = {}; for (var key in this){ if (this.hasOwnProperty(key)) clean[key] = this[key]; } return clean; }, getLength: function(){ var length = 0; for (var key in this){ if (this.hasOwnProperty(key)) length++; } return length; } }); Hash.alias('forEach', 'each'); Array.implement({ forEach: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this); } }); Array.alias('forEach', 'each'); function $A(iterable){ if (iterable.item){ var array = []; for (var i = 0, l = iterable.length; i < l; i++) array[i] = iterable[i]; return array; } return Array.prototype.slice.call(iterable); }; function $arguments(i){ return function(){ return arguments[i]; }; }; function $chk(obj){ return !!(obj || obj === 0); }; function $clear(timer){ clearTimeout(timer); clearInterval(timer); return null; }; function $defined(obj){ return (obj != undefined); }; function $each(iterable, fn, bind){ var type = $type(iterable); ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind); }; function $empty(){}; function $extend(original, extended){ for (var key in (extended || {})) original[key] = extended[key]; return original; }; function $H(object){ return new Hash(object); }; function $lambda(value){ return (typeof value == 'function') ? value : function(){ return value; }; }; function $merge(){ var mix = {}; for (var i = 0, l = arguments.length; i < l; i++){ var object = arguments[i]; if ($type(object) != 'object') continue; for (var key in object){ var op = object[key], mp = mix[key]; mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $merge(mp, op) : $unlink(op); } } return mix; }; function $pick(){ for (var i = 0, l = arguments.length; i < l; i++){ if (arguments[i] != undefined) return arguments[i]; } return null; }; function $random(min, max){ return Math.floor(Math.random() * (max - min + 1) + min); }; function $splat(obj){ var type = $type(obj); return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : []; }; var $time = Date.now || function(){ return +new Date; }; function $try(){ for (var i = 0, l = arguments.length; i < l; i++){ try { return arguments[i](); } catch(e){} } return null; }; function $type(obj){ if (obj == undefined) return false; if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name; if (obj.nodeName){ switch (obj.nodeType){ case 1: return 'element'; case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; } } else if (typeof obj.length == 'number'){ if (obj.callee) return 'arguments'; else if (obj.item) return 'collection'; } return typeof obj; }; function $unlink(object){ var unlinked; switch ($type(object)){ case 'object': unlinked = {}; for (var p in object) unlinked[p] = $unlink(object[p]); break; case 'hash': unlinked = new Hash(object); break; case 'array': unlinked = []; for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]); break; default: return object; } return unlinked; }; /* Script: Browser.js The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash. License: MIT-style license. */ var Browser = $merge({ Engine: {name: 'unknown', version: 0}, Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()}, Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}, Plugins: {}, Engines: { presto: function(){ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }, trident: function(){ return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4); }, webkit: function(){ return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419); }, gecko: function(){ return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18); } } }, Browser || {}); Browser.Platform[Browser.Platform.name] = true; Browser.detect = function(){ for (var engine in this.Engines){ var version = this.Engines[engine](); if (version){ this.Engine = {name: engine, version: version}; this.Engine[engine] = this.Engine[engine + version] = true; break; } } return {name: engine, version: version}; }; Browser.detect(); Browser.Request = function(){ return $try(function(){ return new XMLHttpRequest(); }, function(){ return new ActiveXObject('MSXML2.XMLHTTP'); }); }; Browser.Features.xhr = !!(Browser.Request()); Browser.Plugins.Flash = (function(){ var version = ($try(function(){ return navigator.plugins['Shockwave Flash'].description; }, function(){ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); }) || '0 r0').match(/\d+/g); return {version: parseInt(version[0] || 0 + '.' + version[1] || 0), build: parseInt(version[2] || 0)}; })(); function $exec(text){ if (!text) return text; if (window.execScript){ window.execScript(text); } else { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text; document.head.appendChild(script); document.head.removeChild(script); } return text; }; Native.UID = 1; var $uid = (Browser.Engine.trident) ? function(item){ return (item.uid || (item.uid = [Native.UID++]))[0]; } : function(item){ return item.uid || (item.uid = Native.UID++); }; var Window = new Native({ name: 'Window', legacy: (Browser.Engine.trident) ? null: window.Window, initialize: function(win){ $uid(win); if (!win.Element){ win.Element = $empty; if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2 win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {}; } win.document.window = win; return $extend(win, Window.Prototype); }, afterImplement: function(property, value){ window[property] = Window.Prototype[property] = value; } }); Window.Prototype = {$family: {name: 'window'}}; new Window(window); var Document = new Native({ name: 'Document', legacy: (Browser.Engine.trident) ? null: window.Document, initialize: function(doc){ $uid(doc); doc.head = doc.getElementsByTagName('head')[0]; doc.html = doc.getElementsByTagName('html')[0]; if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){ doc.execCommand("BackgroundImageCache", false, true); }); if (Browser.Engine.trident) doc.window.attachEvent('onunload', function() { doc.window.detachEvent('onunload', arguments.callee); doc.head = doc.html = doc.window = null; }); return $extend(doc, Document.Prototype); }, afterImplement: function(property, value){ document[property] = Document.Prototype[property] = value; } }); Document.Prototype = {$family: {name: 'document'}}; new Document(document); /* Script: Array.js Contains Array Prototypes like each, contains, and erase. License: MIT-style license. */ Array.implement({ every: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (!fn.call(bind, this[i], i, this)) return false; } return true; }, filter: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) results.push(this[i]); } return results; }, clean: function() { return this.filter($defined); }, indexOf: function(item, from){ var len = this.length; for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ if (this[i] === item) return i; } return -1; }, map: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this); return results; }, some: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) return true; } return false; }, associate: function(keys){ var obj = {}, length = Math.min(this.length, keys.length); for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; return obj; }, link: function(object){ var result = {}; for (var i = 0, l = this.length; i < l; i++){ for (var key in object){ if (object[key](this[i])){ result[key] = this[i]; delete object[key]; break; } } } return result; }, contains: function(item, from){ return this.indexOf(item, from) != -1; }, extend: function(array){ for (var i = 0, j = array.length; i < j; i++) this.push(array[i]); return this; }, getLast: function(){ return (this.length) ? this[this.length - 1] : null; }, getRandom: function(){ return (this.length) ? this[$random(0, this.length - 1)] : null; }, include: function(item){ if (!this.contains(item)) this.push(item); return this; }, combine: function(array){ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); return this; }, erase: function(item){ for (var i = this.length; i--; i){ if (this[i] === item) this.splice(i, 1); } return this; }, empty: function(){ this.length = 0; return this; }, flatten: function(){ var array = []; for (var i = 0, l = this.length; i < l; i++){ var type = $type(this[i]); if (!type) continue; array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]); } return array; }, hexToRgb: function(array){ if (this.length != 3) return null; var rgb = this.map(function(value){ if (value.length == 1) value += value; return value.toInt(16); }); return (array) ? rgb : 'rgb(' + rgb + ')'; }, rgbToHex: function(array){ if (this.length < 3) return null; if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; var hex = []; for (var i = 0; i < 3; i++){ var bit = (this[i] - 0).toString(16); hex.push((bit.length == 1) ? '0' + bit : bit); } return (array) ? hex : '#' + hex.join(''); } }); /* Script: Function.js Contains Function Prototypes like create, bind, pass, and delay. License: MIT-style license. */ Function.implement({ extend: function(properties){ for (var property in properties) this[property] = properties[property]; return this; }, create: function(options){ var self = this; options = options || {}; return function(event){ var args = options.arguments; args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0); if (options.event) args = [event || window.event].extend(args); var returns = function(){ return self.apply(options.bind || null, args); }; if (options.delay) return setTimeout(returns, options.delay); if (options.periodical) return setInterval(returns, options.periodical); if (options.attempt) return $try(returns); return returns(); }; }, run: function(args, bind){ return this.apply(bind, $splat(args)); }, pass: function(args, bind){ return this.create({bind: bind, arguments: args}); }, bind: function(bind, args){ return this.create({bind: bind, arguments: args}); }, bindWithEvent: function(bind, args){ return this.create({bind: bind, arguments: args, event: true}); }, attempt: function(args, bind){ return this.create({bind: bind, arguments: args, attempt: true})(); }, delay: function(delay, bind, args){ return this.create({bind: bind, arguments: args, delay: delay})(); }, periodical: function(periodical, bind, args){ return this.create({bind: bind, arguments: args, periodical: periodical})(); } }); /* Script: Number.js Contains Number Prototypes like limit, round, times, and ceil. License: MIT-style license. */ Number.implement({ limit: function(min, max){ return Math.min(max, Math.max(min, this)); }, round: function(precision){ precision = Math.pow(10, precision || 0); return Math.round(this * precision) / precision; }, times: function(fn, bind){ for (var i = 0; i < this; i++) fn.call(bind, i, this); }, toFloat: function(){ return parseFloat(this); }, toInt: function(base){ return parseInt(this, base || 10); } }); Number.alias('times', 'each'); (function(math){ var methods = {}; math.each(function(name){ if (!Number[name]) methods[name] = function(){ return Math[name].apply(null, [this].concat($A(arguments))); }; }); Number.implement(methods); })(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); /* Script: String.js Contains String Prototypes like camelCase, capitalize, test, and toInt. License: MIT-style license. */ String.implement({ test: function(regex, params){ return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this); }, contains: function(string, separator){ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1; }, trim: function(){ return this.replace(/^\s+|\s+$/g, ''); }, clean: function(){ return this.replace(/\s+/g, ' ').trim(); }, camelCase: function(){ return this.replace(/-\D/g, function(match){ return match.charAt(1).toUpperCase(); }); }, hyphenate: function(){ return this.replace(/[A-Z]/g, function(match){ return ('-' + match.charAt(0).toLowerCase()); }); }, capitalize: function(){ return this.replace(/\b[a-z]/g, function(match){ return match.toUpperCase(); }); }, escapeRegExp: function(){ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); }, toInt: function(base){ return parseInt(this, base || 10); }, toFloat: function(){ return parseFloat(this); }, hexToRgb: function(array){ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); return (hex) ? hex.slice(1).hexToRgb(array) : null; }, rgbToHex: function(array){ var rgb = this.match(/\d{1,3}/g); return (rgb) ? rgb.rgbToHex(array) : null; }, stripScripts: function(option){ var scripts = ''; var text = this.replace(/]*>([\s\S]*?)<\/script>/gi, function(){ scripts += arguments[1] + '\n'; return ''; }); if (option === true) $exec(scripts); else if ($type(option) == 'function') option(scripts, text); return text; }, substitute: function(object, regexp){ return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ if (match.charAt(0) == '\\') return match.slice(1); return (object[name] != undefined) ? object[name] : ''; }); } }); /* Script: Hash.js Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. License: MIT-style license. */ Hash.implement({ has: Object.prototype.hasOwnProperty, keyOf: function(value){ for (var key in this){ if (this.hasOwnProperty(key) && this[key] === value) return key; } return null; }, hasValue: function(value){ return (Hash.keyOf(this, value) !== null); }, extend: function(properties){ Hash.each(properties, function(value, key){ Hash.set(this, key, value); }, this); return this; }, combine: function(properties){ Hash.each(properties, function(value, key){ Hash.include(this, key, value); }, this); return this; }, erase: function(key){ if (this.hasOwnProperty(key)) delete this[key]; return this; }, get: function(key){ return (this.hasOwnProperty(key)) ? this[key] : null; }, set: function(key, value){ if (!this[key] || this.hasOwnProperty(key)) this[key] = value; return this; }, empty: function(){ Hash.each(this, function(value, key){ delete this[key]; }, this); return this; }, include: function(key, value){ var k = this[key]; if (k == undefined) this[key] = value; return this; }, map: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ results.set(key, fn.call(bind, value, key, this)); }, this); return results; }, filter: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ if (fn.call(bind, value, key, this)) results.set(key, value); }, this); return results; }, every: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false; } return true; }, some: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true; } return false; }, getKeys: function(){ var keys = []; Hash.each(this, function(value, key){ keys.push(key); }); return keys; }, getValues: function(){ var values = []; Hash.each(this, function(value){ values.push(value); }); return values; }, toQueryString: function(base){ var queryString = []; Hash.each(this, function(value, key){ if (base) key = base + '[' + key + ']'; var result; switch ($type(value)){ case 'object': result = Hash.toQueryString(value, key); break; case 'array': var qs = {}; value.each(function(val, i){ qs[i] = val; }); result = Hash.toQueryString(qs, key); break; default: result = key + '=' + encodeURIComponent(value); } if (value != undefined) queryString.push(result); }); return queryString.join('&'); } }); Hash.alias({keyOf: 'indexOf', hasValue: 'contains'}); /* Script: Event.js Contains the Event Native, to make the event object completely crossbrowser. License: MIT-style license. */ var Event = new Native({ name: 'Event', initialize: function(event, win){ win = win || window; var doc = win.document; event = event || win.event; if (event.$extended) return event; this.$extended = true; var type = event.type; var target = event.target || event.srcElement; while (target && target.nodeType == 3) target = target.parentNode; if (type.test(/key/)){ var code = event.which || event.keyCode; var key = Event.Keys.keyOf(code); if (type == 'keydown'){ var fKey = code - 111; if (fKey > 0 && fKey < 13) key = 'f' + fKey; } key = key || String.fromCharCode(code).toLowerCase(); } else if (type.match(/(click|mouse|menu)/i)){ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; var page = { x: event.pageX || event.clientX + doc.scrollLeft, y: event.pageY || event.clientY + doc.scrollTop }; var client = { x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX, y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY }; if (type.match(/DOMMouseScroll|mousewheel/)){ var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; } var rightClick = (event.which == 3) || (event.button == 2); var related = null; if (type.match(/over|out/)){ switch (type){ case 'mouseover': related = event.relatedTarget || event.fromElement; break; case 'mouseout': related = event.relatedTarget || event.toElement; } if (!(function(){ while (related && related.nodeType == 3) related = related.parentNode; return true; }).create({attempt: Browser.Engine.gecko})()) related = false; } } return $extend(this, { event: event, type: type, page: page, client: client, rightClick: rightClick, wheel: wheel, relatedTarget: related, target: target, code: code, key: key, shift: event.shiftKey, control: event.ctrlKey, alt: event.altKey, meta: event.metaKey }); } }); Event.Keys = new Hash({ 'enter': 13, 'up': 38, 'down': 40, 'left': 37, 'right': 39, 'esc': 27, 'space': 32, 'backspace': 8, 'tab': 9, 'delete': 46 }); Event.implement({ stop: function(){ return this.stopPropagation().preventDefault(); }, stopPropagation: function(){ if (this.event.stopPropagation) this.event.stopPropagation(); else this.event.cancelBubble = true; return this; }, preventDefault: function(){ if (this.event.preventDefault) this.event.preventDefault(); else this.event.returnValue = false; return this; } }); /* Script: Class.js Contains the Class Function for easily creating, extending, and implementing reusable Classes. License: MIT-style license. */ var Class = new Native({ name: 'Class', initialize: function(properties){ properties = properties || {}; var klass = function(){ for (var key in this){ if ($type(this[key]) != 'function') this[key] = $unlink(this[key]); } this.constructor = klass; if (Class.prototyping) return this; var instance = (this.initialize) ? this.initialize.apply(this, arguments) : this; if (this.options && this.options.initialize) this.options.initialize.call(this); return instance; }; for (var mutator in Class.Mutators){ if (!properties[mutator]) continue; properties = Class.Mutators[mutator](properties, properties[mutator]); delete properties[mutator]; } $extend(klass, this); klass.constructor = Class; klass.prototype = properties; return klass; } }); Class.Mutators = { Extends: function(self, klass){ Class.prototyping = klass.prototype; var subclass = new klass; delete subclass.parent; subclass = Class.inherit(subclass, self); delete Class.prototyping; return subclass; }, Implements: function(self, klasses){ $splat(klasses).each(function(klass){ Class.prototying = klass; $extend(self, ($type(klass) == 'class') ? new klass : klass); delete Class.prototyping; }); return self; } }; Class.extend({ inherit: function(object, properties){ var caller = arguments.callee.caller; for (var key in properties){ var override = properties[key]; var previous = object[key]; var type = $type(override); if (previous && type == 'function'){ if (override != previous){ if (caller){ override.__parent = previous; object[key] = override; } else { Class.override(object, key, override); } } } else if(type == 'object'){ object[key] = $merge(previous, override); } else { object[key] = override; } } if (caller) object.parent = function(){ return arguments.callee.caller.__parent.apply(this, arguments); }; return object; }, override: function(object, name, method){ var parent = Class.prototyping; if (parent && object[name] != parent[name]) parent = null; var override = function(){ var previous = this.parent; this.parent = parent ? parent[name] : object[name]; var value = method.apply(this, arguments); this.parent = previous; return value; }; object[name] = override; } }); Class.implement({ implement: function(){ var proto = this.prototype; $each(arguments, function(properties){ Class.inherit(proto, properties); }); return this; } }); /* Script: Class.Extras.js Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. License: MIT-style license. */ var Chain = new Class({ $chain: [], chain: function(){ this.$chain.extend(Array.flatten(arguments)); return this; }, callChain: function(){ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; }, clearChain: function(){ this.$chain.empty(); return this; } }); var Events = new Class({ $events: {}, addEvent: function(type, fn, internal){ type = Events.removeOn(type); if (fn != $empty){ this.$events[type] = this.$events[type] || []; this.$events[type].include(fn); if (internal) fn.internal = true; } return this; }, addEvents: function(events){ for (var type in events) this.addEvent(type, events[type]); return this; }, fireEvent: function(type, args, delay){ type = Events.removeOn(type); if (!this.$events || !this.$events[type]) return this; this.$events[type].each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, removeEvent: function(type, fn){ type = Events.removeOn(type); if (!this.$events[type]) return this; if (!fn.internal) this.$events[type].erase(fn); return this; }, removeEvents: function(events){ if ($type(events) == 'object'){ for (var type in events) this.removeEvent(type, events[type]); return this; } if (events) events = Events.removeOn(events); for (var type in this.$events){ if (events && events != type) continue; var fns = this.$events[type]; for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]); } return this; } }); Events.removeOn = function(string){ return string.replace(/^on([A-Z])/, function(full, first) { return first.toLowerCase(); }); }; var Options = new Class({ setOptions: function(){ this.options = $merge.run([this.options].extend(arguments)); if (!this.addEvent) return this; for (var option in this.options){ if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; this.addEvent(option, this.options[option]); delete this.options[option]; } return this; } }); /* Script: Element.js One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. License: MIT-style license. */ var Element = new Native({ name: 'Element', legacy: window.Element, initialize: function(tag, props){ var konstructor = Element.Constructors.get(tag); if (konstructor) return konstructor(props); if (typeof tag == 'string') return document.newElement(tag, props); return $(tag).set(props); }, afterImplement: function(key, value){ Element.Prototype[key] = value; if (Array[key]) return; Elements.implement(key, function(){ var items = [], elements = true; for (var i = 0, j = this.length; i < j; i++){ var returns = this[i][key].apply(this[i], arguments); items.push(returns); if (elements) elements = ($type(returns) == 'element'); } return (elements) ? new Elements(items) : items; }); } }); Element.Prototype = {$family: {name: 'element'}}; Element.Constructors = new Hash; var IFrame = new Native({ name: 'IFrame', generics: false, initialize: function(){ var params = Array.link(arguments, {properties: Object.type, iframe: $defined}); var props = params.properties || {}; var iframe = $(params.iframe) || false; var onload = props.onload || $empty; delete props.onload; props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time()); iframe = new Element(iframe || 'iframe', props); var onFrameLoad = function(){ var host = $try(function(){ return iframe.contentWindow.location.host; }); if (host && host == window.location.host){ var win = new Window(iframe.contentWindow); new Document(iframe.contentWindow.document); $extend(win.Element.prototype, Element.Prototype); } onload.call(iframe.contentWindow, iframe.contentWindow.document); }; (window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad); return iframe; } }); var Elements = new Native({ initialize: function(elements, options){ options = $extend({ddup: true, cash: true}, options); elements = elements || []; if (options.ddup || options.cash){ var uniques = {}, returned = []; for (var i = 0, l = elements.length; i < l; i++){ var el = $.element(elements[i], !options.cash); if (options.ddup){ if (uniques[el.uid]) continue; uniques[el.uid] = true; } returned.push(el); } elements = returned; } return (options.cash) ? $extend(elements, this) : elements; } }); Elements.implement({ filter: function(filter, bind){ if (!filter) return this; return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){ return item.match(filter); } : filter, bind)); } }); Document.implement({ newElement: function(tag, props){ if (Browser.Engine.trident && props){ ['name', 'type', 'checked'].each(function(attribute){ if (!props[attribute]) return; tag += ' ' + attribute + '="' + props[attribute] + '"'; if (attribute != 'checked') delete props[attribute]; }); tag = '<' + tag + '>'; } return $.element(this.createElement(tag)).set(props); }, newTextNode: function(text){ return this.createTextNode(text); }, getDocument: function(){ return this; }, getWindow: function(){ return this.window; } }); Window.implement({ $: function(el, nocash){ if (el && el.$family && el.uid) return el; var type = $type(el); return ($[type]) ? $[type](el, nocash, this.document) : null; }, $$: function(selector){ if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector); var elements = []; var args = Array.flatten(arguments); for (var i = 0, l = args.length; i < l; i++){ var item = args[i]; switch ($type(item)){ case 'element': elements.push(item); break; case 'string': elements.extend(this.document.getElements(item, true)); } } return new Elements(elements); }, getDocument: function(){ return this.document; }, getWindow: function(){ return this; } }); $.string = function(id, nocash, doc){ id = doc.getElementById(id); return (id) ? $.element(id, nocash) : null; }; $.element = function(el, nocash){ $uid(el); if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){ var proto = Element.Prototype; for (var p in proto) el[p] = proto[p]; }; return el; }; $.object = function(obj, nocash, doc){ if (obj.toElement) return $.element(obj.toElement(doc), nocash); return null; }; $.textnode = $.whitespace = $.window = $.document = $arguments(0); Native.implement([Element, Document], { getElement: function(selector, nocash){ return $(this.getElements(selector, true)[0] || null, nocash); }, getElements: function(tags, nocash){ tags = tags.split(','); var elements = []; var ddup = (tags.length > 1); tags.each(function(tag){ var partial = this.getElementsByTagName(tag.trim()); (ddup) ? elements.extend(partial) : elements = partial; }, this); return new Elements(elements, {ddup: ddup, cash: !nocash}); } }); (function(){ var collected = {}, storage = {}; var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'}; var get = function(uid){ return (storage[uid] || (storage[uid] = {})); }; var clean = function(item, retain){ if (!item) return; var uid = item.uid; if (Browser.Engine.trident){ if (item.clearAttributes){ var clone = retain && item.cloneNode(false); item.clearAttributes(); if (clone) item.mergeAttributes(clone); } else if (item.removeEvents){ item.removeEvents(); } if ((/object/i).test(item.tagName)){ for (var p in item){ if (typeof item[p] == 'function') item[p] = $empty; } Element.dispose(item); } } if (!uid) return; collected[uid] = storage[uid] = null; }; var purge = function(){ Hash.each(collected, clean); if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean); if (window.CollectGarbage) CollectGarbage(); collected = storage = null; }; var walk = function(element, walk, start, match, all, nocash){ var el = element[start || walk]; var elements = []; while (el){ if (el.nodeType == 1 && (!match || Element.match(el, match))){ if (!all) return $(el, nocash); elements.push(el); } el = el[walk]; } return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null; }; var attributes = { 'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent' }; var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer']; var camels = ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']; Hash.extend(attributes, bools.associate(bools)); Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase))); var inserters = { before: function(context, element){ if (element.parentNode) element.parentNode.insertBefore(context, element); }, after: function(context, element){ if (!element.parentNode) return; var next = element.nextSibling; (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context); }, bottom: function(context, element){ element.appendChild(context); }, top: function(context, element){ var first = element.firstChild; (first) ? element.insertBefore(context, first) : element.appendChild(context); } }; inserters.inside = inserters.bottom; Hash.each(inserters, function(inserter, where){ where = where.capitalize(); Element.implement('inject' + where, function(el){ inserter(this, $(el, true)); return this; }); Element.implement('grab' + where, function(el){ inserter($(el, true), this); return this; }); }); Element.implement({ set: function(prop, value){ switch ($type(prop)){ case 'object': for (var p in prop) this.set(p, prop[p]); break; case 'string': var property = Element.Properties.get(prop); (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value); } return this; }, get: function(prop){ var property = Element.Properties.get(prop); return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop); }, erase: function(prop){ var property = Element.Properties.get(prop); (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); return this; }, setProperty: function(attribute, value){ var key = attributes[attribute]; if (value == undefined) return this.removeProperty(attribute); if (key && bools[attribute]) value = !!value; (key) ? this[key] = value : this.setAttribute(attribute, '' + value); return this; }, setProperties: function(attributes){ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); return this; }, getProperty: function(attribute){ var key = attributes[attribute]; var value = (key) ? this[key] : this.getAttribute(attribute, 2); return (bools[attribute]) ? !!value : (key) ? value : value || null; }, getProperties: function(){ var args = $A(arguments); return args.map(this.getProperty, this).associate(args); }, removeProperty: function(attribute){ var key = attributes[attribute]; (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute); return this; }, removeProperties: function(){ Array.each(arguments, this.removeProperty, this); return this; }, hasClass: function(className){ return this.className.contains(className, ' '); }, addClass: function(className){ if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); return this; }, removeClass: function(className){ this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1'); return this; }, toggleClass: function(className){ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className); }, adopt: function(){ Array.flatten(arguments).each(function(element){ element = $(element, true); if (element) this.appendChild(element); }, this); return this; }, appendText: function(text, where){ return this.grab(this.getDocument().newTextNode(text), where); }, grab: function(el, where){ inserters[where || 'bottom']($(el, true), this); return this; }, inject: function(el, where){ inserters[where || 'bottom'](this, $(el, true)); return this; }, replaces: function(el){ el = $(el, true); el.parentNode.replaceChild(this, el); return this; }, wraps: function(el, where){ el = $(el, true); return this.replaces(el).grab(el, where); }, getPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, false, nocash); }, getAllPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, true, nocash); }, getNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, false, nocash); }, getAllNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, true, nocash); }, getFirst: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, false, nocash); }, getLast: function(match, nocash){ return walk(this, 'previousSibling', 'lastChild', match, false, nocash); }, getParent: function(match, nocash){ return walk(this, 'parentNode', null, match, false, nocash); }, getParents: function(match, nocash){ return walk(this, 'parentNode', null, match, true, nocash); }, getChildren: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, true, nocash); }, getWindow: function(){ return this.ownerDocument.window; }, getDocument: function(){ return this.ownerDocument; }, getElementById: function(id, nocash){ var el = this.ownerDocument.getElementById(id); if (!el) return null; for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ if (!parent) return null; } return $.element(el, nocash); }, getSelected: function(){ return new Elements($A(this.options).filter(function(option){ return option.selected; })); }, getComputedStyle: function(property){ if (this.currentStyle) return this.currentStyle[property.camelCase()]; var computed = this.getDocument().defaultView.getComputedStyle(this, null); return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null; }, toQueryString: function(){ var queryString = []; this.getElements('input, select, textarea', true).each(function(el){ if (!el.name || el.disabled) return; var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){ return opt.value; }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value; $splat(value).each(function(val){ if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val)); }); }); return queryString.join('&'); }, clone: function(contents, keepid){ contents = contents !== false; var clone = this.cloneNode(contents); var clean = function(node, element){ if (!keepid) node.removeAttribute('id'); if (Browser.Engine.trident){ node.clearAttributes(); node.mergeAttributes(element); node.removeAttribute('uid'); if (node.options){ var no = node.options, eo = element.options; for (var j = no.length; j--;) no[j].selected = eo[j].selected; } } var prop = props[element.tagName.toLowerCase()]; if (prop && element[prop]) node[prop] = element[prop]; }; if (contents){ var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*'); for (var i = ce.length; i--;) clean(ce[i], te[i]); } clean(clone, this); return $(clone); }, destroy: function(){ Element.empty(this); Element.dispose(this); clean(this, true); return null; }, empty: function(){ $A(this.childNodes).each(function(node){ Element.destroy(node); }); return this; }, dispose: function(){ return (this.parentNode) ? this.parentNode.removeChild(this) : this; }, hasChild: function(el){ el = $(el, true); if (!el) return false; if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el); return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16); }, match: function(tag){ return (!tag || (tag == this) || (Element.get(this, 'tag') == tag)); } }); Native.implement([Element, Window, Document], { addListener: function(type, fn){ if (type == 'unload'){ var old = fn, self = this; fn = function(){ self.removeListener('unload', fn); old(); }; } else { collected[this.uid] = this; } if (this.addEventListener) this.addEventListener(type, fn, false); else this.attachEvent('on' + type, fn); return this; }, removeListener: function(type, fn){ if (this.removeEventListener) this.removeEventListener(type, fn, false); else this.detachEvent('on' + type, fn); return this; }, retrieve: function(property, dflt){ var storage = get(this.uid), prop = storage[property]; if (dflt != undefined && prop == undefined) prop = storage[property] = dflt; return $pick(prop); }, store: function(property, value){ var storage = get(this.uid); storage[property] = value; return this; }, eliminate: function(property){ var storage = get(this.uid); delete storage[property]; return this; } }); window.addListener('unload', purge); })(); Element.Properties = new Hash; Element.Properties.style = { set: function(style){ this.style.cssText = style; }, get: function(){ return this.style.cssText; }, erase: function(){ this.style.cssText = ''; } }; Element.Properties.tag = { get: function(){ return this.tagName.toLowerCase(); } }; Element.Properties.html = (function(){ var wrapper = document.createElement('div'); var translations = { table: [1, '', '
'], select: [1, ''], tbody: [2, '', '
'], tr: [3, '', '
'] }; translations.thead = translations.tfoot = translations.tbody; var html = { set: function(){ var html = Array.flatten(arguments).join(''); var wrap = Browser.Engine.trident && translations[this.get('tag')]; if (wrap){ var first = wrapper; first.innerHTML = wrap[1] + html + wrap[2]; for (var i = wrap[0]; i--;) first = first.firstChild; this.empty().adopt(first.childNodes); } else { this.innerHTML = html; } } }; html.erase = html.set; return html; })(); if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = { get: function(){ if (this.innerText) return this.innerText; var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body); var text = temp.innerText; temp.destroy(); return text; } }; /* Script: Element.Event.js Contains Element methods for dealing with events, and custom Events. License: MIT-style license. */ Element.Properties.events = {set: function(events){ this.addEvents(events); }}; Native.implement([Element, Window, Document], { addEvent: function(type, fn){ var events = this.retrieve('events', {}); events[type] = events[type] || {'keys': [], 'values': []}; if (events[type].keys.contains(fn)) return this; events[type].keys.push(fn); var realType = type, custom = Element.Events.get(type), condition = fn, self = this; if (custom){ if (custom.onAdd) custom.onAdd.call(this, fn); if (custom.condition){ condition = function(event){ if (custom.condition.call(this, event)) return fn.call(this, event); return true; }; } realType = custom.base || realType; } var defn = function(){ return fn.call(self); }; var nativeEvent = Element.NativeEvents[realType]; if (nativeEvent){ if (nativeEvent == 2){ defn = function(event){ event = new Event(event, self.getWindow()); if (condition.call(self, event) === false) event.stop(); }; } this.addListener(realType, defn); } events[type].values.push(defn); return this; }, removeEvent: function(type, fn){ var events = this.retrieve('events'); if (!events || !events[type]) return this; var pos = events[type].keys.indexOf(fn); if (pos == -1) return this; events[type].keys.splice(pos, 1); var value = events[type].values.splice(pos, 1)[0]; var custom = Element.Events.get(type); if (custom){ if (custom.onRemove) custom.onRemove.call(this, fn); type = custom.base || type; } return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this; }, addEvents: function(events){ for (var event in events) this.addEvent(event, events[event]); return this; }, removeEvents: function(events){ if ($type(events) == 'object'){ for (var type in events) this.removeEvent(type, events[type]); return this; } var attached = this.retrieve('events'); if (!attached) return this; if (!events){ for (var type in attached) this.removeEvents(type); this.eliminate('events'); } else if (attached[events]){ while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]); attached[events] = null; } return this; }, fireEvent: function(type, args, delay){ var events = this.retrieve('events'); if (!events || !events[type]) return this; events[type].keys.each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, cloneEvents: function(from, type){ from = $(from); var fevents = from.retrieve('events'); if (!fevents) return this; if (!type){ for (var evType in fevents) this.cloneEvents(from, evType); } else if (fevents[type]){ fevents[type].keys.each(function(fn){ this.addEvent(type, fn); }, this); } return this; } }); Element.NativeEvents = { click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons mousewheel: 2, DOMMouseScroll: 2, //mouse wheel mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement keydown: 2, keypress: 2, keyup: 2, //keyboard focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window error: 1, abort: 1, scroll: 1 //misc }; (function(){ var $check = function(event){ var related = event.relatedTarget; if (related == undefined) return true; if (related === false) return false; return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related)); }; Element.Events = new Hash({ mouseenter: { base: 'mouseover', condition: $check }, mouseleave: { base: 'mouseout', condition: $check }, mousewheel: { base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel' } }); })(); /* Script: Element.Style.js Contains methods for interacting with the styles of Elements in a fashionable way. License: MIT-style license. */ Element.Properties.styles = {set: function(styles){ this.setStyles(styles); }}; Element.Properties.opacity = { set: function(opacity, novisibility){ if (!novisibility){ if (opacity == 0){ if (this.style.visibility != 'hidden') this.style.visibility = 'hidden'; } else { if (this.style.visibility != 'visible') this.style.visibility = 'visible'; } } if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1; if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')'; this.style.opacity = opacity; this.store('opacity', opacity); }, get: function(){ return this.retrieve('opacity', 1); } }; Element.implement({ setOpacity: function(value){ return this.set('opacity', value, true); }, getOpacity: function(){ return this.get('opacity'); }, setStyle: function(property, value){ switch (property){ case 'opacity': return this.set('opacity', parseFloat(value)); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); if ($type(value) != 'string'){ var map = (Element.Styles.get(property) || '@').split(' '); value = $splat(value).map(function(val, i){ if (!map[i]) return ''; return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; }).join(' '); } else if (value == String(Number(value))){ value = Math.round(value); } this.style[property] = value; return this; }, getStyle: function(property){ switch (property){ case 'opacity': return this.get('opacity'); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); var result = this.style[property]; if (!$chk(result)){ result = []; for (var style in Element.ShortStyles){ if (property != style) continue; for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s)); return result.join(' '); } result = this.getComputedStyle(property); } if (result){ result = String(result); var color = result.match(/rgba?\([\d\s,]+\)/); if (color) result = result.replace(color[0], color[0].rgbToHex()); } if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result)))){ if (property.test(/^(height|width)$/)){ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; values.each(function(value){ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); }, this); return this['offset' + property.capitalize()] - size + 'px'; } if ((Browser.Engine.presto) && String(result).test('px')) return result; if (property.test(/(border(.+)Width|margin|padding)/)) return '0px'; } return result; }, setStyles: function(styles){ for (var style in styles) this.setStyle(style, styles[style]); return this; }, getStyles: function(){ var result = {}; Array.each(arguments, function(key){ result[key] = this.getStyle(key); }, this); return result; } }); Element.Styles = new Hash({ left: '@px', top: '@px', bottom: '@px', right: '@px', width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' }); Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ var Short = Element.ShortStyles; var All = Element.Styles; ['margin', 'padding'].each(function(style){ var sd = style + direction; Short[style][sd] = All[sd] = '@px'; }); var bd = 'border' + direction; Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; Short[bd] = {}; Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; }); /* Script: Element.Dimensions.js Contains methods to work with size, scroll, or positioning of Elements and the window object. License: MIT-style license. Credits: - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). */ (function(){ Element.implement({ scrollTo: function(x, y){ if (isBody(this)){ this.getWindow().scrollTo(x, y); } else { this.scrollLeft = x; this.scrollTop = y; } return this; }, getSize: function(){ if (isBody(this)) return this.getWindow().getSize(); return {x: this.offsetWidth, y: this.offsetHeight}; }, getScrollSize: function(){ if (isBody(this)) return this.getWindow().getScrollSize(); return {x: this.scrollWidth, y: this.scrollHeight}; }, getScroll: function(){ if (isBody(this)) return this.getWindow().getScroll(); return {x: this.scrollLeft, y: this.scrollTop}; }, getScrolls: function(){ var element = this, position = {x: 0, y: 0}; while (element && !isBody(element)){ position.x += element.scrollLeft; position.y += element.scrollTop; element = element.parentNode; } return position; }, getOffsetParent: function(){ var element = this; if (isBody(element)) return null; if (!Browser.Engine.trident) return element.offsetParent; while ((element = element.parentNode) && !isBody(element)){ if (styleString(element, 'position') != 'static') return element; } return null; }, getOffsets: function(){ if (Browser.Engine.trident){ var bound = this.getBoundingClientRect(), html = this.getDocument().documentElement; return { x: bound.left + html.scrollLeft - html.clientLeft, y: bound.top + html.scrollTop - html.clientTop }; } var element = this, position = {x: 0, y: 0}; if (isBody(this)) return position; while (element && !isBody(element)){ position.x += element.offsetLeft; position.y += element.offsetTop; if (Browser.Engine.gecko){ if (!borderBox(element)){ position.x += leftBorder(element); position.y += topBorder(element); } var parent = element.parentNode; if (parent && styleString(parent, 'overflow') != 'visible'){ position.x += leftBorder(parent); position.y += topBorder(parent); } } else if (element != this && Browser.Engine.webkit){ position.x += leftBorder(element); position.y += topBorder(element); } element = element.offsetParent; } if (Browser.Engine.gecko && !borderBox(this)){ position.x -= leftBorder(this); position.y -= topBorder(this); } return position; }, getPosition: function(relative){ if (isBody(this)) return {x: 0, y: 0}; var offset = this.getOffsets(), scroll = this.getScrolls(); var position = {x: offset.x - scroll.x, y: offset.y - scroll.y}; var relativePosition = (relative && (relative = $(relative))) ? relative.getPosition() : {x: 0, y: 0}; return {x: position.x - relativePosition.x, y: position.y - relativePosition.y}; }, getCoordinates: function(element){ if (isBody(this)) return this.getWindow().getCoordinates(); var position = this.getPosition(element), size = this.getSize(); var obj = {left: position.x, top: position.y, width: size.x, height: size.y}; obj.right = obj.left + obj.width; obj.bottom = obj.top + obj.height; return obj; }, computePosition: function(obj){ return {left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top')}; }, position: function(obj){ return this.setStyles(this.computePosition(obj)); } }); Native.implement([Document, Window], { getSize: function(){ var win = this.getWindow(); if (Browser.Engine.presto || Browser.Engine.webkit) return {x: win.innerWidth, y: win.innerHeight}; var doc = getCompatElement(this); return {x: doc.clientWidth, y: doc.clientHeight}; }, getScroll: function(){ var win = this.getWindow(); var doc = getCompatElement(this); return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; }, getScrollSize: function(){ var doc = getCompatElement(this); var min = this.getSize(); return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)}; }, getPosition: function(){ return {x: 0, y: 0}; }, getCoordinates: function(){ var size = this.getSize(); return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; } }); // private methods var styleString = Element.getComputedStyle; function styleNumber(element, style){ return styleString(element, style).toInt() || 0; }; function borderBox(element){ return styleString(element, '-moz-box-sizing') == 'border-box'; }; function topBorder(element){ return styleNumber(element, 'border-top-width'); }; function leftBorder(element){ return styleNumber(element, 'border-left-width'); }; function isBody(element){ return (/^(?:body|html)$/i).test(element.tagName); }; function getCompatElement(element){ var doc = element.getDocument(); return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; }; })(); //aliases Native.implement([Window, Document, Element], { getHeight: function(){ return this.getSize().y; }, getWidth: function(){ return this.getSize().x; }, getScrollTop: function(){ return this.getScroll().y; }, getScrollLeft: function(){ return this.getScroll().x; }, getScrollHeight: function(){ return this.getScrollSize().y; }, getScrollWidth: function(){ return this.getScrollSize().x; }, getTop: function(){ return this.getPosition().y; }, getLeft: function(){ return this.getPosition().x; } }); /* Script: Selectors.js Adds advanced CSS Querying capabilities for targeting elements. Also includes pseudoselectors support. License: MIT-style license. */ Native.implement([Document, Element], { getElements: function(expression, nocash){ expression = expression.split(','); var items, local = {}; for (var i = 0, l = expression.length; i < l; i++){ var selector = expression[i], elements = Selectors.Utils.search(this, selector, local); if (i != 0 && elements.item) elements = $A(elements); items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements); } return new Elements(items, {ddup: (expression.length > 1), cash: !nocash}); } }); Element.implement({ match: function(selector){ if (!selector || (selector == this)) return true; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false; var parsed = Selectors.Utils.parseSelector(selector); return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true; } }); var Selectors = {Cache: {nth: {}, parsed: {}}}; Selectors.RegExps = { id: (/#([\w-]+)/), tag: (/^(\w+|\*)/), quick: (/^(\w+|\*)$/), splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g), combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g) }; Selectors.Utils = { chk: function(item, uniques){ if (!uniques) return true; var uid = $uid(item); if (!uniques[uid]) return uniques[uid] = true; return false; }, parseNthArgument: function(argument){ if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument]; var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/); if (!parsed) return false; var inta = parseInt(parsed[1]); var a = (inta || inta === 0) ? inta : 1; var special = parsed[2] || false; var b = parseInt(parsed[3]) || 0; if (a != 0){ b--; while (b < 1) b += a; while (b >= a) b -= a; } else { a = b; special = 'index'; } switch (special){ case 'n': parsed = {a: a, b: b, special: 'n'}; break; case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break; case 'even': parsed = {a: 2, b: 1, special: 'n'}; break; case 'first': parsed = {a: 0, special: 'index'}; break; case 'last': parsed = {special: 'last-child'}; break; case 'only': parsed = {special: 'only-child'}; break; default: parsed = {a: (a - 1), special: 'index'}; } return Selectors.Cache.nth[argument] = parsed; }, parseSelector: function(selector){ if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector]; var m, parsed = {classes: [], pseudos: [], attributes: []}; while ((m = Selectors.RegExps.combined.exec(selector))){ var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7]; if (cn){ parsed.classes.push(cn); } else if (pn){ var parser = Selectors.Pseudo.get(pn); if (parser) parsed.pseudos.push({parser: parser, argument: pa}); else parsed.attributes.push({name: pn, operator: '=', value: pa}); } else if (an){ parsed.attributes.push({name: an, operator: ao, value: av}); } } if (!parsed.classes.length) delete parsed.classes; if (!parsed.attributes.length) delete parsed.attributes; if (!parsed.pseudos.length) delete parsed.pseudos; if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null; return Selectors.Cache.parsed[selector] = parsed; }, parseTagAndID: function(selector){ var tag = selector.match(Selectors.RegExps.tag); var id = selector.match(Selectors.RegExps.id); return [(tag) ? tag[1] : '*', (id) ? id[1] : false]; }, filter: function(item, parsed, local){ var i; if (parsed.classes){ for (i = parsed.classes.length; i--; i){ var cn = parsed.classes[i]; if (!Selectors.Filters.byClass(item, cn)) return false; } } if (parsed.attributes){ for (i = parsed.attributes.length; i--; i){ var att = parsed.attributes[i]; if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false; } } if (parsed.pseudos){ for (i = parsed.pseudos.length; i--; i){ var psd = parsed.pseudos[i]; if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false; } } return true; }, getByTagAndID: function(ctx, tag, id){ if (id){ var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true); return (item && Selectors.Filters.byTag(item, tag)) ? [item] : []; } else { return ctx.getElementsByTagName(tag); } }, search: function(self, expression, local){ var splitters = []; var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){ splitters.push(m1); return ':)' + m2; }).split(':)'); var items, filtered, item; for (var i = 0, l = selectors.length; i < l; i++){ var selector = selectors[i]; if (i == 0 && Selectors.RegExps.quick.test(selector)){ items = self.getElementsByTagName(selector); continue; } var splitter = splitters[i - 1]; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (i == 0){ items = Selectors.Utils.getByTagAndID(self, tag, id); } else { var uniques = {}, found = []; for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques); items = found; } var parsed = Selectors.Utils.parseSelector(selector); if (parsed){ filtered = []; for (var m = 0, n = items.length; m < n; m++){ item = items[m]; if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item); } items = filtered; } } return items; } }; Selectors.Getters = { ' ': function(found, self, tag, id, uniques){ var items = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = items.length; i < l; i++){ var item = items[i]; if (Selectors.Utils.chk(item, uniques)) found.push(item); } return found; }, '>': function(found, self, tag, id, uniques){ var children = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = children.length; i < l; i++){ var child = children[i]; if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child); } return found; }, '+': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); break; } } return found; }, '~': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (!Selectors.Utils.chk(self, uniques)) break; if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); } } return found; } }; Selectors.Filters = { byTag: function(self, tag){ return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag)); }, byID: function(self, id){ return (!id || (self.id && self.id == id)); }, byClass: function(self, klass){ return (self.className && self.className.contains(klass, ' ')); }, byPseudo: function(self, parser, argument, local){ return parser.call(self, argument, local); }, byAttribute: function(self, name, operator, value){ var result = Element.prototype.getProperty.call(self, name); if (!result) return (operator == '!='); if (!operator || value == undefined) return true; switch (operator){ case '=': return (result == value); case '*=': return (result.contains(value)); case '^=': return (result.substr(0, value.length) == value); case '$=': return (result.substr(result.length - value.length) == value); case '!=': return (result != value); case '~=': return result.contains(value, ' '); case '|=': return result.contains(value, '-'); } return false; } }; Selectors.Pseudo = new Hash({ // w3c pseudo selectors checked: function(){ return this.checked; }, empty: function(){ return !(this.innerText || this.textContent || '').length; }, not: function(selector){ return !Element.match(this, selector); }, contains: function(text){ return (this.innerText || this.textContent || '').contains(text); }, 'first-child': function(){ return Selectors.Pseudo.index.call(this, 0); }, 'last-child': function(){ var element = this; while ((element = element.nextSibling)){ if (element.nodeType == 1) return false; } return true; }, 'only-child': function(){ var prev = this; while ((prev = prev.previousSibling)){ if (prev.nodeType == 1) return false; } var next = this; while ((next = next.nextSibling)){ if (next.nodeType == 1) return false; } return true; }, 'nth-child': function(argument, local){ argument = (argument == undefined) ? 'n' : argument; var parsed = Selectors.Utils.parseNthArgument(argument); if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local); var count = 0; local.positions = local.positions || {}; var uid = $uid(this); if (!local.positions[uid]){ var self = this; while ((self = self.previousSibling)){ if (self.nodeType != 1) continue; count ++; var position = local.positions[$uid(self)]; if (position != undefined){ count = position + count; break; } } local.positions[uid] = count; } return (local.positions[uid] % parsed.a == parsed.b); }, // custom pseudo selectors index: function(index){ var element = this, count = 0; while ((element = element.previousSibling)){ if (element.nodeType == 1 && ++count > index) return false; } return (count == index); }, even: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n+1', local); }, odd: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n', local); } }); /* Script: Domready.js Contains the domready custom event. License: MIT-style license. */ Element.Events.domready = { onAdd: function(fn){ if (Browser.loaded) fn.call(this); } }; (function(){ var domready = function(){ if (Browser.loaded) return; Browser.loaded = true; window.fireEvent('domready'); document.fireEvent('domready'); }; if (Browser.Engine.trident){ var temp = document.createElement('div'); (function(){ ($try(function(){ temp.doScroll('left'); return $(temp).inject(document.body).set('html', 'temp').dispose(); })) ? domready() : arguments.callee.delay(50); })(); } else if (Browser.Engine.webkit && Browser.Engine.version < 525){ (function(){ (['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50); })(); } else { window.addEvent('load', domready); document.addEvent('DOMContentLoaded', domready); } })(); /* Script: JSON.js JSON encoder and decoder. License: MIT-style license. See Also: */ var JSON = new Hash({ $specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}, $replaceChars: function(chr){ return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16); }, encode: function(obj){ switch ($type(obj)){ case 'string': return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"'; case 'array': return '[' + String(obj.map(JSON.encode).filter($defined)) + ']'; case 'object': case 'hash': var string = []; Hash.each(obj, function(value, key){ var json = JSON.encode(value); if (json) string.push(JSON.encode(key) + ':' + json); }); return '{' + string + '}'; case 'number': case 'boolean': return String(obj); case false: return 'null'; } return null; }, decode: function(string, secure){ if ($type(string) != 'string' || !string.length) return null; if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null; return eval('(' + string + ')'); } }); Native.implement([Hash, Array, String, Number], { toJSON: function(){ return JSON.encode(this); } }); /* Script: Cookie.js Class for creating, loading, and saving browser Cookies. License: MIT-style license. Credits: Based on the functions by Peter-Paul Koch (http://quirksmode.org). */ var Cookie = new Class({ Implements: Options, options: { path: false, domain: false, duration: false, secure: false, document: document }, initialize: function(key, options){ this.key = key; this.setOptions(options); }, write: function(value){ value = encodeURIComponent(value); if (this.options.domain) value += '; domain=' + this.options.domain; if (this.options.path) value += '; path=' + this.options.path; if (this.options.duration){ var date = new Date(); date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); value += '; expires=' + date.toGMTString(); } if (this.options.secure) value += '; secure'; this.options.document.cookie = this.key + '=' + value; return this; }, read: function(){ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); return (value) ? decodeURIComponent(value[1]) : null; }, dispose: function(){ new Cookie(this.key, $merge(this.options, {duration: -1})).write(''); return this; } }); Cookie.write = function(key, value, options){ return new Cookie(key, options).write(value); }; Cookie.read = function(key){ return new Cookie(key).read(); }; Cookie.dispose = function(key, options){ return new Cookie(key, options).dispose(); }; /* Script: Fx.js Contains the basic animation logic to be extended by all other Fx Classes. License: MIT-style license. */ var Fx = new Class({ Implements: [Chain, Events, Options], options: { /* onStart: $empty, onCancel: $empty, onComplete: $empty, */ fps: 50, unit: false, duration: 500, link: 'ignore' }, initialize: function(options){ this.subject = this.subject || this; this.setOptions(options); this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt(); var wait = this.options.wait; if (wait === false) this.options.link = 'cancel'; }, getTransition: function(){ return function(p){ return -(Math.cos(Math.PI * p) - 1) / 2; }; }, step: function(){ var time = $time(); if (time < this.time + this.options.duration){ var delta = this.transition((time - this.time) / this.options.duration); this.set(this.compute(this.from, this.to, delta)); } else { this.set(this.compute(this.from, this.to, 1)); this.complete(); } }, set: function(now){ return now; }, compute: function(from, to, delta){ return Fx.compute(from, to, delta); }, check: function(caller){ if (!this.timer) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false; } return false; }, start: function(from, to){ if (!this.check(arguments.callee, from, to)) return this; this.from = from; this.to = to; this.time = 0; this.transition = this.getTransition(); this.startTimer(); this.onStart(); return this; }, complete: function(){ if (this.stopTimer()) this.onComplete(); return this; }, cancel: function(){ if (this.stopTimer()) this.onCancel(); return this; }, onStart: function(){ this.fireEvent('start', this.subject); }, onComplete: function(){ this.fireEvent('complete', this.subject); if (!this.callChain()) this.fireEvent('chainComplete', this.subject); }, onCancel: function(){ this.fireEvent('cancel', this.subject).clearChain(); }, pause: function(){ this.stopTimer(); return this; }, resume: function(){ this.startTimer(); return this; }, stopTimer: function(){ if (!this.timer) return false; this.time = $time() - this.time; this.timer = $clear(this.timer); return true; }, startTimer: function(){ if (this.timer) return false; this.time = $time() - this.time; this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this); return true; } }); Fx.compute = function(from, to, delta){ return (to - from) * delta + from; }; Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; /* Script: Fx.CSS.js Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. License: MIT-style license. */ Fx.CSS = new Class({ Extends: Fx, //prepares the base from/to object prepare: function(element, property, values){ values = $splat(values); var values1 = values[1]; if (!$chk(values1)){ values[1] = values[0]; values[0] = element.getStyle(property); } var parsed = values.map(this.parse); return {from: parsed[0], to: parsed[1]}; }, //parses a value into an array parse: function(value){ value = $lambda(value)(); value = (typeof value == 'string') ? value.split(' ') : $splat(value); return value.map(function(val){ val = String(val); var found = false; Fx.CSS.Parsers.each(function(parser, key){ if (found) return; var parsed = parser.parse(val); if ($chk(parsed)) found = {value: parsed, parser: parser}; }); found = found || {value: val, parser: Fx.CSS.Parsers.String}; return found; }); }, //computes by a from and to prepared objects, using their parsers. compute: function(from, to, delta){ var computed = []; (Math.min(from.length, to.length)).times(function(i){ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); }); computed.$family = {name: 'fx:css:value'}; return computed; }, //serves the value as settable serve: function(value, unit){ if ($type(value) != 'fx:css:value') value = this.parse(value); var returned = []; value.each(function(bit){ returned = returned.concat(bit.parser.serve(bit.value, unit)); }); return returned; }, //renders the change to an element render: function(element, property, value, unit){ element.setStyle(property, this.serve(value, unit)); }, //searches inside the page css to find the values for a selector search: function(selector){ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; var to = {}; Array.each(document.styleSheets, function(sheet, j){ var href = sheet.href; if (href && href.contains('://') && !href.contains(document.domain)) return; var rules = sheet.rules || sheet.cssRules; Array.each(rules, function(rule, i){ if (!rule.style) return; var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ return m.toLowerCase(); }) : null; if (!selectorText || !selectorText.test('^' + selector + '$')) return; Element.Styles.each(function(value, style){ if (!rule.style[style] || Element.ShortStyles[style]) return; value = String(rule.style[style]); to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value; }); }); }); return Fx.CSS.Cache[selector] = to; } }); Fx.CSS.Cache = {}; Fx.CSS.Parsers = new Hash({ Color: { parse: function(value){ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; }, compute: function(from, to, delta){ return from.map(function(value, i){ return Math.round(Fx.compute(from[i], to[i], delta)); }); }, serve: function(value){ return value.map(Number); } }, Number: { parse: parseFloat, compute: Fx.compute, serve: function(value, unit){ return (unit) ? value + unit : value; } }, String: { parse: $lambda(false), compute: $arguments(1), serve: $arguments(0) } }); /* Script: Fx.Tween.js Formerly Fx.Style, effect to transition any CSS property for an element. License: MIT-style license. */ Fx.Tween = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = $(element); this.parent(options); }, set: function(property, now){ if (arguments.length == 1){ now = property; property = this.property || this.options.property; } this.render(this.element, property, now, this.options.unit); return this; }, start: function(property, from, to){ if (!this.check(arguments.callee, property, from, to)) return this; var args = Array.flatten(arguments); this.property = this.options.property || args.shift(); var parsed = this.prepare(this.element, this.property, args); return this.parent(parsed.from, parsed.to); } }); Element.Properties.tween = { set: function(options){ var tween = this.retrieve('tween'); if (tween) tween.cancel(); return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('tween')){ if (options || !this.retrieve('tween:options')) this.set('tween', options); this.store('tween', new Fx.Tween(this, this.retrieve('tween:options'))); } return this.retrieve('tween'); } }; Element.implement({ tween: function(property, from, to){ this.get('tween').start(arguments); return this; }, fade: function(how){ var fade = this.get('tween'), o = 'opacity', toggle; how = $pick(how, 'toggle'); switch (how){ case 'in': fade.start(o, 1); break; case 'out': fade.start(o, 0); break; case 'show': fade.set(o, 1); break; case 'hide': fade.set(o, 0); break; case 'toggle': var flag = this.retrieve('fade:flag', this.get('opacity') == 1); fade.start(o, (flag) ? 0 : 1); this.store('fade:flag', !flag); toggle = true; break; default: fade.start(o, arguments); } if (!toggle) this.eliminate('fade:flag'); return this; }, highlight: function(start, end){ if (!end){ end = this.retrieve('highlight:original', this.getStyle('background-color')); end = (end == 'transparent') ? '#fff' : end; } var tween = this.get('tween'); tween.start('background-color', start || '#ffff88', end).chain(function(){ this.setStyle('background-color', this.retrieve('highlight:original')); tween.callChain(); }.bind(this)); return this; } }); /* Script: Fx.Morph.js Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. License: MIT-style license. */ Fx.Morph = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = $(element); this.parent(options); }, set: function(now){ if (typeof now == 'string') now = this.search(now); for (var p in now) this.render(this.element, p, now[p], this.options.unit); return this; }, compute: function(from, to, delta){ var now = {}; for (var p in from) now[p] = this.parent(from[p], to[p], delta); return now; }, start: function(properties){ if (!this.check(arguments.callee, properties)) return this; if (typeof properties == 'string') properties = this.search(properties); var from = {}, to = {}; for (var p in properties){ var parsed = this.prepare(this.element, p, properties[p]); from[p] = parsed.from; to[p] = parsed.to; } return this.parent(from, to); } }); Element.Properties.morph = { set: function(options){ var morph = this.retrieve('morph'); if (morph) morph.cancel(); return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('morph')){ if (options || !this.retrieve('morph:options')) this.set('morph', options); this.store('morph', new Fx.Morph(this, this.retrieve('morph:options'))); } return this.retrieve('morph'); } }; Element.implement({ morph: function(props){ this.get('morph').start(props); return this; } }); /* Script: Fx.Transitions.js Contains a set of advanced transitions to be used with any of the Fx Classes. License: MIT-style license. Credits: Easing Equations by Robert Penner, , modified and optimized to be used with MooTools. */ Fx.implement({ getTransition: function(){ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; if (typeof trans == 'string'){ var data = trans.split(':'); trans = Fx.Transitions; trans = trans[data[0]] || trans[data[0].capitalize()]; if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; } return trans; } }); Fx.Transition = function(transition, params){ params = $splat(params); return $extend(transition, { easeIn: function(pos){ return transition(pos, params); }, easeOut: function(pos){ return 1 - transition(1 - pos, params); }, easeInOut: function(pos){ return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2; } }); }; Fx.Transitions = new Hash({ linear: $arguments(0) }); Fx.Transitions.extend = function(transitions){ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); }; Fx.Transitions.extend({ Pow: function(p, x){ return Math.pow(p, x[0] || 6); }, Expo: function(p){ return Math.pow(2, 8 * (p - 1)); }, Circ: function(p){ return 1 - Math.sin(Math.acos(p)); }, Sine: function(p){ return 1 - Math.sin((1 - p) * Math.PI / 2); }, Back: function(p, x){ x = x[0] || 1.618; return Math.pow(p, 2) * ((x + 1) * p - x); }, Bounce: function(p){ var value; for (var a = 0, b = 1; 1; a += b, b /= 2){ if (p >= (7 - 4 * a) / 11){ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); break; } } return value; }, Elastic: function(p, x){ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3); } }); ['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ Fx.Transitions[transition] = new Fx.Transition(function(p){ return Math.pow(p, [i + 2]); }); }); /* Script: Request.js Powerful all purpose Request Class. Uses XMLHTTPRequest. License: MIT-style license. */ var Request = new Class({ Implements: [Chain, Events, Options], options: {/* onRequest: $empty, onComplete: $empty, onCancel: $empty, onSuccess: $empty, onFailure: $empty, onException: $empty,*/ url: '', data: '', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }, async: true, format: false, method: 'post', link: 'ignore', isSuccess: null, emulation: true, urlEncoded: true, encoding: 'utf-8', evalScripts: false, evalResponse: false }, initialize: function(options){ this.xhr = new Browser.Request(); this.setOptions(options); this.options.isSuccess = this.options.isSuccess || this.isSuccess; this.headers = new Hash(this.options.headers); }, onStateChange: function(){ if (this.xhr.readyState != 4 || !this.running) return; this.running = false; this.status = 0; $try(function(){ this.status = this.xhr.status; }.bind(this)); if (this.options.isSuccess.call(this, this.status)){ this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML}; this.success(this.response.text, this.response.xml); } else { this.response = {text: null, xml: null}; this.failure(); } this.xhr.onreadystatechange = $empty; }, isSuccess: function(){ return ((this.status >= 200) && (this.status < 300)); }, processScripts: function(text){ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text); return text.stripScripts(this.options.evalScripts); }, success: function(text, xml){ this.onSuccess(this.processScripts(text), xml); }, onSuccess: function(){ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); }, failure: function(){ this.onFailure(); }, onFailure: function(){ this.fireEvent('complete').fireEvent('failure', this.xhr); }, setHeader: function(name, value){ this.headers.set(name, value); return this; }, getHeader: function(name){ return $try(function(){ return this.xhr.getResponseHeader(name); }.bind(this)); }, check: function(caller){ if (!this.running) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false; } return false; }, send: function(options){ if (!this.check(arguments.callee, options)) return this; this.running = true; var type = $type(options); if (type == 'string' || type == 'element') options = {data: options}; var old = this.options; options = $extend({data: old.data, url: old.url, method: old.method}, options); var data = options.data, url = options.url, method = options.method; switch ($type(data)){ case 'element': data = $(data).toQueryString(); break; case 'object': case 'hash': data = Hash.toQueryString(data); } if (this.options.format){ var format = 'format=' + this.options.format; data = (data) ? format + '&' + data : format; } if (this.options.emulation && ['put', 'delete'].contains(method)){ var _method = '_method=' + method; data = (data) ? _method + '&' + data : _method; method = 'post'; } if (this.options.urlEncoded && method == 'post'){ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding); } if (data && method == 'get'){ url = url + (url.contains('?') ? '&' : '?') + data; data = null; } this.xhr.open(method.toUpperCase(), url, this.options.async); this.xhr.onreadystatechange = this.onStateChange.bind(this); this.headers.each(function(value, key){ try { this.xhr.setRequestHeader(key, value); } catch (e){ this.fireEvent('exception', [key, value]); } }, this); this.fireEvent('request'); this.xhr.send(data); if (!this.options.async) this.onStateChange(); return this; }, cancel: function(){ if (!this.running) return this; this.running = false; this.xhr.abort(); this.xhr.onreadystatechange = $empty; this.xhr = new Browser.Request(); this.fireEvent('cancel'); return this; } }); (function(){ var methods = {}; ['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){ methods[method] = function(){ var params = Array.link(arguments, {url: String.type, data: $defined}); return this.send($extend(params, {method: method.toLowerCase()})); }; }); Request.implement(methods); })(); Element.Properties.send = { set: function(options){ var send = this.retrieve('send'); if (send) send.cancel(); return this.eliminate('send').store('send:options', $extend({ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') }, options)); }, get: function(options){ if (options || !this.retrieve('send')){ if (options || !this.retrieve('send:options')) this.set('send', options); this.store('send', new Request(this.retrieve('send:options'))); } return this.retrieve('send'); } }; Element.implement({ send: function(url){ var sender = this.get('send'); sender.send({data: this, url: url || sender.options.url}); return this; } }); /* Script: Request.HTML.js Extends the basic Request Class with additional methods for interacting with HTML responses. License: MIT-style license. */ Request.HTML = new Class({ Extends: Request, options: { update: false, evalScripts: true, filter: false }, processHTML: function(text){ var match = text.match(/]*>([\s\S]*?)<\/body>/i); text = (match) ? match[1] : text; var container = new Element('div'); return $try(function(){ var root = '' + text + '', doc; if (Browser.Engine.trident){ doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = false; doc.loadXML(root); } else { doc = new DOMParser().parseFromString(root, 'text/xml'); } root = doc.getElementsByTagName('root')[0]; for (var i = 0, k = root.childNodes.length; i < k; i++){ var child = Element.clone(root.childNodes[i], true, true); if (child) container.grab(child); } return container; }) || container.set('html', text); }, success: function(text){ var options = this.options, response = this.response; response.html = text.stripScripts(function(script){ response.javascript = script; }); var temp = this.processHTML(response.html); response.tree = temp.childNodes; response.elements = temp.getElements('*'); if (options.filter) response.tree = response.elements.filter(options.filter); if (options.update) $(options.update).empty().set('html', response.html); if (options.evalScripts) $exec(response.javascript); this.onSuccess(response.tree, response.elements, response.html, response.javascript); } }); Element.Properties.load = { set: function(options){ var load = this.retrieve('load'); if (load) load.cancel(); return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options)); }, get: function(options){ if (options || ! this.retrieve('load')){ if (options || !this.retrieve('load:options')) this.set('load', options); this.store('load', new Request.HTML(this.retrieve('load:options'))); } return this.retrieve('load'); } }; Element.implement({ load: function(){ this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type})); return this; } }); /* Script: Request.JSON.js Extends the basic Request Class with additional methods for sending and receiving JSON data. License: MIT-style license. */ Request.JSON = new Class({ Extends: Request, options: { secure: true }, initialize: function(options){ this.parent(options); this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'}); }, success: function(text){ this.response.json = JSON.decode(text, this.options.secure); this.onSuccess(this.response.json, text); } }); /* Script: TipsX3.js Tooltips, BubbleTips, whatever they are, they will appear on mouseover License: MIT-style license. Credits: The idea behind Tips.js is based on Bubble Tooltips () by Alessandro Fulcitiniti TipsX3.js is based on Tips.js, with slight modifications, by razvan@e-magine.ro */ /* Class: TipsX3 Display a tip on any element with a title and/or href. Note: Tips requires an XHTML doctype. Arguments: elements - a collection of elements to apply the tooltips to on mouseover. options - an object. See options Below. Options: maxTitleChars - the maximum number of characters to display in the title of the tip. defaults to 30. onShow - optionally you can alter the default onShow behaviour with this option (like displaying a fade in effect); onHide - optionally you can alter the default onHide behaviour with this option (like displaying a fade out effect); showDelay - the delay the onShow method is called. (defaults to 100 ms) hideDelay - the delay the onHide method is called. (defaults to 100 ms) className - the prefix for your tooltip classNames. defaults to 'tool'. the whole tooltip will have as classname: tool-tip the title will have as classname: tool-title the text will have as classname: tool-text offsets - the distance of your tooltip from the mouse. an Object with x/y properties. fixed - if set to true, the toolTip will not follow the mouse. loadingText - text to display as a title while loading an AJAX tooltip. errTitle, errText - text to display when there's a problem with the AJAX request. Example: (start code) (end) Note: The title of the element will always be used as the tooltip body. If you put :: on your title, the text before :: will become the tooltip title. If you put DOM:someElementID in your title, $('someElementID').innerHTML will be used as your tooltip contents (same syntax as above). If you put AJAX:http://www.example.com/path/to/ajax_file.php in your title, the response text will be used as tooltip contents (same syntax as above). Either absolute or relative paths are ok. */ var Garbage = { elements: [], collect: function(el){ if (!el.$tmp){ Garbage.elements.push(el); el.$tmp = {'opacity': 1}; } return el; }, trash: function(elements){ for (var i = 0, j = elements.length, el; i < j; i++){ if (!(el = elements[i]) || !el.$tmp) continue; if (el.$events) el.fireEvent('trash').removeEvents(); for (var p in el.$tmp) el.$tmp[p] = null; for (var p in Element.prototype) el[p] = null; el.htmlElement = el.$tmp = el = null; Garbage.elements.remove(el); } }, empty: function(){ Garbage.collect(window); Garbage.collect(document); Garbage.trash(Garbage.elements); } }; var TipsX3 = new Class({ options: { // modded for X3 onShow: function(tip){ //tip.fade('in'); tip.setStyle('visibility', 'visible'); }, onHide: function(tip){ //tip.fade('out'); tip.setStyle('visibility', 'hidden'); }, maxTitleChars: 8, showDelay: 100, hideDelay: 100, className: 'armory-tooltip', offsets: {'x': 8, 'y': 8}, fixed: false, loadingText: 'Please wait...', errTitle: 'Error', errText: 'There was a problem retrieving the item from the WoW Armory', maxWidth: 250, idName: '' }, initialize: function(elements, options){ this.setOptions(options); this.toolTip = new Element('div', { 'class': this.options.className + '-tip', 'styles': { 'position': 'absolute', 'top': '0', 'left': '0', 'visibility': 'hidden', 'max-width' : this.options.maxWidth, 'z-index' : 5000 } }).inject(document.body); this.wrapper = new Element('div').inject(this.toolTip); $$(elements).each(this.build, this); if (this.options.initialize) this.options.initialize.call(this); }, build: function(el){ if (!el) return false; if (el) Garbage.collect( el ); if( !el.$tmp || el.$tmp == undefined ) return; el.$tmp.myTitle = (el.href && el.getTag() == 'a') ? el.href.replace('http://', '') : (el.rel || false); if (el.title){ // check if we need to extract contents from a DOM element if (el.title.test('^DOM:', 'i')) { el.title = $(el.title.split(':')[1].trim()).innerHTML; } // check for an URL to retrieve content from if (el.title.test('^AJAX:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } // check for an URL to retrieve content from if (el.title.test('^Item:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } // check for an URL to retrieve content from if (el.title.test('^WoWArmory:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } // check for an URL to retrieve content from if (el.title.test('^Character:', 'i')) { el.title = this.options.loadingText + '::' + el.title; } var dual = el.title.split('::'); if (dual.length > 1) { el.$tmp.myTitle = dual[0].trim(); el.$tmp.myText = dual[1].trim(); } else { el.$tmp.myText = el.title; } el.removeAttribute('title'); } else { el.$tmp.myText = false; } if (el.$tmp.myTitle && el.$tmp.myTitle.length > this.options.maxTitleChars) el.$tmp.myTitle = el.$tmp.myTitle.substr(0, this.options.maxTitleChars - 1) + "…"; el.addEvent('mouseenter', function(event){ this.start(el); if (!this.options.fixed) this.locate(event); else this.position(el); }.bind(this)); if (!this.options.fixed) el.addEvent('mousemove', this.locate.bindWithEvent(this)); var end = this.end.bind(this); el.addEvent('mouseleave', end); el.addEvent('trash', end); }, modify: function( element, newTitle, newText ){ element.$tmp.myTitle = newTitle; element.$tmp.myText = newText; this.startFinal( element, false ); }, modifyItem: function( element, newTitle, newText, itemIcon ){ element.$tmp.myTitle = newTitle; element.$tmp.myText = newText; element.$tmp.itemIcon = itemIcon; this.startFinal( element, false ); }, modifyNotActive: function( element, newTitle, newText ){ element.$tmp.myTitle = newTitle; element.$tmp.myText = newText; }, start: function( el ) { this.startFinal( el, true ); }, startFinal: function( el, show ) { this.wrapper.empty(); // check if we have an AJAX Item request - if so, show a loading animation and launch the request if (el.$tmp.myText && el.$tmp.myText.test('^Item:', 'i')) { var linkObj = this; this.ajax = new Request.JSON( { link: 'cancel', evalScripts: false, url: el.$tmp.myText.replace(/Item:/i,'/powered/items/'), onComplete: function( item ){ linkObj.modifyItem( el, '', item.html, item.icon ) }}).get(); el.$tmp.myText = '
'; } // check if we have an AJAX Char request - if so, show a loading animation and launch the request if (el.$tmp.myText && el.$tmp.myText.test('^Character:', 'i')) { var linkObj = this; this.ajax = new Request.HTML( { link: 'cancel', evalScripts: false, url: el.$tmp.myText.replace(/Character:/i,'/powered/characters/'), onSuccess: function( var1, var2, response, var3 ) { linkObj.modify( el, '', response ) }}).get(); el.$tmp.myText = '
'; } // check if we have an AJAX wow armory item request - if so, show a loading animation and launch the request if (el.$tmp.myText && el.$tmp.myText.test('^WoWArmory:', 'i')) { var linkObj = this; var myArmoryUrl = el.$tmp.myText.replace(/WoWArmory:/i,''); myArmoryUrl = '/wowArmoryProxy.php?url=' + escape( myArmoryUrl ); this.ajax = new Request.HTML( { link: 'cancel', evalScripts: false, url: myArmoryUrl, onSuccess: function( var1, var2, response, var3 ) { linkObj.modify( el, '', response ) }}).get(); el.$tmp.myText = '
'; } if( el.$tmp.itemIcon ) { this.wrapper.set( 'html', '
' ); } else { this.wrapper.set( 'html', '
' ); } this.theTip = $( this.options.idName ); if (el.$tmp.myTitle){ this.title = new Element('span').inject( new Element('div', {'class': this.options.className + '-title'}).inject( this.theTip ) ).set( 'html', el.$tmp.myTitle ); } if (el.$tmp.myText){ this.text = new Element('span').inject( new Element('div', {'class': this.options.className + '-text'}).inject( this.theTip ) ).set( 'html', el.$tmp.myText ); } if( show == true ) { $clear(this.timer); this.timer = this.show.delay( this.options.showDelay, this); } }, end: function(event){ $clear(this.timer); this.timer = this.hide.delay( this.options.hideDelay, this); }, position: function(element){ var pos = element.getPosition(); this.toolTip.setStyles({ 'left': pos.x + this.options.offsets.x, 'top': pos.y + this.options.offsets.y }); }, locate: function(event){ var win = {'x': window.getWidth(), 'y': window.getHeight()}; var scroll = {'x': window.getScrollLeft(), 'y': window.getScrollTop()}; var tip = {'x': this.toolTip.offsetWidth, 'y': this.toolTip.offsetHeight}; var prop = {'x': 'left', 'y': 'top'}; for (var z in prop){ var pos = event.page[z] + this.options.offsets[z]; if ((pos + tip[z] - scroll[z]) > win[z]) pos =event.page[z] - this.options.offsets[z] - tip[z]; if( z == 'y' && pos - scroll.y < 10 ) { pos = scroll.y + 10; } this.toolTip.setStyle(prop[z], pos); }; }, show: function(){ if (this.options.timeout) this.timer = this.hide.delay( this.options.timeout, this ); this.fireEvent( 'onShow', [this.toolTip] ); }, hide: function(){ this.fireEvent( 'onHide', [this.toolTip] ); } }); TipsX3.implement( new Events, new Options );var MorphList = new Class({ Implements: [Events, Options], options: {/* onClick: $empty, onMorph: $empty,*/ morph: { 'link': 'cancel' } }, initialize: function(menu, options) { var that = this; this.setOptions(options); this.menu = $(menu); this.menuitems = this.menu.getChildren(); this.menuitems.addEvents({ mouseenter: function(){ that.morphTo(this); }, mouseleave: function(){ that.morphTo(that.current); }, click: function(ev){ that.click(ev, this); } }); this.bg = new Element('li', {'class': 'background'}).adopt(new Element('div', {'class': 'left'})); this.bg.inject(this.menu).set('morph', this.options.morph); this.setCurrent(this.menu.getElement('.current')); }, click: function(ev, item) { this.setCurrent(item, true); this.fireEvent('onClick', [ev, item]); }, setCurrent: function(el, effect){ if(el && ! this.current) { this.bg.set('styles', { left: el.offsetLeft, width: el.offsetWidth, height: el.offsetHeight, top: el.offsetTop }); (effect) ? this.bg.fade('hide').fade('in') : this.bg.fade('show'); } if(this.current) this.current.removeClass('current'); if(el) this.current = el.addClass('current'); }, morphTo: function(to) { if(! this.current) return; this.bg.morph({ left: to.offsetLeft, top: to.offsetTop, width: to.offsetWidth, height: to.offsetHeight }); this.fireEvent('onMorph', to); } }); window.addEvent('domready', function() { // attach fancy menu for browsers other than ie6 if( !Browser.Engine.trident4 ) { new MorphList( $('nav'), { transition: Fx.Transitions.backOut, duration: 700 } ); } // Check all links for item links or character links var allLinks = $$( 'a' ); for (var i = allLinks.length - 1; i >= 0; i--){ var currentLink = allLinks[i]; if( !currentLink.href || currentLink.href == '' ) continue; if( currentLink.rel && currentLink.rel == 'notooltip' ) continue; if( currentLink.id && currentLink.id == 'fdbk_tab' ) continue; var regExp1 = new RegExp( "^http://" + window.location.hostname + "\/items\/", "mi" ); var regExp2 = new RegExp( "^\/items\/", "mi" ); var regExp3 = new RegExp( "^http://" + window.location.hostname + "\/eu\/", "mi" ); var regExp4 = new RegExp( "^\/eu\/", "mi" ); var regExp5 = new RegExp( "^http://" + window.location.hostname + "\/us\/", "mi" ); var regExp6 = new RegExp( "^\/us\/", "mi" ); var regExp7 = new RegExp( "^http://" + window.location.hostname + "\/cn\/", "mi" ); var regExp8 = new RegExp( "^\/cn\/", "mi" ); var regExp9 = new RegExp( "^http://" + window.location.hostname + "\/kr\/", "mi" ); var regExp10 = new RegExp( "^\/kr\/", "mi" ); var regExp11 = new RegExp( "^http://" + window.location.hostname + "\/tw\/", "mi" ); var regExp12 = new RegExp( "^\/tw\/", "mi" ); // Check whether it starts with Armory Light if( regExp1.test( currentLink.href ) == false && regExp2.test( currentLink.href ) == false && regExp3.test( currentLink.href ) == false && regExp4.test( currentLink.href ) == false && regExp5.test( currentLink.href ) == false && regExp6.test( currentLink.href ) == false && regExp7.test( currentLink.href ) == false && regExp8.test( currentLink.href ) == false && regExp9.test( currentLink.href ) == false && regExp10.test( currentLink.href ) == false && regExp11.test( currentLink.href ) == false && regExp12.test( currentLink.href ) == false ) continue; var linkElements = currentLink.href.split( '/' ); var linkRel = currentLink.rel; for (var j=linkElements.length; j <= 6; j++) { linkElements[j] = ''; }; var LinkType = linkElements[3].toLowerCase(); var ItemId = linkElements[4].toLowerCase(); var ServerName = linkElements[4]; var CharacterName = linkElements[5]; var JSONRequestUrl = false; if( LinkType == 'items' && ItemId != '' && ItemId > 0 && linkRel != 'notip' && linkRel.match('wowarmory') == null ) { // Ok it's an item Link, create a span wrapper var mySpan = new Element('span', { 'title': 'Item:' + ItemId, 'class': 'armoryitemtip' } ); mySpan.wraps( currentLink ); } else if( LinkType == 'items' && ItemId != '' && ItemId > 0 && linkRel.match('wowarmory') == 'wowarmory' ) { // Ok it's an item Link for the wow armory, create a span wrapper var linkRelElements = currentLink.rel.split( ':' ); var armoryRegion = linkRelElements[1]; if( armoryRegion == 'us' ) armoryRegion = 'www'; var mySpan = new Element('span', { 'title': 'WoWArmory:http://' + armoryRegion + '.wowarmory.com/item-tooltip.xml?i=' + ItemId + '&s=' + linkRelElements[4] + '&r=' + linkRelElements[2] + '&n=' + linkRelElements[3], 'class': 'armoryitemtip' } ); mySpan.wraps( currentLink ); } else if( ( LinkType == 'eu' || LinkType == 'us' || LinkType == 'kr' || LinkType == 'cn' || LinkType == 'tw' )&& ServerName != '' && CharacterName != '' ) { // Ok it's an item Link, create a span wrapper var mySpan = new Element('span', { 'title': 'Character:' + LinkType + '/' + ServerName + '/' + CharacterName, 'class': 'armoryitemtip' } ); mySpan.wraps( currentLink ); } else { continue; } }; // Preload tooltip background new Element('img',{ src: 'http://static.armory-light.com/images/tooltip-trans.png' }); // Attach tooltips to all armorytip elements var ArmoryLightTips = new TipsX3( $$('.armorytip'), { maxTitleChars: 100, showDelay: 0, maxWidth: 265, idName: 'armoryGenericTipContent' } ); // Attach item tooltips to all armoryitemtip elements var ArmoryLightTips = new TipsX3( $$('.armoryitemtip'), { maxTitleChars: 100, maxWidth: 335, showDelay: 0, idName: 'armoryItemTipContent', loadingText: 'Loading...' } ); // Set highslide options if( undefined !== window.hs ) { hs.graphicsDir = 'http://static.armory-light.com/images/highslide/'; hs.outlineType = 'rounded-black'; hs.wrapperClassName = 'draggable-header no-footer wide-border'; hs.allowSizeReduction = false; hs.preserveContent = false; hs.align = 'center'; hs.dimmingOpacity = 0.35; hs.dragByHeading = false; hs.fadeInOut = true; hs.dimmingGeckoFix = true; hs.dimmingDuration = 20; hs.showCredits = false; hs.objectLoadTime = 'after'; hs.transitions = ["expand"]; hs.Expander.prototype.onDrag = function (sender, e) { return false; }; hs.Expander.prototype.onBeforeClose = function (sender, e) { window.location.hash = '#'; return true; }; } }); Array.prototype.inArray = function( value ) { var i; for (i=0; i < this.length; i++) { // Matches identical (===), not just similar (==). if (this[i] === value) { return true; } } return false; }; var talent = new Array(); var rank = new Array(); var tree = new Array(); var treeStartStop = new Array(); var currentLevel = getLevel(); var numPointsAvailable = currentLevel - 9; var windowTitle = ''; var CurrentSpec = ''; var maxTierArray = new Array(); var ArmoryLightTalentTips = null; // Onload handler window.addEvent( 'domready', function() { // Get the window title if( document.title ) { windowTitle = document.title; var firstArrowPos = windowTitle.indexOf( '»' ); if( firstArrowPos > 0 ) { windowTitle = windowTitle.substring( 0, firstArrowPos ) + '({t0}/{t1}/{t2}) ' + windowTitle.substring( firstArrowPos ); } } // initialise all talents initTalents(); // Create/edit link to talent spec $('resetalltalents').addEvent( 'click', function( e ) { var pQuestion = confirm( 'Do you really want to reset all talent trees?' ); if( pQuestion ) { CurrentSpec = ''; CheckSpec(); for (theTalentID = 0; talent[theTalentID]; theTalentID++) { RenderOneTalent( theTalentID, false ); } RenderTalents(); // prevent default link e.stop(); return false; } }); // initialise tooltips ArmoryLightTalentTips = new TipsX3( $$('.armorytiptalents'), { maxTitleChars: 100, showDelay: 0, maxWidth: 300, idName: 'armoryTalentTipContent' } ); }); window.addEvent( 'load', function() { var myFx = new Fx.Tween( 'overlay' ); myFx.start( 'opacity', '0.85', '0' ); }); function UpdateControls() { // Display available points var tPointsAvail = getAvailablePoints(); if( tPointsAvail == 0 ) tPointsAvail = 'none'; $('pointsAvailable').set( 'text', tPointsAvail ); // Display available points var tRequiredLevel = 80 - ( getAvailablePoints() ) ; if( tRequiredLevel < 10 ) tRequiredLevel = 10; $('requiresLevel').set( 'text', tRequiredLevel ); // Update spent points for( var i=1; i <=3; i++ ) { var control = $( 'talentTreePointsSpent' + i ); control.set( 'text', getSpentPointsInTree( i-1 ) ); }; // Update window title if( document.title ) { var obj = { t0: getSpentPointsInTree( 0 ).toString(), t1: getSpentPointsInTree( 1 ).toString(), t2: getSpentPointsInTree( 2 ).toString() }; var title = windowTitle; document.title = title.substitute( obj ); } } // Fill the trees with talents function initTalents() { // First, hide the table with an overlay var tablesContainer = $('talentTrees'); var tablesContainerSize = tablesContainer.getSize(); $('overlay').style.width = tablesContainerSize.x + 'px'; $('overlay').style.height = tablesContainerSize.y + 'px'; $('overlay').style.display = 'block'; // Get current talent tree CurrentSpec = getTalents(); CheckSpec(); // Create/edit link to talent spec $('talentbuildlink').set( 'href', '/talents/' + playerClass.toLowerCase().stripSpaces() + '/' + CurrentSpec ); // Set the tree backgrounds $( 'talentTree0' ).setStyle( 'background-image', 'url(http://static.armory-light.com/images/talentCalculator/' + playerClass + '/' + tree[0].toLowerCase().stripSpaces() + '/background.jpg)' ); $( 'talentTree1' ).setStyle( 'background-image', 'url(http://static.armory-light.com/images/talentCalculator/' + playerClass + '/' + tree[1].toLowerCase().stripSpaces() + '/background.jpg)' ); $( 'talentTree2' ).setStyle( 'background-image', 'url(http://static.armory-light.com/images/talentCalculator/' + playerClass + '/' + tree[2].toLowerCase().stripSpaces() + '/background.jpg)' ); // Set the max tier for each tree to 0 maxTierArray[0] = 0; maxTierArray[1] = 0; maxTierArray[2] = 0; // Make text in talent trees not selectable disableSelection( $( 'talentTrees' ) ); // Display the controls for (var i=1; i <=3; i++) { var controls = $( 'talentTreeControls' + i ); var controlHtml = ' '; controlHtml += tree[i-1]; controlHtml += ' ()'; controls.set( 'html', controlHtml ); }; // Attach tooltips var ArmoryLightTipsGenerated = new TipsX3( $$('.armorytipgenerated'), { maxTitleChars: 100, showDelay: 0, maxWidth: 265, idName: 'armoryGenericTipContentGenerated' } ); UpdateControls(); // Figure out which image we use var cTalentIcons = 'http://static.armory-light.com/images/talentCalculator/' + playerClass.toLowerCase().stripSpaces() + '/' + playerClass.toLowerCase().stripSpaces(); // Loop over all talents var iPosition = 0; var cMyIconIndent = 0; var cTreeTurnover = 0; for (theTalentID = 0; talent[theTalentID]; theTalentID++) { curTalent = talent[theTalentID]; var cTree = curTalent[0]; // Tree 0-2 var cColumn = curTalent[3]; // Column 1-4 var cTier = curTalent[4]; // Tier 1-11 (11 for the patch 3.0.2 talents) var cName = curTalent[1]; // The talent name var cMaxPoint = curTalent[2]; // Maximum number of points for that talent (1-5) // Find out the ID of the element (for example s0t9c2) var tId = 's' + cTree + 't' + cTier + 'c' + cColumn; var tIdElement = $( tId ); // Find out how many points were spent for that talent var talentPointsSet = getSpentPointsInTalent( iPosition ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // If we're in a new tree, reset the indent if( cTreeTurnover != cTree ) { cTreeTurnover = cTree; cMyIconIndent = 0; } // If talent points are spent, update the maxTier array if( talentPointsSet > 0 ) maxTierArray[cTree] = cTier; // Fill the ID element with a clickable div and a span which holds the talent points tIdElement.set( 'html', '
', talentPointsSet, '/', cMaxPoint, '
' ); // Add the hasTalent class (this adds the background graphic for that talent slot) tIdElement.addClass( 'hasTalent' ); var myTalentImage = $( 'talent' + tId ); myTalentImage.setStyle( 'background-image', 'url(' + cTalentIcons + '_' + (cTree + 1) + '.jpg)' ); if( talentPointsSet > 0 ) myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px 0px' ); else myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px -36px' ); // Find out which other talents this talent depends on if( curTalent[5] && curTalent[5][0] && typeof curTalent[5] == "object" ) { var dependency = curTalent[5][0]; var depTalent = talent[dependency]; var stepsLeft = ( cColumn - depTalent[3] ); var stepsUp = ( cTier - depTalent[4] ); var imageFile = ''; if( stepsUp > 0 ) imageFile += "down-" + stepsUp; if( stepsLeft > 0 ) imageFile += "-right-" + stepsLeft; else if( stepsLeft < 0 ) imageFile += "-left-" + (stepsLeft*-1); var arrowColor = 'grey'; if( getSpentPointsInTalent( iPosition ) == cMaxPoint ) arrowColor = 'yellow'; else if( getAvailablePoints() <= 0 ) arrowColor = 'grey'; else if( isDependencySatisfied( curTalent ) && getSpentPointsInTree( cTree ) >= ( cTier - 1 ) * 5 ) arrowColor = 'green'; var myArrow = new Element( 'div', { 'class' : arrowColor + ' dependency arrow-' + imageFile, 'id' : 'arrow' + tId }); // Inject the arrow element myArrow.inject( tIdElement, 'top' ); } // Add hover and click events var myButton = $( 'talentButton' + iPosition ); if( Browser.Engine.trident == true ) myButton.addEvent( 'dblclick', function( event ) { clickTalent( event, this.getProperty( 'rel' ) ) } ); myButton.addEvent( 'mousedown', function( event ) { clickTalent( event, this.getProperty( 'rel' ) ) } ); myButton.addEvent( 'contextmenu', function( event ) { event.stop(); return false; } ); // Add the correct class to the TD, remove the other classes if( getSpentPointsInTalent( iPosition ) == 0 ) { tIdElement.addClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( iPosition ) == cMaxPoint ) { tIdElement.addClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( iPosition ) < cMaxPoint ) { tIdElement.addClass( 'talentSomePoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); } // Check whether the talent can be learned if( getAvailablePoints() > 0 && parseInt( getSpentPointsInTree( cTree ) ) >= parseInt( ( cTier - 1 ) * 5 ) && isDependencySatisfied( curTalent ) ) tIdElement.addClass( 'talentCanLearn' ); else tIdElement.removeClass( 'talentCanLearn' ); // Go to next position iPosition++; // Increase the indent by 36 (pixels) since each talent icon is offset by 36 pixels cMyIconIndent += 36; } } // This function re-renders the talent tree based on the current spec function RenderOneTalent( theTalentID, callRenderTalents ) { curTalent = talent[theTalentID]; var cTree = curTalent[0]; var cColumn = curTalent[3]; var cTier = curTalent[4]; var cName = curTalent[1]; var cMaxPoint = curTalent[2]; var cMyIconIndent = theTalentID * 36; if( cTree > 0 ) cMyIconIndent = cMyIconIndent - ( parseInt( treeStartStop[0] + 1 ) * 36); if( cTree > 1 ) cMyIconIndent = cMyIconIndent + ( parseInt( treeStartStop[0] + 1 ) * 36) - ( parseInt( treeStartStop[1] + 1 ) * 36 ); // Find out the ID of the element (for example s0t9c2) var tId = 's' + cTree + 't' + cTier + 'c' + cColumn; var tIdElement = $( tId ); // Find out how many points were spent for that talent var talentPointsSet = getSpentPointsInTalent( theTalentID ); var myTalentImage = $( 'talent' + tId ); var myTalentPoints = $( 'talentPoints' + theTalentID ); // Set the background position of the talent icon, greying it out or making it visible if( talentPointsSet > 0 ) myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px 0px' ); else myTalentImage.setStyle( 'background-position', '-' + cMyIconIndent + 'px -36px' ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // Set the talent button title ArmoryLightTalentTips.modify( $('talentButton' + theTalentID ), cName, cRankDescription ); // Update the points myTalentPoints.set( 'text', talentPointsSet + '/' + cMaxPoint ); // Add the correct class to the TD if( getSpentPointsInTalent( theTalentID ) == 0 ) { tIdElement.addClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( theTalentID ) == cMaxPoint ) { tIdElement.addClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); tIdElement.removeClass( 'talentSomePoints' ); } else if( getSpentPointsInTalent( theTalentID ) < cMaxPoint ) { tIdElement.addClass( 'talentSomePoints' ); tIdElement.removeClass( 'talentAllPoints' ); tIdElement.removeClass( 'talentNoPoints' ); } if( callRenderTalents ) RenderTalents(); } // This function re-renders the talent tree based on the current spec function RenderTalents() { UpdateControls(); // Create/edit link to talent spec $('talentbuildlink').set( 'href', '/talents/' + playerClass.toLowerCase().stripSpaces() + '/' + CurrentSpec ); // Loop over all talents var iPosition = 0; for (theTalentID = 0; talent[theTalentID]; theTalentID++) { curTalent = talent[theTalentID]; var cTree = curTalent[0]; var cColumn = curTalent[3]; var cTier = curTalent[4]; var cName = curTalent[1]; var cMaxPoint = curTalent[2]; // Find out how many points were spent for that talent var talentPointsSet = getSpentPointsInTalent( theTalentID ); // If talent points are spent, update the maxTier array if( talentPointsSet > 0 ) maxTierArray[cTree] = cTier; // Find out the ID of the element (for example s0t9c2) var tId = 's' + cTree + 't' + cTier + 'c' + cColumn; var tIdElement = $( tId ); // Check whether the talent can be learned if( getAvailablePoints() > 0 && getSpentPointsInTree( cTree ) >= ( cTier - 1 ) * 5 && isDependencySatisfied( curTalent ) ) { // CanLearn, check if the element already has the class if( !tIdElement.hasClass( 'talentCanLearn' ) ) { tIdElement.addClass( 'talentCanLearn' ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // Set the talent button title ArmoryLightTalentTips.modifyNotActive( $('talentButton' + theTalentID ), cName, cRankDescription ); } } else { // CanNOTLearn, check if the element already has the class if( tIdElement.hasClass( 'talentCanLearn' ) ) { tIdElement.removeClass( 'talentCanLearn' ); // Get the talent description for the current Rank and for the next rank if( talentPointsSet == 0 ) { // Only get next rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) cRankDescription += "Requires " + parseInt( ( cTier - 1 ) * 5 ) + " points spent in " + tree[cTree] + " Talents"; if( !isDependencySatisfied( curTalent ) ) { var dependency = curTalent[5][0]; var dependantPoints = curTalent[5][1]; cRankDescription += "Requires " + dependantPoints + " points spent in " + talent[dependency][1] + ""; } cRankDescription += rank[theTalentID][0]; cRankDescription += "
Click to learn"; } else if( talentPointsSet > 0 && talentPointsSet >= cMaxPoint ) { // Only get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // Different tooltip for Opera if( Browser.Engine.presto && Browser.Platform.mac ) cRankDescription += "
⌘ + click to unlearn"; else if( Browser.Engine.presto ) cRankDescription += "
Ctrl + click to unlearn"; else cRankDescription += "
Right click to unlearn"; } else { // Get current rank cRankDescription = "Rank " + talentPointsSet + "/" + cMaxPoint + ""; cRankDescription += rank[theTalentID][talentPointsSet - 1]; // And add next rank description cRankDescription += "
Next rank:"; cRankDescription += rank[theTalentID][talentPointsSet]; cRankDescription += "
Click to learn"; } // Set the talent button title ArmoryLightTalentTips.modifyNotActive( $('talentButton' + theTalentID ), cName, cRankDescription ); } } // Find out which other talents this talent depends on if( curTalent[5] && curTalent[5][0] && typeof curTalent[5] == "object" ) { var arrowColor = 'grey'; if( getSpentPointsInTalent( iPosition ) == cMaxPoint ) arrowColor = 'yellow'; else if( getAvailablePoints() <= 0 ) arrowColor = 'grey'; else if( isDependencySatisfied( curTalent ) && getSpentPointsInTree( cTree ) >= ( cTier - 1 ) * 5 ) arrowColor = 'green'; // Try to find the arrow element var myArrow = $( 'arrow' + tId ); // if the arrow element really exists... if( myArrow ) { if( arrowColor == 'grey' ) { myArrow.removeClass( 'yellow' ); myArrow.removeClass( 'green' ); myArrow.addClass( 'grey' ); } else if( arrowColor == 'yellow' ) { myArrow.removeClass( 'grey' ); myArrow.removeClass( 'green' ); myArrow.addClass( 'yellow' ); } else if( arrowColor == 'green' ) { myArrow.removeClass( 'grey' ); myArrow.removeClass( 'yellow' ); myArrow.addClass( 'green' ); } } } // Go to next position iPosition++; } } // Checks a talent tree for correct length function CheckSpec() { if( CurrentSpec.length != ( parseInt( treeStartStop[2] ) + 1 ) ) { CurrentSpec = CurrentSpec.pad( parseInt( treeStartStop[2] ) + 1, "0", 1 ); } } // Strip all spaces from a string String.prototype.stripSpaces = function() { return this.replace( /\s/g, "" ); }; String.prototype.pad = function(l, s, t){ return s || (s = " "), (l -= this.length) > 0 ? (s = new Array(Math.ceil(l / s.length) + 1).join(s)).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2)) + this + s.substr(0, l - t) : this; }; function getTalentID(talentName) { var theTalentID; for (theTalentID = 0; talent[theTalentID]; theTalentID++) { if (talent[theTalentID][1] == talentName) return theTalentID; } } function getAvailablePoints() { return parseInt( numPointsAvailable - getSpentPoints( CurrentSpec ) ); } function getSpentPoints( TalentTree ) { var PointsSpent = 0; for( var i = TalentTree.length - 1; i >= 0; i-- ) { PointsSpent += parseInt( TalentTree.charAt( i ) ); }; return PointsSpent; } function getSpentPointsInTalent( talentID ) { var PointsSpent = CurrentSpec.charAt( talentID ); if( PointsSpent == "" ) PointsSpent = 0; return parseInt( PointsSpent ); } function getSpentPointsInTree( treeID ) { var myTalentTree = new Array(); myTalentTree[0] = CurrentSpec.substring( 0, treeStartStop[0] + 1 ); myTalentTree[1] = CurrentSpec.substring( treeStartStop[0] + 1, treeStartStop[1] + 1 ); myTalentTree[2] = CurrentSpec.substring( treeStartStop[1] + 1, treeStartStop[2] + 1 ); var cSelectedTree = myTalentTree[treeID]; var PointsSpent = 0; for( var i = cSelectedTree.length - 1; i >= 0; i-- ) { PointsSpent += parseInt( cSelectedTree.charAt( i ) ); }; return PointsSpent; } function isDependencySatisfied( learnTalent ) { // Does the talent have a dependency and is that dependency satisfied if( learnTalent[5] && learnTalent[5][0] && typeof learnTalent[5] == "object" ) { var dependency = learnTalent[5][0]; var dependantPoints = learnTalent[5][1]; var depTalent = talent[dependency]; var depTalentID = getTalentID( depTalent[1] ); if( getSpentPointsInTalent( depTalentID ) != dependantPoints ) return false; } return true; } function hasDependentTalentWithPoints( talentID ) { var loopStart; var loopStop; var theTree = talent[talentID][0]; if ( talentID != 0 ) loopStart = talentID - 1; else loopStart = talentID; loopStop = treeStartStop[theTree]; while( loopStart <= loopStop ){ if( talent[loopStart][5] && talent[loopStart][5][0] == talentID && getSpentPointsInTalent( loopStart ) != 0 ) return true; loopStart++; } return false; } function getPointsSpentInTier( theTree, theTier ) { var PointsSpentInTier = 0; for (var i = talent.length - 1; i >= 0; i--){ if( talent[i][0] == theTree && talent[i][4] == theTier ) { // This is a talent in the right tree and in the right tier! PointsSpentInTier += parseInt( getSpentPointsInTalent( i ) ); } }; return PointsSpentInTier; } function clickTalent( e, talentID ) { if( e.shift || e.alt || e.control || e.meta || e.rightClick ) UnlearnTalent( talentID, 1 ); else LearnTalent( talentID, 1 ); e.stop(); } function LearnTalent( talentID, numPoints ) { var learnTalent = talent[talentID]; var cTree = learnTalent[0]; var cColumn = learnTalent[3]; var cTier = learnTalent[4]; var cName = learnTalent[1]; var cMaxPoint = learnTalent[2]; // Check whether there are points left to learn if( getAvailablePoints() <= 0 ) return; // Check whether max number of points already spent in that talent if( getSpentPointsInTalent( talentID ) >= cMaxPoint ) return; // Depending on the tier, calculate whether enough points were spent to go deeper into the tree if( parseInt( getSpentPointsInTree( cTree ) ) < parseInt( ( cTier - 1 ) * 5 ) ) return; // Does the talent have a dependency and is that dependency satisfied if( !isDependencySatisfied( learnTalent ) ) return; // Ok, all dependencies are satisfied... build the new tree with one more point spent :) CurrentSpec = (CurrentSpec.slice( 0, talentID )).toString() + ( parseInt( getSpentPointsInTalent( talentID ) ) + numPoints ).toString() + (CurrentSpec.slice( parseInt( talentID ) + 1 ) ).toString(); // Render the talent trees RenderOneTalent( talentID, true ); } function UnlearnTalent( talentID, numPoints ) { // Check whether there are points spent in that talent if( getSpentPointsInTalent( talentID ) < numPoints ) return; if( hasDependentTalentWithPoints( talentID ) ) return; var unLearnTalent = talent[talentID]; var cTree = unLearnTalent[0]; var cColumn = unLearnTalent[3]; var cTier = unLearnTalent[4]; var cName = unLearnTalent[1]; var cMaxPoint = unLearnTalent[2]; // Get the max tier for this tree var maxTier = parseInt( maxTierArray[cTree] ); // Unlearning in the max tier is always allowed if( cTier != maxTier ) { var numPointsSpentInTier = new Array(); // For every tier up to the max tier, check if there are enough points left for( var i = maxTier; i >= 1; i-- ) { // Get the number of points spent in this tier numPointsSpentInTier[i] = getPointsSpentInTier( cTree, i ); // If we are removing points from THIS tier, calculate this too if( cTier == i ) numPointsSpentInTier[i] = numPointsSpentInTier[i] - parseInt( numPoints ); // Fix if below 0 if( numPointsSpentInTier[i] < 0 ) numPointsSpentInTier[i] = 0; }; for( var i = maxTier; i >= 1; i-- ) { // Calculate how many points we need for the talents in this tier var pointsRequired = parseInt( ( i - 1 ) * 5 ); var numPointsSpentSoFar = 0; var j = (i-1); while( j >= 1 ) { numPointsSpentSoFar += parseInt( numPointsSpentInTier[j] ); j--; } // Return if number of points not satisfied if( numPointsSpentSoFar < pointsRequired ) return; } } // Ok, all dependencies are satisfied... build the new tree with one less point spent :) CurrentSpec = (CurrentSpec.slice( 0, talentID )).toString() + ( parseInt( getSpentPointsInTalent( talentID ) ) - numPoints ).toString() + (CurrentSpec.slice( parseInt( talentID ) + 1 ) ).toString(); // Render the talent trees RenderOneTalent( talentID, true ); } function disableSelection( element ) { if( typeof element.onselectstart != "undefined" ) element.onselectstart = function() { return false; }; if( !element || !element.style ) return; element.onmousedown = function(){ return false; }; element.unselectable = 'on'; element.setStyle( 'MozUserSelect', 'none' ); element.setStyle( 'cursor', 'default' ) ; } var playerClass = "shaman"; var i = 0; var t = 0; var className = "Shaman Talents"; tree[i] = "Elemental"; i++; tree[i] = "Enhancement"; i++; tree[i] = "Restoration"; i++; i = 0; talent[i] = [0, "Convection", 5, 2, 1]; i++; talent[i] = [0, "Concussion", 5, 3, 1]; i++; talent[i] = [0, "Call of Flame", 3, 1, 2]; i++; talent[i] = [0, "Elemental Warding", 3, 2, 2]; i++; talent[i] = [0, "Elemental Devastation", 3, 3, 2]; i++; talent[i] = [0, "Reverberation", 5, 1, 3]; i++; talent[i] = [0, "Elemental Focus", 1, 2, 3]; i++; talent[i] = [0, "Elemental Fury", 5, 3, 3]; i++; talent[i] = [0, "Improved Fire Nova Totem", 2, 1, 4]; i++; talent[i] = [0, "Eye of the Storm", 3, 4, 4]; i++; talent[i] = [0, "Elemental Reach", 2, 1, 5]; i++; talent[i] = [0, "Call of Thunder", 1, 2, 5, [getTalentID("Elemental Focus"),1]]; i++; talent[i] = [0, "Unrelenting Storm", 3, 4, 5]; i++; talent[i] = [0, "Elemental Precision", 3, 1, 6]; i++; talent[i] = [0, "Lightning Mastery", 5, 3, 6, [getTalentID("Elemental Fury"),5]]; i++; talent[i] = [0, "Elemental Mastery", 1, 2, 7, [getTalentID("Call of Thunder"),1]]; i++; talent[i] = [0, "Storm, Earth and Fire", 3, 3, 7]; i++; talent[i] = [0, "Booming Echoes", 2, 1, 8]; i++; talent[i] = [0, "Elemental Oath", 2, 2, 8, [getTalentID("Elemental Mastery"),1]]; i++; talent[i] = [0, "Lightning Overload", 3, 3, 8]; i++; talent[i] = [0, "Astral Shift", 3, 1, 9]; i++; talent[i] = [0, "Totem of Wrath", 1, 2, 9]; i++; talent[i] = [0, "Lava Flows", 3, 3, 9]; i++; talent[i] = [0, "Shamanism", 5, 2, 10]; i++; talent[i] = [0, "Thunderstorm", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [1, "Enhancing Totems", 3, 1, 1]; i++; talent[i] = [1, "Earth's Grasp", 2, 2, 1]; i++; talent[i] = [1, "Ancestral Knowledge", 5, 3, 1]; i++; talent[i] = [1, "Guardian Totems", 2, 1, 2]; i++; talent[i] = [1, "Thundering Strikes", 5, 2, 2]; i++; talent[i] = [1, "Improved Ghost Wolf", 2, 3, 2]; i++; talent[i] = [1, "Improved Shields", 3, 4, 2]; i++; talent[i] = [1, "Elemental Weapons", 3, 1, 3]; i++; talent[i] = [1, "Shamanistic Focus", 1, 3, 3]; i++; talent[i] = [1, "Anticipation", 3, 4, 3]; i++; talent[i] = [1, "Flurry", 5, 2, 4, [getTalentID("Thundering Strikes"),5]]; i++; talent[i] = [1, "Toughness", 5, 3, 4]; i++; talent[i] = [1, "Improved Windfury Totem", 2, 1, 5]; i++; talent[i] = [1, "Spirit Weapons", 1, 2, 5]; i++; talent[i] = [1, "Mental Dexterity", 3, 3, 5]; i++; talent[i] = [1, "Unleashed Rage", 3, 1, 6]; i++; talent[i] = [1, "Weapon Mastery", 3, 3, 6]; i++; talent[i] = [1, "Frozen Power", 2, 4, 6]; i++; talent[i] = [1, "Dual Wield Specialization", 3, 1, 7, [getTalentID("Dual Wield"),1]]; i++; talent[i] = [1, "Dual Wield", 1, 2, 7, [getTalentID("Spirit Weapons"),1]]; i++; talent[i] = [1, "Stormstrike", 1, 3, 7]; i++; talent[i] = [1, "Static Shock", 3, 1, 8]; i++; talent[i] = [1, "Lava Lash", 1, 2, 8, [getTalentID("Dual Wield"),1]]; i++; talent[i] = [1, "Improved Stormstrike", 2, 3, 8, [getTalentID("Stormstrike"),1]]; i++; talent[i] = [1, "Mental Quickness", 3, 1, 9]; i++; talent[i] = [1, "Shamanistic Rage", 1, 2, 9]; i++; talent[i] = [1, "Earthen Power", 2, 3, 9]; i++; talent[i] = [1, "Maelstrom Weapon", 5, 2, 10]; i++; talent[i] = [1, "Feral Spirit", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [2, "Improved Healing Wave", 5, 2, 1]; i++; talent[i] = [2, "Totemic Focus", 5, 3, 1]; i++; talent[i] = [2, "Improved Reincarnation", 2, 1, 2]; i++; talent[i] = [2, "Healing Grace", 3, 2, 2]; i++; talent[i] = [2, "Tidal Focus", 5, 3, 2]; i++; talent[i] = [2, "Improved Water Shield", 3, 1, 3]; i++; talent[i] = [2, "Healing Focus", 3, 2, 3]; i++; talent[i] = [2, "Tidal Force", 1, 3, 3]; i++; talent[i] = [2, "Ancestral Healing", 3, 4, 3]; i++; talent[i] = [2, "Restorative Totems", 3, 2, 4]; i++; talent[i] = [2, "Tidal Mastery", 5, 3, 4]; i++; talent[i] = [2, "Healing Way", 3, 1, 5]; i++; talent[i] = [2, "Nature's Swiftness", 1, 3, 5]; i++; talent[i] = [2, "Focused Mind", 3, 4, 5]; i++; talent[i] = [2, "Purification", 5, 3, 6]; i++; talent[i] = [2, "Nature's Guardian", 5, 1, 7]; i++; talent[i] = [2, "Mana Tide Totem", 1, 2, 7, [getTalentID("Restorative Totems"),3]]; i++; talent[i] = [2, "Cleanse Spirit", 1, 3, 7, [getTalentID("Purification"),5]]; i++; talent[i] = [2, "Blessing of the Eternals", 2, 1, 8]; i++; talent[i] = [2, "Improved Chain Heal", 2, 2, 8]; i++; talent[i] = [2, "Nature's Blessing", 3, 3, 8]; i++; talent[i] = [2, "Ancestral Awakening", 3, 1, 9]; i++; talent[i] = [2, "Earth Shield", 1, 2, 9]; i++; talent[i] = [2, "Improved Earth Shield", 2, 3, 9, [getTalentID("Earth Shield"),1]]; i++; talent[i] = [2, "Tidal Waves", 5, 2, 10]; i++; talent[i] = [2, "Riptide", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; i = 0; // Name: Convection rank[i] = [ "Reduces the mana cost of your Shock, Lightning Bolt, Chain Lightning, Lava Burst, and Wind Shear spells by 2%.", "Reduces the mana cost of your Shock, Lightning Bolt, Chain Lightning, Lava Burst, and Wind Shear spells by 4%.", "Reduces the mana cost of your Shock, Lightning Bolt, Chain Lightning, Lava Burst, and Wind Shear spells by 6%.", "Reduces the mana cost of your Shock, Lightning Bolt, Chain Lightning, Lava Burst, and Wind Shear spells by 8%.", "Reduces the mana cost of your Shock, Lightning Bolt, Chain Lightning, Lava Burst, and Wind Shear spells by 10%." ]; i++; // Name: Concussion rank[i] = [ "Increases the damage done by your Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shock spells by 1%.", "Increases the damage done by your Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shock spells by 2%.", "Increases the damage done by your Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shock spells by 3%.", "Increases the damage done by your Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shock spells by 4%.", "Increases the damage done by your Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shock spells by 5%." ]; i++; // Name: Call of Flame rank[i] = [ "Increases the damage done by your Fire Totems by 5%, and damage done by your Lava Burst spell by 2%.", "Increases the damage done by your Fire Totems by 10%, and damage done by your Lava Burst spell by 4%.", "Increases the damage done by your Fire Totems by 15%, and damage done by your Lava Burst spell by 6%." ]; i++; // Name: Elemental Warding rank[i] = [ "Reduces all damage taken by 2%.", "Reduces all damage taken by 4%.", "Reduces all damage taken by 6%." ]; i++; // Name: Elemental Devastation rank[i] = [ "Your non-periodic offensive spell crits will increase your chance to get a critical strike with melee attacks by 3% for 10 sec.", "Your non-periodic offensive spell crits will increase your chance to get a critical strike with melee attacks by 6% for 10 sec.", "Your non-periodic offensive spell crits will increase your chance to get a critical strike with melee attacks by 9% for 10 sec." ]; i++; // Name: Reverberation rank[i] = [ "Reduces the cooldown of your Shock spells and Wind Shear by 0.2 sec.", "Reduces the cooldown of your Shock spells and Wind Shear by 0.4 sec.", "Reduces the cooldown of your Shock spells and Wind Shear by 0.6 sec.", "Reduces the cooldown of your Shock and Wind Shear spells by 0.8 sec.", "Reduces the cooldown of your Shock and Wind Shear spells by 1 sec." ]; i++; // Name: Elemental Focus rank[i] = [ "After landing a non-periodic critical strike with a Fire, Frost, or Nature damage spell, you enter a Clearcasting state.  The Clearcasting state reduces the mana cost of your next 2 damage or healing spells by 40%." ]; i++; // Name: Elemental Fury rank[i] = [ "Increases the critical strike damage bonus of your Searing, Magma, and Fire Nova Totems and your Fire, Frost, and Nature spells by 20%.", "Increases the critical strike damage bonus of your Searing, Magma, and Fire Nova Totems and your Fire, Frost, and Nature spells by 40%.", "Increases the critical strike damage bonus of your Searing, Magma, and Fire Nova Totems and your Fire, Frost, and Nature spells by 60%.", "Increases the critical strike damage bonus of your Searing, Magma, and Fire Nova Totems and your Fire, Frost, and Nature spells by 80%.", "Increases the critical strike damage bonus of your Searing, Magma, and Fire Nova Totems and your Fire, Frost, and Nature spells by 100%." ]; i++; // Name: Improved Fire Nova Totem rank[i] = [ "Increases the damage done by your Fire Nova Totem by 10%, and your Fire Nova totem has a 50% chance to stun all targets damaged by your Fire Nova Totem for 2 sec.", "Increases the damage done by your Fire Nova Totem by 20%, and your Fire Nova totem has a 100% chance to stun all targets damaged by your Fire Nova Totem for 2 sec." ]; i++; // Name: Eye of the Storm rank[i] = [ "Reduces the pushback suffered from damaging attacks while casting Lightning Bolt, Chain Lightning, Lava Burst and Hex spells by 23%.", "Reduces the pushback suffered from damaging attacks while casting Lightning Bolt, Chain Lightning, Lava Burst and Hex spells by 46%.", "Reduces the pushback suffered from damaging attacks while casting Lightning Bolt, Chain Lightning, Lava Burst and Hex spells by 70%." ]; i++; // Name: Elemental Reach rank[i] = [ "Increases the range of your Lightning Bolt, Chain Lightning and Lava Burst spells by 3 yards, increases the radius of your Thunderstorm spell by 10%, and increases the range of your Flame Shock by 7 yards.", "Increases the range of your Lightning Bolt, Chain Lightning and Lava Burst spells by 6 yards, increases the radius of your Thunderstorm spell by 20%, and increases the range of your Flame Shock by 15 yards." ]; i++; // Name: Call of Thunder rank[i] = [ "Increases the critical strike chance of your Lightning Bolt, Chain Lightning and Thunderstorm spells by an additional 5%." ]; i++; // Name: Unrelenting Storm rank[i] = [ "Regenerate mana equal to 4% of your Intellect every 5 sec, even while casting.", "Regenerate mana equal to 8% of your Intellect every 5 sec, even while casting.", "Regenerate mana equal to 12% of your Intellect every 5 sec, even while casting." ]; i++; // Name: Elemental Precision rank[i] = [ "Increases your chance to hit with Fire, Frost and Nature spells by 1% and reduces the threat caused by Fire, Frost and Nature spells by 10%.", "Increases your chance to hit with Fire, Frost and Nature spells by 2% and reduces the threat caused by Fire, Frost and Nature spells by 20%.", "Increases your chance to hit with Fire, Frost and Nature spells by 3% and reduces the threat caused by Fire, Frost and Nature spells by 30%." ]; i++; // Name: Lightning Mastery rank[i] = [ "Reduces the cast time of your Lightning Bolt, Chain Lightning, and Lava Burst spells by 0.1 sec.", "Reduces the cast time of your Lightning Bolt, Chain Lightning, and Lava Burst spells by 0.2 sec.", "Reduces the cast time of your Lightning Bolt, Chain Lightning, and Lava Burst spells by 0.3 sec.", "Reduces the cast time of your Lightning Bolt, Chain Lightning, and Lava Burst spells by 0.4 sec.", "Reduces the cast time of your Lightning Bolt, Chain Lightning, and Lava Burst spells by 0.5 sec." ]; i++; // Name: Elemental Mastery rank[i] = [ "When activated, your next Lightning Bolt, Chain Lightning or Lava Burst spell becomes an instant cast spell. In addition, your Fire, Frost, and Nature damage spells have a 15% increased critical strike chance for 15 sec. Elemental Mastery shares a cooldown with Nature\'s Swiftness." ]; i++; // Name: Storm, Earth and Fire rank[i] = [ "Reduces the cooldown of your Chain Lightning spell by .75 sec, your Earthbind Totem also has a 33% chance to root targets for 5 sec when cast and the periodic damage done by your Flame Shock is increased by 20%.", "Reduces the cooldown of your Chain Lightning spell by 1.5 sec, your Earthbind Totem also has a 66% chance to root targets for 5 sec when cast and the periodic damage done by your Flame Shock is increased by 40%.", "Reduces the cooldown of your Chain Lightning spell by 2.5 sec, your Earthbind Totem also has a 100% chance to root targets for 5 sec when cast and the periodic damage done by your Flame Shock is increased by 60%." ]; i++; // Name: Booming Echoes rank[i] = [ "Reduces the cooldown of your Flame Shock and Frost Shock spells by an additional 1 sec., and increases the direct damage done by your Flame Shock and Frost Shock spells by an additional 10%.", "Reduces the cooldown of your Flame Shock and Frost Shock spells by an additional 2 sec., and increases the direct damage done by your Flame Shock and Frost Shock spells by an additional 20%." ]; i++; // Name: Elemental Oath rank[i] = [ "Your non-periodic spell critical strikes grant your party or raid members within 100 yards Elemental Oath, increasing spell critical strike chance by 3%.  In addition, while Clearcasting from Elemental Focus is active, you deal 5% more spell damage.  Lasts 15 sec.", "Your non-periodic spell critical strikes grant your party or raid members within 100 yards Elemental Oath, increasing spell critical strike chance by 5%.  In addition, while Clearcasting from Elemental Focus is active, you deal 10% more spell damage.  Lasts 15 sec." ]; i++; // Name: Lightning Overload rank[i] = [ "Gives your Lightning Bolt and Chain Lightning spells a 11% chance to cast a second, similar spell on the same target at no additional cost that causes half damage and no threat.", "Gives your Lightning Bolt and Chain Lightning spells a 22% chance to cast a second, similar spell on the same target at no additional cost that causes half damage and no threat.", "Gives your Lightning Bolt and Chain Lightning spells a 33% chance to cast a second, similar spell on the same target at no additional cost that causes half damage and no threat." ]; i++; // Name: Astral Shift rank[i] = [ "When stunned, feared or silenced you  shift into the Astral Plane reducing all damage taken by 10% for the duration of the stun, fear or silence effect.", "When stunned, feared or silenced you  shift into the Astral Plane reducing all damage taken by 20% for the duration of the stun, fear or silence effect.", "When stunned, feared or silenced you shift into the Astral Plane reducing all damage taken by 30% for the duration of the stun, fear or silence effect." ]; i++; // Name: Totem of Wrath rank[i] = [ "Summons a Totem of Wrath with 5 health at the feet of the caster.  The totem increases spell power by 100 for all party and raid members, and increases the critical strike chance of all attacks by 3% against all enemies within 40 yards.  Lasts 5 min." ]; i++; // Name: Lava Flows rank[i] = [ "Increases the critical strike damage bonus of your Lava Burst spell by an additional 6%, and when your Flame Shock is dispelled your spell casting speed is increased by 10% for 6 sec.", "Increases the critical strike damage bonus of your Lava Burst spell by an additional 12%, and when your Flame Shock is dispelled your spell casting speed is increased by 20% for 6 sec.", "Increases the critical strike damage bonus of your Lava Burst spell by an additional 24%, and when your Flame Shock is dispelled your spell casting speed is increased by 30% for 6 sec." ]; i++; // Name: Shamanism rank[i] = [ "Your Lightning Bolt spell gains an additional 2% and your Lava Burst gains an additional 4% of your bonus damage effects.", "Your Lightning Bolt spell gains an additional 4% and your Lava Burst gains an additional 8% of your bonus damage effects.", "Your Lightning Bolt spell gains an additional 6% and your Lava Burst gains an additional 12% of your bonus damage effects.", "Your Lightning Bolt spell gains an additional 8% and your Lava Burst gains an additional 16% of your bonus damage effects.", "Your Lightning Bolt spell gains an additional 10% and your Lava Burst gains an additional 20% of your bonus damage effects." ]; i++; // Name: Thunderstorm rank[i] = [ "You call down a bolt of lightning, energizing you and damaging nearby enemies within 10 yards. Restores 8% mana to you and deals 551 to 629 Nature damage to all nearby enemies, knocking them back 20 yards. This spell is usable while stunned." ]; i++; // Name: Enhancing Totems rank[i] = [ "Increases the effect of your Strength of Earth and Flametongue Totems by 5%.", "Increases the effect of your Strength of Earth and Flametongue Totems by 10%.", "Increases the effect of your Strength of Earth and Flametongue Totems by 15%." ]; i++; // Name: Earth's Grasp rank[i] = [ "Increases the health of your Stoneclaw Totem by 25% and the radius of your Earthbind Totem by 10%.", "Increases the health of your Stoneclaw Totem by 50% and the radius of your Earthbind Totem by 20%." ]; i++; // Name: Ancestral Knowledge rank[i] = [ "Increases your Intellect by 2%.", "Increases your Intellect by 4%.", "Increases your Intellect by 6%.", "Increases your Intellect by 8%.", "Increases your Intellect by 10%." ]; i++; // Name: Guardian Totems rank[i] = [ "Increases the amount of armor increased by your Stoneskin Totem by 10% and reduces the cooldown of your Grounding Totem by 1 sec.", "Increases the amount of armor increased by your Stoneskin Totem by 20% and reduces the cooldown of your Grounding Totem by 2 sec." ]; i++; // Name: Thundering Strikes rank[i] = [ "Improves your chance to get a critical strike with all spells and attacks by 1%.", "Improves your chance to get a critical strike with all spells and attacks by 2%.", "Improves your chance to get a critical strike with all spells and attacks by 3%.", "Improves your chance to get a critical strike with all spells and attacks by 4%.", "Improves your chance to get a critical strike with all spells and attacks by 5%." ]; i++; // Name: Improved Ghost Wolf rank[i] = [ "Reduces the cast time of your Ghost Wolf spell by 1 sec.", "Reduces the cast time of your Ghost Wolf spell by 2 sec." ]; i++; // Name: Improved Shields rank[i] = [ "Increases the damage done by your Lightning Shield orbs by 5%, increases the amount of mana gained from your Water Shield orbs by 5% and increases the amount of healing done by your Earth Shield orbs by 5%.", "Increases the damage done by your Lightning Shield orbs by 10%, increases the amount of mana gained from your Water Shield orbs by 10% and increases the amount of healing done by your Earth Shield orbs by 10%.", "Increases the damage done by your Lightning Shield orbs by 15%, increases the amount of mana gained from your Water Shield orbs by 15% and increases the amount of healing done by your Earth Shield orbs by 15%." ]; i++; // Name: Elemental Weapons rank[i] = [ "Increases the damage caused by your Windfury Weapon effect by 13%  increases the spell damage on your Flametongue Weapon by 10% and increases the bonus healing on your Earthliving Weapon by 10%.", "Increases the damage caused by your Windfury Weapon effect by 27%  increases the spell damage on your Flametongue Weapon by 20% and increases the bonus healing on your Earthliving Weapon by 20%.", "Increases the damage caused by your Windfury Weapon effect by 40%  increases the spell damage on your Flametongue Weapon by 30% and increases the bonus healing on your Earthliving Weapon by 30%." ]; i++; // Name: Shamanistic Focus rank[i] = [ "Reduces the mana cost of your Shock spells by 45%." ]; i++; // Name: Anticipation rank[i] = [ "Increases your chance to dodge by an additional 1%, and reduces the duration of all Disarm effects used against you by 16%. This does not stack with other Disarm duration reducing effects.", "Increases your chance to dodge by an additional 2%, and reduces the duration of all Disarm effects used against you by 25%. This does not stack with other Disarm duration reducing effects.", "Increases your chance to dodge by an additional 3%, and reduces the duration of all Disarm effects used against you by 50%. This does not stack with other Disarm duration reducing effects." ]; i++; // Name: Flurry rank[i] = [ "Increases your attack speed by 5% for your next 3 swings after dealing a critical strike.", "Increases your attack speed by 10% for your next 3 swings after dealing a critical strike.", "Increases your attack speed by 15% for your next 3 swings after dealing a critical strike.", "Increases your attack speed by 20% for your next 3 swings after dealing a critical strike.", "Increases your attack speed by 25% for your next 3 swings after dealing a critical strike." ]; i++; // Name: Toughness rank[i] = [ "Increases your Stamina by 2%, and reduces the duration of movement slowing effects on you by 6%.", "Increases your Stamina by 4%, and reduces the duration of movement slowing effects on you by 12%.", "Increases your Stamina by 6%, and reduces the duration of movement slowing effects on you by 18%.", "Increases your Stamina by 8%, and reduces the duration of movement slowing effects on you by 24%.", "Increases your Stamina by 10%, and reduces the duration of movement slowing effects on you by 30%." ]; i++; // Name: Improved Windfury Totem rank[i] = [ "Increases the melee haste granted by your Windfury totem by 2%.", "Increases the melee haste granted by your Windfury totem by 4%." ]; i++; // Name: Spirit Weapons rank[i] = [ "Gives a chance to parry enemy melee attacks and reduces all threat generated by 30%." ]; i++; // Name: Mental Dexterity rank[i] = [ "Increases your Attack Power by 33% of your Intellect.", "Increases your Attack Power by 66% of your Intellect.", "Increases your Attack Power by 100% of your Intellect." ]; i++; // Name: Unleashed Rage rank[i] = [ "Increases your expertise by 3, and causes your critical hits with melee attacks to increase all party and raid members\' attack power by 4% if within 45 yards of the Shaman.  Lasts 10 sec.", "Increases your expertise by 6, and causes your critical hits with melee attacks to increase all party and raid members\' attack power by 7% if within 45 yards of the Shaman.  Lasts 10 sec.", "Increases your expertise by 9, and causes your critical hits with melee attacks to increase all party and raid members\' attack power by 10% if within 45 yards of the Shaman.  Lasts 10 sec." ]; i++; // Name: Weapon Mastery rank[i] = [ "Increases the damage you deal with all weapons by 4%.", "Increases the damage you deal with all weapons by 7%.", "Increases the damage you deal with all weapons by 10%." ]; i++; // Name: Frozen Power rank[i] = [ "Increases the damage done by your Lightning Bolt, Chain Lightning, Lava Lash and Shock spells by 5% on targets afflicted by your Frostbrand Attack effect, and your Frost Shock has a 50% chance to root the target in ice for 5 sec. when used on targets at or further than 15 yards from you.", "Increases the damage done by your Lightning Bolt, Chain Lightning, Lava Lash and Shock spells by 10% on targets afflicted by your Frostbrand Attack effect, and your Frost Shock has a 100% chance to root the target in ice for 5 sec. when used on targets at or further than 15 yards from you." ]; i++; // Name: Dual Wield Specialization rank[i] = [ "Increases your chance to hit while dual wielding by an additional 2%.", "Increases your chance to hit while dual wielding by an additional 4%.", "Increases your chance to hit while dual wielding by an additional 6%." ]; i++; // Name: Dual Wield rank[i] = [ "Allows one-hand and off-hand weapons to be equipped in the off-hand." ]; i++; // Name: Stormstrike rank[i] = [ "Instantly attack with both weapons.  In addition, the next 4 sources of Nature damage dealt to the target from the Shaman are increased by 20%. Lasts 12 sec." ]; i++; // Name: Static Shock rank[i] = [ "You have a 2% chance to hit your target with a Lightning Shield Orb charge when you deal melee attacks and abilities, and increases the number of charges of your Lightning Shield by 2.", "You have a 4% chance to hit your target with a Lightning Shield Orb charge when you deal melee attacks and abilities, and increases the number of charges of your Lightning Shield by 4.", "You have a 6% chance to hit your target with a Lightning Shield Orb charge when you deal melee attacks and abilities, and increases the number of charges of your Lightning Shield by 6." ]; i++; // Name: Lava Lash rank[i] = [ "You charge your off-hand weapon with lava, instantly dealing 100% off-hand Weapon damage. Damage is increased by 25% if your off-hand weapon is enchanted with Flametongue." ]; i++; // Name: Improved Stormstrike rank[i] = [ "When you Stormstrike, you have a 50% chance to immediately grant you 20% of your base mana.", "When you Stormstrike, you have a 100% chance to immediately grant you 20% of your base mana." ]; i++; // Name: Mental Quickness rank[i] = [ "Reduces the mana cost of your instant cast Shaman spells by 2% and increases your spell power by an amount equal to 10% of your attack power.", "Reduces the mana cost of your instant cast Shaman spells by 4% and increases your spell power by an amount equal to 20% of your attack power.", "Reduces the mana cost of your instant cast Shaman spells by 6% and increases your spell power by an amount equal to 30% of your attack power." ]; i++; // Name: Shamanistic Rage rank[i] = [ "Reduces all damage taken by 30% and gives your successful melee attacks a chance to regenerate mana equal to 15% of your attack power. This spell is usable while stunned. Lasts 15 sec." ]; i++; // Name: Earthen Power rank[i] = [ "Your Earthbind Totem has a 50% chance to also remove all snare effects and make you and nearby friendly targets immune to them for 5 sec when it pulses.", "Your Earthbind Totem has a 100% chance to also remove all snare effects and make you and nearby friendly targets immune to them for 5 sec when it pulses." ]; i++; // Name: Maelstrom Weapon rank[i] = [ "When you deal damage with a melee weapon, you have a chance to reduce the cast time of your next Lightning Bolt, Chain Lightning, Lesser Healing Wave, Healing Wave, Chain Heal, or Hex spell by 20%. Stacks up to 5 times. Lasts 30 sec.", "When you deal damage with a melee weapon, you have a chance to reduce the cast time of your next Lightning Bolt, Chain Lightning, Lesser Healing Wave, Healing Wave, Chain Heal, or Hex spell by 20%. Stacks up to 5 times. Lasts 30 sec.", "When you deal damage with a melee weapon, you have a chance to reduce the cast time of your next Lightning Bolt, Chain Lightning, Lesser Healing Wave, Healing Wave, Chain Heal, or Hex spell by 20%. Stacks up to 5 times. Lasts 30 sec.", "When you deal damage with a melee weapon, you have a chance to reduce the cast time of your next Lightning Bolt, Chain Lightning, Lesser Healing Wave, Healing Wave, Chain Heal, or Hex spell by 20%. Stacks up to 5 times. Lasts 30 sec.", "When you deal damage with a melee weapon, you have a chance to reduce the cast time of your next Lightning Bolt, Chain Lightning, Lesser Healing Wave, Healing Wave, Chain Heal, or Hex spell by 20%. Stacks up to 5 times. Lasts 30 sec." ]; i++; // Name: Feral Spirit rank[i] = [ "Summons two Spirit Wolves under the command of the Shaman, lasting 45 sec." ]; i++; // Name: Improved Healing Wave rank[i] = [ "Reduces the casting time of your Healing Wave spell by 0.1 sec.", "Reduces the casting time of your Healing Wave spell by 0.2 sec.", "Reduces the casting time of your Healing Wave spell by 0.3 sec.", "Reduces the casting time of your Healing Wave spell by 0.4 sec.", "Reduces the casting time of your Healing Wave spell by 0.5 sec." ]; i++; // Name: Totemic Focus rank[i] = [ "Reduces the mana cost of your totems by 5%.", "Reduces the mana cost of your totems by 10%.", "Reduces the mana cost of your totems by 15%.", "Reduces the mana cost of your totems by 20%.", "Reduces the mana cost of your totems by 25%." ]; i++; // Name: Improved Reincarnation rank[i] = [ "Reduces the cooldown of your Reincarnation spell by 10 min and increases the amount of health and mana you reincarnate with by an additional 10%.", "Reduces the cooldown of your Reincarnation spell by 20 min and increases the amount of health and mana you reincarnate with by an additional 20%." ]; i++; // Name: Healing Grace rank[i] = [ "Reduces the threat generated by your healing spells by 5% and reduces the chance your helpful spells and damage over time effects will be dispelled by 10%.", "Reduces the threat generated by your healing spells by 10% and reduces the chance your helpful spells and damage over time effects will be dispelled by 20%.", "Reduces the threat generated by your healing spells by 15% and reduces the chance your helpful spells and damage over time effects will be dispelled by 30%." ]; i++; // Name: Tidal Focus rank[i] = [ "Reduces the mana cost of your healing spells by 1%.", "Reduces the mana cost of your healing spells by 2%.", "Reduces the mana cost of your healing spells by 3%.", "Reduces the mana cost of your healing spells by 4%.", "Reduces the mana cost of your healing spells by 5%." ]; i++; // Name: Improved Water Shield rank[i] = [ "You have a 33% chance to instantly gain mana as if you consumed a Water Shield Orb when you gain a critical effect from your Healing Wave or Riptide spells, a 19.8% chance when you gain a critical effect from your Lesser Healing Wave spell, and a 9.9% chance when you gain a critical effect from your Chain Heal spell.", "You have a 66% chance to instantly gain mana as if you consumed a Water Shield Orb when you gain a critical effect from your Healing Wave or Riptide spells, a 39.6% chance when you gain a critical effect from your Lesser Healing Wave spell, and a 19.8% chance when you gain a critical effect from your Chain Heal spell.", "You have a 100% chance to instantly gain mana as if you consumed a Water Shield Orb when you gain a critical effect from your Healing Wave or Riptide spells, a 60% chance when you gain a critical effect from your Lesser Healing Wave spell, and a 30% chance when you gain a critical effect from your Chain Heal spell." ]; i++; // Name: Healing Focus rank[i] = [ "Reduces the pushback suffered from damaging attacks while casting any Shaman healing spell by 23%.", "Reduces the pushback suffered from damaging attacks while casting any Shaman healing spell by 46%.", "Reduces the pushback suffered from damaging attacks while casting any Shaman healing spell by 70%." ]; i++; // Name: Tidal Force rank[i] = [ "Increases the critical effect chance of your Healing Wave, Lesser Healing Wave and Chain Heal by 60%. Each critical heal reduces the chance by 20%. Lasts 20 sec." ]; i++; // Name: Ancestral Healing rank[i] = [ "Reduces your target\'s physical damage taken by 3% for 15 sec after getting a critical effect from one of your healing spells.", "Reduces your target\'s physical damage taken by 7% for 15 sec after getting a critical effect from one of your healing spells.", "Reduces your target\'s physical damage taken by 10% for 15 sec after getting a critical effect from one of your healing spells." ]; i++; // Name: Restorative Totems rank[i] = [ "Increases the effect of your Mana Spring Totem by 7%, and increases the amount healed by your Healing Stream Totem by 15%", "Increases the effect of your Mana Spring Totem by 12%, and increases the amount healed by your Healing Stream Totem by 30%", "Increases the effect of your Mana Spring Totem by 20%, and increases the amount healed by your Healing Stream Totem by 45%" ]; i++; // Name: Tidal Mastery rank[i] = [ "Increases the critical effect chance of your healing and lightning spells by 1%.", "Increases the critical effect chance of your healing and lightning spells by 2%.", "Increases the critical effect chance of your healing and lightning spells by 3%.", "Increases the critical effect chance of your healing and lightning spells by 4%.", "Increases the critical effect chance of your healing and lightning spells by 5%." ]; i++; // Name: Healing Way rank[i] = [ "Increases the amount healed by your Healing Wave spell by 8%.", "Increases the amount healed by your Healing Wave spell by 16%.", "Increases the amount healed by your Healing Wave spell by 25%." ]; i++; // Name: Nature's Swiftness rank[i] = [ "When activated, your next Nature spell with a base casting time less than 10 sec. becomes an instant cast spell. Nature\'s Swiftness shares a cooldown with Elemental Mastery." ]; i++; // Name: Focused Mind rank[i] = [ "Reduces the duration of any Silence or Interrupt effects used against the Shaman by 10%. This effect does not stack with other similar effects.", "Reduces the duration of any Silence or Interrupt effects used against the Shaman by 20%. This effect does not stack with other similar effects.", "Reduces the duration of any Silence or Interrupt effects used against the Shaman by 30%. This effect does not stack with other similar effects." ]; i++; // Name: Purification rank[i] = [ "Increases the effectiveness of your healing spells by 2%.", "Increases the effectiveness of your healing spells by 4%.", "Increases the effectiveness of your healing spells by 6%.", "Increases the effectiveness of your healing spells by 8%.", "Increases the effectiveness of your healing spells by 10%." ]; i++; // Name: Nature's Guardian rank[i] = [ "Whenever a damaging attack is taken that reduces you below 30% health, you have a 100% chance to increase your maximum health by 3% for 10 sec and reduce your threat level on that target.  30 second cooldown.", "Whenever a damaging attack is taken that reduces you below 30% health, you have a 100% chance to increase your maximum health by 6% for 10 sec and reduce your threat level on that target.  30 second cooldown.", "Whenever a damaging attack is taken that reduces you below 30% health, you have a 100% chance to increase your maximum health by 9% for 10 sec and reduce your threat level on that target.  30 second cooldown.", "Whenever a damaging attack is taken that reduces you below 30% health, you have a 100% chance to increase your maximum health by 12% for 10 sec and reduce your threat level on that target.  30 second cooldown.", "Whenever a damaging attack is taken that reduces you below 30% health, you have a 100% chance to increase your maximum health by 15% for 10 sec and reduce your threat level on that target.  30 second cooldown." ]; i++; // Name: Mana Tide Totem rank[i] = [ "Summons a Mana Tide Totem with 10% of the caster\'s health at the feet of the caster for [13 sec - 1] sec that restores 6% of total mana every 3 seconds to group members within 30 yards." ]; i++; // Name: Cleanse Spirit rank[i] = [ "Cleanse the spirit of a friendly target, removing 1 poison effect, 1 disease effect, and 1 curse effect." ]; i++; // Name: Blessing of the Eternals rank[i] = [ "Increases the critical effect chance of your spells by 2%, and increases the chance to apply the Earthliving heal over time effect on the target by 40% when they are at or under 35% total health.", "Increases the critical effect chance of your spells by 4%, and increases the chance to apply the Earthliving heal over time effect on the target by 80% when they are at or under 35% total health." ]; i++; // Name: Improved Chain Heal rank[i] = [ "Increases the amount healed by your Chain Heal spell by 10%.", "Increases the amount healed by your Chain Heal spell by 20%." ]; i++; // Name: Nature's Blessing rank[i] = [ "Increases your healing by an amount equal to 5% of your Intellect.", "Increases your healing by an amount equal to 10% of your Intellect.", "Increases your healing by an amount equal to 15% of your Intellect." ]; i++; // Name: Ancestral Awakening rank[i] = [ "When you critically heal with your Healing Wave, Lesser Healing Wave or Riptide you summon an Ancestral spirit to aid you, instantly healing the lowest percentage health friendly party or raid target within 40 yards for 10% of the amount healed.", "When you critically heal with your Healing Wave, Lesser Healing Wave or Riptide you summon an Ancestral spirit to aid you, instantly healing the lowest percentage health friendly party or raid target within 40 yards for 20% of the amount healed.", "When you critically heal with your Healing Wave, Lesser Healing Wave or Riptide you summon an Ancestral spirit to aid you, instantly healing the lowest percentage health friendly party or raid target within 40 yards for 30% of the amount healed." ]; i++; // Name: Earth Shield rank[i] = [ "Protects the target with an earthen shield, reducing casting or channeling time lost when damaged by 30%  and causing attacks to heal the shielded target for 150.  This effect can only occur once every few seconds.  6 charges.  Lasts 10 min.  Earth Shield can only be placed on one target at a time and only one Elemental Shield can be active on a target at a time." ]; i++; // Name: Improved Earth Shield rank[i] = [ "Increases the amount of charges for your Earth Shield by 1, and increases the healing done by your Earth Shield by 5%.", "Increases the amount of charges for your Earth Shield by 2, and increases the healing done by your Earth Shield by 10%." ]; i++; // Name: Tidal Waves rank[i] = [ "When you cast Chain Heal or Riptide, you have a 20% chance to lower the cast time of your Healing Wave spell by 30% and increase the critical effect chance of your Lesser Healing Wave spell by 25%, until two such spells have been cast. In addition, your Healing Wave gains an additional 4% of your bonus healing effects and your Lesser Healing Wave gains an additional 2% of your bonus healing effects.", "When you cast Chain Heal or Riptide, you have a 40% chance to lower the cast time of your Healing Wave spell by 30% and increase the critical effect chance of your Lesser Healing Wave spell by 25%, until two such spells have been cast. In addition, your Healing Wave gains an additional 8% of your bonus healing effects and your Lesser Healing Wave gains an additional 4% of your bonus healing effects.", "When you cast Chain Heal or Riptide, you have a 60% chance to lower the cast time of your Healing Wave spell by 30% and increase the critical effect chance of your Lesser Healing Wave spell by 25%, until two such spells have been cast. In addition, your Healing Wave gains an additional 12% of your bonus healing effects and your Lesser Healing Wave gains an additional 6% of your bonus healing effects.", "When you cast Chain Heal or Riptide, you have a 80% chance to lower the cast time of your Healing Wave spell by 30% and increase the critical effect chance of your Lesser Healing Wave spell by 25%, until two such spells have been cast. In addition, your Healing Wave gains an additional 16% of your bonus healing effects and your Lesser Healing Wave gains an additional 8% of your bonus healing effects.", "When you cast Chain Heal or Riptide, you have a 100% chance to lower the cast time of your Healing Wave spell by 30% and increase the critical effect chance of your Lesser Healing Wave spell by 25%, until two such spells have been cast. In addition, your Healing Wave gains an additional 20% of your bonus healing effects and your Lesser Healing Wave gains an additional 10% of your bonus healing effects." ]; i++; // Name: Riptide rank[i] = [ "Heals a friendly target for 639 to 691 and another 665 over 15 sec.  Your next Chain Heal cast on that primary target within 15 sec will consume the healing over time effect and increase the amount of the Chain Heal by 25%." ]; i++;