/* 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 = "priest"; var i = 0; var t = 0; var className = "Priest Talents"; tree[i] = "Discipline"; i++; tree[i] = "Holy"; i++; tree[i] = "Shadow"; i++; i = 0; talent[i] = [0, "Unbreakable Will", 5, 2, 1]; i++; talent[i] = [0, "Twin Disciplines", 5, 3, 1]; i++; talent[i] = [0, "Silent Resolve", 3, 1, 2]; i++; talent[i] = [0, "Improved Inner Fire", 3, 2, 2]; i++; talent[i] = [0, "Improved Power Word: Fortitude", 2, 3, 2]; i++; talent[i] = [0, "Martyrdom", 2, 4, 2]; i++; talent[i] = [0, "Meditation", 3, 1, 3]; i++; talent[i] = [0, "Inner Focus", 1, 2, 3]; i++; talent[i] = [0, "Improved Power Word: Shield", 3, 3, 3]; i++; talent[i] = [0, "Absolution", 3, 1, 4]; i++; talent[i] = [0, "Mental Agility", 3, 2, 4]; i++; talent[i] = [0, "Improved Mana Burn", 2, 4, 4]; i++; talent[i] = [0, "Reflective Shield", 2, 1, 5]; i++; talent[i] = [0, "Mental Strength", 5, 2, 5]; i++; talent[i] = [0, "Soul Warding", 1, 3, 5, [getTalentID("Improved Power Word: Shield"),3]]; i++; talent[i] = [0, "Focused Power", 2, 1, 6]; i++; talent[i] = [0, "Enlightenment", 3, 3, 6]; i++; talent[i] = [0, "Focused Will", 3, 1, 7]; i++; talent[i] = [0, "Power Infusion", 1, 2, 7, [getTalentID("Mental Strength"),5]]; i++; talent[i] = [0, "Improved Flash Heal", 3, 3, 7]; i++; talent[i] = [0, "Renewed Hope", 2, 1, 8]; i++; talent[i] = [0, "Rapture", 3, 2, 8]; i++; talent[i] = [0, "Aspiration", 2, 3, 8]; i++; talent[i] = [0, "Divine Aegis", 3, 1, 9]; i++; talent[i] = [0, "Pain Suppression", 1, 2, 9]; i++; talent[i] = [0, "Grace", 2, 3, 9]; i++; talent[i] = [0, "Borrowed Time", 5, 2, 10]; i++; talent[i] = [0, "Penance", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [1, "Healing Focus", 2, 1, 1]; i++; talent[i] = [1, "Improved Renew", 3, 2, 1]; i++; talent[i] = [1, "Holy Specialization", 5, 3, 1]; i++; talent[i] = [1, "Spell Warding", 5, 2, 2]; i++; talent[i] = [1, "Divine Fury", 5, 3, 2]; i++; talent[i] = [1, "Desperate Prayer", 1, 1, 3]; i++; talent[i] = [1, "Blessed Recovery", 3, 2, 3]; i++; talent[i] = [1, "Inspiration", 3, 4, 3]; i++; talent[i] = [1, "Holy Reach", 2, 1, 4]; i++; talent[i] = [1, "Improved Healing", 3, 2, 4]; i++; talent[i] = [1, "Searing Light", 2, 3, 4, [getTalentID("Divine Fury"),5]]; i++; talent[i] = [1, "Healing Prayers", 2, 1, 5]; i++; talent[i] = [1, "Spirit of Redemption", 1, 2, 5]; i++; talent[i] = [1, "Spiritual Guidance", 5, 3, 5]; i++; talent[i] = [1, "Surge of Light", 2, 1, 6]; i++; talent[i] = [1, "Spiritual Healing", 5, 3, 6]; i++; talent[i] = [1, "Holy Concentration", 3, 1, 7]; i++; talent[i] = [1, "Lightwell", 1, 2, 7, [getTalentID("Spirit of Redemption"),1]]; i++; talent[i] = [1, "Blessed Resilience", 3, 3, 7]; i++; talent[i] = [1, "Body and Soul", 2, 1, 8]; i++; talent[i] = [1, "Empowered Healing", 5, 2, 8]; i++; talent[i] = [1, "Serendipity", 3, 3, 8]; i++; talent[i] = [1, "Empowered Renew", 3, 1, 9]; i++; talent[i] = [1, "Circle of Healing", 1, 2, 9]; i++; talent[i] = [1, "Test of Faith", 3, 3, 9]; i++; talent[i] = [1, "Divine Providence", 5, 2, 10]; i++; talent[i] = [1, "Guardian Spirit", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [2, "Spirit Tap", 3, 1, 1]; i++; talent[i] = [2, "Improved Spirit Tap", 2, 2, 1, [getTalentID("Spirit Tap"),3]]; i++; talent[i] = [2, "Darkness", 5, 3, 1]; i++; talent[i] = [2, "Shadow Affinity", 3, 1, 2]; i++; talent[i] = [2, "Improved Shadow Word: Pain", 2, 2, 2]; i++; talent[i] = [2, "Shadow Focus", 3, 3, 2]; i++; talent[i] = [2, "Improved Psychic Scream", 2, 1, 3]; i++; talent[i] = [2, "Improved Mind Blast", 5, 2, 3]; i++; talent[i] = [2, "Mind Flay", 1, 3, 3]; i++; talent[i] = [2, "Veiled Shadows", 2, 2, 4]; i++; talent[i] = [2, "Shadow Reach", 2, 3, 4]; i++; talent[i] = [2, "Shadow Weaving", 3, 4, 4]; i++; talent[i] = [2, "Silence", 1, 1, 5, [getTalentID("Improved Psychic Scream"),2]]; i++; talent[i] = [2, "Vampiric Embrace", 1, 2, 5]; i++; talent[i] = [2, "Improved Vampiric Embrace", 2, 3, 5, [getTalentID("Vampiric Embrace"),1]]; i++; talent[i] = [2, "Focused Mind", 3, 4, 5]; i++; talent[i] = [2, "Mind Melt", 2, 1, 6]; i++; talent[i] = [2, "Improved Devouring Plague", 3, 3, 6]; i++; talent[i] = [2, "Shadowform", 1, 2, 7, [getTalentID("Vampiric Embrace"),1]]; i++; talent[i] = [2, "Shadow Power", 5, 3, 7]; i++; talent[i] = [2, "Improved Shadowform", 2, 1, 8, [getTalentID("Shadowform"),1]]; i++; talent[i] = [2, "Misery", 3, 3, 8]; i++; talent[i] = [2, "Psychic Horror", 1, 1, 9]; i++; talent[i] = [2, "Vampiric Touch", 1, 2, 9, [getTalentID("Shadowform"),1]]; i++; talent[i] = [2, "Pain and Suffering", 3, 3, 9]; i++; talent[i] = [2, "Twisted Faith", 5, 3, 10]; i++; talent[i] = [2, "Dispersion", 1, 2, 11, [getTalentID("Vampiric Touch"),1]]; i++; treeStartStop[t] = i - 1; t++; i = 0; // Name: Unbreakable Will rank[i] = [ "Reduces the duration of Stun, Fear, and Silence effects done to you by an additional 6%.", "Reduces the duration of Stun, Fear, and Silence effects done to you by an additional 12%.", "Reduces the duration of Stun, Fear, and Silence effects done to you by an additional 18%.", "Reduces the duration of Stun, Fear, and Silence effects done to you by an additional 24%.", "Reduces the duration of Stun, Fear, and Silence effects done to you by an additional 30%." ]; i++; // Name: Twin Disciplines rank[i] = [ "Increases the damage and healing done by your instant spells by 1%.", "Increases the damage and healing done by your instant spells by 2%.", "Increases the damage and healing done by your instant spells by 3%.", "Increases the damage and healing done by your instant spells by 4%.", "Increases the damage and healing done by your instant spells by 5%." ]; i++; // Name: Silent Resolve rank[i] = [ "Reduces the threat generated by your Holy and Discipline spells by 7% and reduces the chance your helpful spells and damage over time effects will be dispelled by 10%.", "Reduces the threat generated by your Holy and Discipline spells by 14% and reduces the chance your helpful spells and damage over time effects will be dispelled by 20%.", "Reduces the threat generated by your Holy and Discipline spells by 20% and reduces the chance your helpful spells and damage over time effects will be dispelled by 30%." ]; i++; // Name: Improved Inner Fire rank[i] = [ "Increases the effect of your Inner Fire spell by 15%, and increases the total number of charges by 4.", "Increases the effect of your Inner Fire spell by 30%, and increases the total number of charges by 8.", "Increases the effect of your Inner Fire spell by 45%, and increases the total number of charges by 12." ]; i++; // Name: Improved Power Word: Fortitude rank[i] = [ "Increases the effect of your Power Word: Fortitude and Prayer of Fortitude spells by 15%, and increases your total Stamina by 2%.", "Increases the effect of your Power Word: Fortitude and Prayer of Fortitude spells by 30%, and increases your total Stamina by 4%." ]; i++; // Name: Martyrdom rank[i] = [ "Gives you a 50% chance to gain the Focused Casting effect that lasts for 6 sec after being the victim of a melee or ranged critical strike.  The Focused Casting effect reduces the pushback suffered from damaging attacks while casting Priest spells and decreases the duration of Interrupt effects by 10%.", "Gives you a 100% chance to gain the Focused Casting effect that lasts for 6 sec after being the victim of a melee or ranged critical strike.  The Focused Casting effect reduces the pushback suffered from damaging attacks while casting Priest spells and decreases the duration of Interrupt effects by 20%." ]; i++; // Name: Meditation rank[i] = [ "Allows 17% of your mana regeneration to continue while casting.", "Allows 33% of your mana regeneration to continue while casting.", "Allows 50% of your mana regeneration to continue while casting." ]; i++; // Name: Inner Focus rank[i] = [ "When activated, reduces the mana cost of your next spell by 100% and increases its critical effect chance by 25% if it is capable of a critical effect." ]; i++; // Name: Improved Power Word: Shield rank[i] = [ "Increases the damage absorbed by your Power Word: Shield by 5%.", "Increases the damage absorbed by your Power Word: Shield by 10%.", "Increases the damage absorbed by your Power Word: Shield by 15%." ]; i++; // Name: Absolution rank[i] = [ "Reduces the mana cost of your Dispel Magic, Cure Disease, Abolish Disease and Mass Dispel spells by 5%.", "Reduces the mana cost of your Dispel Magic, Cure Disease, Abolish Disease and Mass Dispel spells by 10%.", "Reduces the mana cost of your Dispel Magic, Cure Disease, Abolish Disease and Mass Dispel spells by 15%." ]; i++; // Name: Mental Agility rank[i] = [ "Reduces the mana cost of your instant cast spells by 4%.", "Reduces the mana cost of your instant cast spells by 7%.", "Reduces the mana cost of your instant cast spells by 10%." ]; i++; // Name: Improved Mana Burn rank[i] = [ "Reduces the casting time of your Mana Burn spell by 0.5 sec.", "Reduces the casting time of your Mana Burn spell by 1 sec." ]; i++; // Name: Reflective Shield rank[i] = [ "Causes 22% of the damage you absorb with Power Word: Shield to reflect back at the attacker.  This damage causes no threat.", "Causes 45% of the damage you absorb with Power Word: Shield to reflect back at the attacker.  This damage causes no threat." ]; i++; // Name: Mental Strength rank[i] = [ "Increases your total Intellect by 3%.", "Increases your total Intellect by 6%.", "Increases your total Intellect by 9%.", "Increases your total Intellect by 12%.", "Increases your total Intellect by 15%." ]; i++; // Name: Soul Warding rank[i] = [ "Reduces the cooldown of your Power Word: Shield ability by 4 sec, and reduces the mana cost of your Power Word: Shield by 15%." ]; i++; // Name: Focused Power rank[i] = [ "Increases damage and healing done by your spells by 2%. In addition, your Mass Dispel cast time is reduced by 0.5 sec.", "Increases damage and healing done by your spells by 4%. In addition, your Mass Dispel cast time is reduced by 1 sec." ]; i++; // Name: Enlightenment rank[i] = [ "Increases your total Spirit by 2% and increases your spell haste by 2%.", "Increases your total Spirit by 4% and increases your spell haste by 4%.", "Increases your total Spirit by 6% and increases your spell haste by 6%." ]; i++; // Name: Focused Will rank[i] = [ "Increases your spell critical effect chance by 1%, and after taking a critical hit you gain the Focused Will effect, reducing all damage taken by 2% and increasing healing effects on you by 3%.  Stacks up to 3 times.  Lasts 8 sec.", "Increases your spell critical effect chance by 2%, and after taking a critical hit you gain the Focused Will effect, reducing all damage taken by 3% and increasing healing effects on you by 4%.  Stacks up to 3 times.  Lasts 8 sec.", "Increases your spell critical effect chance by 3%, and after taking a critical hit you gain the Focused Will effect, reducing all damage taken by 4% and increasing healing effects on you by 5%.  Stacks up to 3 times.  Lasts 8 sec." ]; i++; // Name: Power Infusion rank[i] = [ "Infuses the target with power, increasing spell casting speed by 20% and reducing the mana cost of all spells by 20%.  Lasts 15 sec." ]; i++; // Name: Improved Flash Heal rank[i] = [ "Reduces the mana cost of your Flash Heal by 5%, and increases the critical effect chance of your Flash Heal by 4% on friendly targets at or below 50% health.", "Reduces the mana cost of your Flash Heal by 10%, and increases the critical effect chance of your Flash Heal by 7% on friendly targets at or below 50% health.", "Reduces the mana cost of your Flash Heal by 15%, and increases the critical effect chance of your Flash Heal by 10% on friendly targets at or below 50% health." ]; i++; // Name: Renewed Hope rank[i] = [ "Increases the critical effect chance of your Flash Heal, Greater Heal and Penance (Heal) spells by 2% on targets afflicted by the Weakened Soul effect, and you have a 50% chance to reduce all damage taken by 3% for 20 sec to all friendly party and raid targets when you cast Power Word: Shield.", "Increases the critical effect chance of your Flash Heal, Greater Heal and Penance (Heal) spells by 4% on targets afflicted by the Weakened Soul effect, and you have a 100% chance to reduce all damage taken by 3% for 20 sec to all friendly party and raid targets when you cast Power Word: Shield." ]; i++; // Name: Rapture rank[i] = [ "When your Power Word: Shield is completely absorbed or dispelled you are instantly energized with 1.5% of your total mana, and you have a 33% chance to energize your shielded target with 2% total mana, 8 rage, 16 energy or 32 runic power. This effect can only occur once every 12 sec.", "When your Power Word: Shield is completely absorbed or dispelled you are instantly energized with 2% of your total mana, and you have a 66% chance to energize your shielded target with 2% total mana, 8 rage, 16 energy or 32 runic power. This effect can only occur once every 12 sec.", "When your Power Word: Shield is completely absorbed or dispelled you are instantly energized with 2.5% of your total mana, and you have a 100% chance to energize your shielded target with 2% total mana, 8 rage, 16 energy or 32 runic power. This effect can only occur once every 12 sec." ]; i++; // Name: Aspiration rank[i] = [ "Reduces the cooldown of your Inner Focus, Power Infusion, Pain Suppression and Penance spells by 10%.", "Reduces the cooldown of your Inner Focus, Power Infusion, Pain Suppression and Penance spells by 20%." ]; i++; // Name: Divine Aegis rank[i] = [ "Critical heals create a protective shield on the target, absorbing 10% of the amount healed. Lasts 12 sec.", "Critical heals create a protective shield on the target, absorbing 20% of the amount healed. Lasts 12 sec.", "Critical heals create a protective shield on the target, absorbing 30% of the amount healed. Lasts 12 sec." ]; i++; // Name: Pain Suppression rank[i] = [ "Instantly reduces a friendly target\'s threat by 5%, reduces all damage taken by 40% and increases resistance to Dispel mechanics by 65% for 8 sec." ]; i++; // Name: Grace rank[i] = [ "Your Flash Heal, Greater Heal, and Penance spells have a 50% chance to bless the target with Grace, increasing all healing received from the Priest by 3%. This effect will stack up to 3 times. Effect lasts 15 sec. Grace can only be active on one target at a time.", "Your Flash Heal, Greater Heal, and Penance spells have a 100% chance to bless the target with Grace, increasing all healing received from the Priest by 3%. This effect will stack up to 3 times. Effect lasts 15 sec. Grace can only be active on one target at a time." ]; i++; // Name: Borrowed Time rank[i] = [ "Grants 5% spell haste for your next spell after casting Power Word: Shield, and increases the amount absorbed by your Power Word: Shield equal to 8% of your spell power.", "Grants 10% spell haste for your next spell after casting Power Word: Shield, and increases the amount absorbed by your Power Word: Shield equal to 16% of your spell power.", "Grants 15% spell haste for your next spell after casting Power Word: Shield, and increases the amount absorbed by your Power Word: Shield equal to 24% of your spell power.", "Grants 20% spell haste for your next spell after casting Power Word: Shield, and increases the amount absorbed by your Power Word: Shield equal to 32% of your spell power.", "Grants 25% spell haste for your next spell after casting Power Word: Shield, and increases the amount absorbed by your Power Word: Shield equal to 40% of your spell power." ]; i++; // Name: Penance rank[i] = [ "Launches a volley of holy light at the target, causing 240 Holy damage to an enemy, or 670 to 756 healing to an ally instantly and every 1 sec for 2 sec." ]; i++; // Name: Healing Focus rank[i] = [ "Reduces the pushback suffered from damaging attacks  while casting any healing spell by 35%.", "Reduces the pushback suffered from damaging attacks  while casting any healing spell by 70%." ]; i++; // Name: Improved Renew rank[i] = [ "Increases the amount healed by your Renew spell by 5%.", "Increases the amount healed by your Renew spell by 10%.", "Increases the amount healed by your Renew spell by 15%." ]; i++; // Name: Holy Specialization rank[i] = [ "Increases the critical effect chance of your Holy spells by 1%.", "Increases the critical effect chance of your Holy spells by 2%.", "Increases the critical effect chance of your Holy spells by 3%.", "Increases the critical effect chance of your Holy spells by 4%.", "Increases the critical effect chance of your Holy spells by 5%." ]; i++; // Name: Spell Warding rank[i] = [ "Reduces all spell damage taken by 2%.", "Reduces all spell damage taken by 4%.", "Reduces all spell damage taken by 6%.", "Reduces all spell damage taken by 8%.", "Reduces all spell damage taken by 10%." ]; i++; // Name: Divine Fury rank[i] = [ "Reduces the casting time of your Smite, Holy Fire, Heal and Greater Heal spells by 0.1 sec.", "Reduces the casting time of your Smite, Holy Fire, Heal and Greater Heal spells by 0.2 sec.", "Reduces the casting time of your Smite, Holy Fire, Heal and Greater Heal spells by 0.3 sec.", "Reduces the casting time of your Smite, Holy Fire, Heal and Greater Heal spells by 0.4 sec.", "Reduces the casting time of your Smite, Holy Fire, Heal and Greater Heal spells by 0.5 sec." ]; i++; // Name: Desperate Prayer rank[i] = [ "Instantly heals the caster for 263 to 325." ]; i++; // Name: Blessed Recovery rank[i] = [ "After being struck by a melee or ranged critical hit, Blessed Recovery heals you for 5% of the damage taken over 6 sec.  Additional critical hits taken during the effect increase the healing received.", "After being struck by a melee or ranged critical hit, Blessed Recovery heals you for 10% of the damage taken over 6 sec.  Additional critical hits taken during the effect increase the healing received.", "After being struck by a melee or ranged critical hit, Blessed Recovery heals you for 15% of the damage taken over 6 sec.  Additional critical hits taken during the effect increase the healing received." ]; i++; // Name: Inspiration rank[i] = [ "Reduces your target\'s physical damage taken by 3% for 15 sec after getting a critical effect from your Flash Heal, Heal, Greater Heal, Binding Heal, Penance, Prayer of Healing, or Circle of Healing spell.", "Reduces your target\'s physical damage taken by 7% for 15 sec after getting a critical effect from your Flash Heal, Heal, Greater Heal, Binding Heal, Penance, Prayer of Healing, or Circle of Healing spell.", "Reduces your target\'s physical damage taken by 10% for 15 sec after getting a critical effect from your Flash Heal, Heal, Greater Heal, Binding Heal, Penance, Prayer of Healing, or Circle of Healing spell." ]; i++; // Name: Holy Reach rank[i] = [ "Increases the range of your Smite and Holy Fire spells and the radius of your Prayer of Healing, Holy Nova, Divine Hymn and Circle of Healing spells by 10%.", "Increases the range of your Smite and Holy Fire spells and the radius of your Prayer of Healing, Holy Nova, Divine Hymn and Circle of Healing spells by 20%." ]; i++; // Name: Improved Healing rank[i] = [ "Reduces the mana cost of your Lesser Heal, Heal, Greater Heal, Divine Hymn and Penance spells by 5%.", "Reduces the mana cost of your Lesser Heal, Heal, Greater Heal, Divine Hymn and Penance spells by 10%.", "Reduces the mana cost of your Lesser Heal, Heal, Greater Heal, Divine Hymn and Penance spells by 15%." ]; i++; // Name: Searing Light rank[i] = [ "Increases the damage of your Smite, Holy Fire, Holy Nova and Penance spells by 5%.", "Increases the damage of your Smite, Holy Fire, Holy Nova and Penance spells by 10%." ]; i++; // Name: Healing Prayers rank[i] = [ "Reduces the mana cost of your Prayer of Healing and Prayer of Mending spell by 10%.", "Reduces the mana cost of your Prayer of Healing and Prayer of Mending spell by 20%." ]; i++; // Name: Spirit of Redemption rank[i] = [ "Increases total Spirit by 5% and upon death, the priest becomes the Spirit of Redemption for 15 sec.  The Spirit of Redemption cannot move, attack, be attacked or targeted by any spells or effects.  While in this form the priest can cast any healing spell free of cost.  When the effect ends, the priest dies." ]; i++; // Name: Spiritual Guidance rank[i] = [ "Increases spell power by 5% of your total Spirit.", "Increases spell power by 10% of your total Spirit.", "Increases spell power by 15% of your total Spirit.", "Increases spell power by 20% of your total Spirit.", "Increases spell power by 25% of your total Spirit." ]; i++; // Name: Surge of Light rank[i] = [ "Your spell criticals have a 25% chance to cause your next Smite or Flash Heal spell to be instant cast, cost no mana but be incapable of a critical hit.  This effect lasts 10 sec.", "Your spell criticals have a 50% chance to cause your next Smite or Flash Heal spell to be instant cast, cost no mana but be incapable of a critical hit.  This effect lasts 10 sec." ]; i++; // Name: Spiritual Healing rank[i] = [ "Increases the amount healed by your healing spells by 2%.", "Increases the amount healed by your healing spells by 4%.", "Increases the amount healed by your healing spells by 6%.", "Increases the amount healed by your healing spells by 8%.", "Increases the amount healed by your healing spells by 10%." ]; i++; // Name: Holy Concentration rank[i] = [ "Your mana regeneration from spirit is increased by 16% for 8 sec after you critically heal with Flash Heal, Greater Heal, Binding Heal or Empowered Renew.", "Your mana regeneration from spirit is increased by 32% for 8 sec after you critically heal with Flash Heal, Greater Heal, Binding Heal or Empowered Renew.", "Your mana regeneration from spirit is increased by 50% for 8 sec after you critically heal with Flash Heal, Greater Heal, Binding Heal or Empowered Renew." ]; i++; // Name: Lightwell rank[i] = [ "Creates a Holy Lightwell.  Members of your raid or party can click the Lightwell to restore 801 health over 6 sec.  Attacks done to you equal to 30% of your total health will cancel the effect. Lightwell lasts for 3 min or 10 charges." ]; i++; // Name: Blessed Resilience rank[i] = [ "Increases the effectiveness of your healing spells by 1%, and critical hits made against you have a 20% chance to prevent you from being critically hit again for 6 sec.", "Increases the effectiveness of your healing spells by 2%, and critical hits made against you have a 40% chance to prevent you from being critically hit again for 6 sec.", "Increases the effectiveness of your healing spells by 3%, and critical hits made against you have a 60% chance to prevent you from being critically hit again for 6 sec." ]; i++; // Name: Body and Soul rank[i] = [ "When you cast Power Word: Shield, you increase the target\'s movement speed by 30% for 4 sec, and you have a 50% chance when you cast Abolish Disease on yourself to also cleanse 1 poison effect in addition to diseases.", "When you cast Power Word: Shield, you increase the target\'s movement speed by 60% for 4 sec, and you have a 100% chance when you cast Abolish Disease on yourself to also cleanse 1 poison effect in addition to diseases." ]; i++; // Name: Empowered Healing rank[i] = [ "Your Greater Heal spell gains an additional 8% and your Flash Heal and Binding Heal gain an additional 4% of your bonus healing effects.", "Your Greater Heal spell gains an additional 16% and your Flash Heal and Binding Heal gain an additional 8% of your bonus healing effects.", "Your Greater Heal spell gains an additional 24% and your Flash Heal and Binding Heal gain an additional 12% of your bonus healing effects.", "Your Greater Heal spell gains an additional 32% and your Flash Heal and Binding Heal gain an additional 16% of your bonus healing effects.", "Your Greater Heal spell gains an additional 40% and your Flash Heal and Binding Heal gain an additional 20% of your bonus healing effects." ]; i++; // Name: Serendipity rank[i] = [ "When you heal with Binding Heal or Flash Heal, the cast time of your next Greater Heal or Prayer of Healing spell is reduced by 4%. Stacks up to 3 times. Lasts 20 sec.", "When you heal with Binding Heal or Flash Heal, the cast time of your next Greater Heal or Prayer of Healing spell is reduced by 8%. Stacks up to 3 times. Lasts 20 sec.", "When you heal with Binding Heal or Flash Heal, the cast time of your next Greater Heal or Prayer of Healing spell is reduced by 12%. Stacks up to 3 times. Lasts 20 sec." ]; i++; // Name: Empowered Renew rank[i] = [ "Your Renew spell gains an additional 5% of your bonus healing effects, and your Renew will instantly heal the target for 5% of the total periodic effect.", "Your Renew spell gains an additional 10% of your bonus healing effects, and your Renew will instantly heal the target for 10% of the total periodic effect.", "Your Renew spell gains an additional 15% of your bonus healing effects, and your Renew will instantly heal the target for 15% of the total periodic effect." ]; i++; // Name: Circle of Healing rank[i] = [ "Heals up to 5 friendly party or raid members within 15 yards of the target for 343 to 379." ]; i++; // Name: Test of Faith rank[i] = [ "Increases healing by 4% on friendly targets at or below 50% health.", "Increases healing by 8% on friendly targets at or below 50% health.", "Increases healing by 12% on friendly targets at or below 50% health." ]; i++; // Name: Divine Providence rank[i] = [ "Increases the amount healed by Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending by 2%, and reduces the cooldown of your Prayer of Mending by 6%.", "Increases the amount healed by Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending by 4%, and reduces the cooldown of your Prayer of Mending by 12%.", "Increases the amount healed by Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending by 6%, and reduces the cooldown of your Prayer of Mending by 18%.", "Increases the amount healed by Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending by 8%, and reduces the cooldown of your Prayer of Mending by 24%.", "Increases the amount healed by Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending by 10%, and reduces the cooldown of your Prayer of Mending by 30%." ]; i++; // Name: Guardian Spirit rank[i] = [ "Calls upon a guardian spirit to watch over the friendly target. The spirit increases the healing received by the target by 40%, and also prevents the target from dying by sacrificing itself.  This sacrifice terminates the effect but heals the target of 50% of their maximum health. Lasts 10 sec." ]; i++; // Name: Spirit Tap rank[i] = [ "Gives you a 33% chance to gain a 100% bonus to your Spirit after killing a target that yields experience or honor.  For the duration, your mana will regenerate at a 83% rate while casting.  Lasts 15 sec.", "Gives you a 66% chance to gain a 100% bonus to your Spirit after killing a target that yields experience or honor.  For the duration, your mana will regenerate at a 83% rate while casting.  Lasts 15 sec.", "Gives you a 100% chance to gain a 100% bonus to your Spirit after killing a target that yields experience or honor.  For the duration, your mana will regenerate at a 83% rate while casting.  Lasts 15 sec." ]; i++; // Name: Improved Spirit Tap rank[i] = [ "Your Mind Blast and Shadow Word: Death critical strikes increase your total Spirit by 5%. For the duration, your mana will regenerate at a 17% rate while casting. Lasts 8 sec.", "Your Mind Blast and Shadow Word: Death critical strikes increase your total Spirit by 10%. For the duration, your mana will regenerate at a 33% rate while casting. Lasts 8 sec." ]; i++; // Name: Darkness rank[i] = [ "Increases your Shadow spell damage by 2%.", "Increases your Shadow spell damage by 4%.", "Increases your Shadow spell damage by 6%.", "Increases your Shadow spell damage by 8%.", "Increases your Shadow spell damage by 10%." ]; i++; // Name: Shadow Affinity rank[i] = [ "Reduces the threat generated by your Shadow spells by 8%, and you receive 5% of your base mana when your Shadow Word: Pain or Vampiric Touch spells are dispelled.", "Reduces the threat generated by your Shadow spells by 16%, and you receive 10% of your base mana when your Shadow Word: Pain or Vampiric Touch spells are dispelled.", "Reduces the threat generated by your Shadow spells by 25%, and you receive 15% of your base mana when your Shadow Word: Pain or Vampiric Touch spells are dispelled." ]; i++; // Name: Improved Shadow Word: Pain rank[i] = [ "Increases the damage of your Shadow Word: Pain spell by 3%.", "Increases the damage of your Shadow Word: Pain spell by 6%." ]; i++; // Name: Shadow Focus rank[i] = [ "Increases your chance to hit with your Shadow spells by 1%, and reduces the mana cost of your Shadow spells by 2%.", "Increases your chance to hit with your Shadow spells by 2%, and reduces the mana cost of your Shadow spells by 4%.", "Increases your chance to hit with your Shadow spells by 3%, and reduces the mana cost of your Shadow spells by 6%." ]; i++; // Name: Improved Psychic Scream rank[i] = [ "Reduces the cooldown of your Psychic Scream spell by 2 sec.", "Reduces the cooldown of your Psychic Scream spell by 4 sec." ]; i++; // Name: Improved Mind Blast rank[i] = [ "Reduces the cooldown of your Mind Blast spell by 0.5 sec., and while in Shadowform your Mind Blast also has a 20% chance to reduce all healing done to the target by 20% for 10 sec.", "Reduces the cooldown of your Mind Blast spell by 1 sec., and while in Shadowform your Mind Blast also has a 40% chance to reduce all healing done to the target by 20% for 10 sec.", "Reduces the cooldown of your Mind Blast spell by 1.5 sec., and while in Shadowform your Mind Blast also has a 60% chance to reduce all healing done to the target by 20% for 10 sec.", "Reduces the cooldown of your Mind Blast spell by 2 sec., and while in Shadowform your Mind Blast also has a 80% chance to reduce all healing done to the target by 20% for 10 sec.", "Reduces the cooldown of your Mind Blast spell by 2.5 sec., and while in Shadowform your Mind Blast also has a 100% chance to reduce all healing done to the target by 20% for 10 sec." ]; i++; // Name: Mind Flay rank[i] = [ "Assault the target\'s mind with Shadow energy, causing -30 Shadow damage over 3 sec and slowing their movement speed by 15%." ]; i++; // Name: Veiled Shadows rank[i] = [ "Decreases the cooldown of your Fade ability by 3 sec, and reduces the cooldown of your Shadowfiend ability by 1 minute.", "Decreases the cooldown of your Fade ability by 6 sec, and reduces the cooldown of your Shadowfiend ability by 2 minutes." ]; i++; // Name: Shadow Reach rank[i] = [ "Increases the range of your offensive Shadow spells by 10%.", "Increases the range of your offensive Shadow spells by 20%." ]; i++; // Name: Shadow Weaving rank[i] = [ "Your Shadow damage spells have a 33% chance to increase the Shadow damage you deal by 2% for 15 sec.  Stacks up to 5 times.", "Your Shadow damage spells have a 66% chance to increase the Shadow damage you deal by 2% for 15 sec.  Stacks up to 5 times.", "Your Shadow damage spells have a 100% chance to increase the Shadow damage you deal by 2% for 15 sec.  Stacks up to 5 times." ]; i++; // Name: Silence rank[i] = [ "Silences the target, preventing them from casting spells for 5 sec." ]; i++; // Name: Vampiric Embrace rank[i] = [ "Afflicts your target with Shadow energy that causes you to be healed for 15% and other party members to be healed for 3% of any Shadow spell damage you deal for 5 min." ]; i++; // Name: Improved Vampiric Embrace rank[i] = [ "Increases the healing received from Vampiric Embrace by 33%.", "Increases the healing received from Vampiric Embrace by 67%." ]; i++; // Name: Focused Mind rank[i] = [ "Reduces the mana cost of your Mind Blast, Mind Control, Mind Flay and Mind Sear spells by 5%.", "Reduces the mana cost of your Mind Blast, Mind Control, Mind Flay and Mind Sear spells by 10%.", "Reduces the mana cost of your Mind Blast, Mind Control, Mind Flay and Mind Sear spells by 15%." ]; i++; // Name: Mind Melt rank[i] = [ "Increases the critical strike chance of your Mind Blast, Mind Flay and Mind Sear spells by 2%, and increases the periodic critical strike chance of your Vampiric Touch, Devouring Plague and Shadow Word: Pain spells by 3%.", "Increases the critical strike chance of your Mind Blast, Mind Flay and Mind Sear spells by 4%, and increases the periodic critical strike chance of your Vampiric Touch, Devouring Plague and Shadow Word: Pain spells by 6%." ]; i++; // Name: Improved Devouring Plague rank[i] = [ "Increases the periodic damage done by your Devouring Plague by 5%, and when you cast Devouring Plague you instantly deal damage equal to 5% of its total periodic effect.", "Increases the periodic damage done by your Devouring Plague by 10%, and when you cast Devouring Plague you instantly deal damage equal to 10% of its total periodic effect.", "Increases the periodic damage done by your Devouring Plague by 15%, and when you cast Devouring Plague you instantly deal damage equal to 15% of its total periodic effect." ]; i++; // Name: Shadowform rank[i] = [ "Assume a Shadowform, increasing your Shadow damage by 15%, reducing all damage done to you by 15% and threat generated by 30%.  However, you may not cast Holy spells while in this form except Cure Disease and Abolish Disease.  Grants the periodic damage from your Shadow Word: Pain, Devouring Plague, and Vampiric Touch spells the ability to critically hit for 100% increased damage." ]; i++; // Name: Shadow Power rank[i] = [ "Increases the critical strike damage bonus of your Mind Blast, Mind Flay, and Shadow Word: Death spells by 20%.", "Increases the critical strike damage bonus of your Mind Blast, Mind Flay, and Shadow Word: Death spells by 40%.", "Increases the critical strike damage bonus of your Mind Blast, Mind Flay, and Shadow Word: Death spells by 60%.", "Increases the critical strike damage bonus of your Mind Blast, Mind Flay, and Shadow Word: Death spells by 80%.", "Increases the critical strike damage bonus of your Mind Blast, Mind Flay, and Shadow Word: Death spells by 100%." ]; i++; // Name: Improved Shadowform rank[i] = [ "Your Fade ability now has a 50% chance to remove all movement impairing effects when used while in Shadowform, and reduces casting or channeling time lost when damaged by 35% when casting any Shadow spell while in Shadowform.", "Your Fade ability now has a 100% chance to remove all movement impairing effects when used while in Shadowform, and reduces casting or channeling time lost when damaged by 70% when casting any Shadow spell while in Shadowform." ]; i++; // Name: Misery rank[i] = [ "Your Shadow Word: Pain, Mind Flay and Vampiric Touch spells also increase the chance for harmful spells to hit by 1% lasting 24 sec, and increases the benefit from spell power gained by your Mind Blast, Mind Flay and Mind Sear spells by 5%.", "Your Shadow Word: Pain, Mind Flay and Vampiric Touch spells also increase the chance for harmful spells to hit by 2% lasting 24 sec, and increases the benefit from spell power gained by your Mind Blast, Mind Flay and Mind Sear spells by 10%.", "Your Shadow Word: Pain, Mind Flay and Vampiric Touch spells also increase the chance for harmful spells to hit by 3% lasting 24 sec, and increases the benefit from spell power gained by your Mind Blast, Mind Flay and Mind Sear spells by 15%." ]; i++; // Name: Psychic Horror rank[i] = [ "You terrify the target, causing them to tremble in horror for 3 sec and drop their main hand and ranged weapons for 10 sec." ]; i++; // Name: Vampiric Touch rank[i] = [ "Causes 450 Shadow damage over 15 sec to your target and causes up to 10 party or raid members to gain 1% of their maximum mana per 5 sec when you deal damage from Mind Blast. In addition, if the Vampiric Touch is dispelled it will cause 720 damage to the afflicted target." ]; i++; // Name: Pain and Suffering rank[i] = [ "Your Mind Flay has a 33% chance to refresh the duration of your Shadow Word: Pain on the target, and reduces the damage you take from your own Shadow Word: Death by 10%.", "Your Mind Flay has a 66% chance to refresh the duration of your Shadow Word: Pain on the target, and reduces the damage you take from your own Shadow Word: Death by 20%.", "Your Mind Flay has a 100% chance to refresh the duration of your Shadow Word: Pain on the target, and reduces the damage you take from your own Shadow Word: Death by 30%." ]; i++; // Name: Twisted Faith rank[i] = [ "Increases your spell power by 2% of your total Spirit, and your damage done by your Mind Flay and Mind Blast is increased by 2% if your target is afflicted by your Shadow Word: Pain.", "Increases your spell power by 4% of your total Spirit, and your damage done by your Mind Flay and Mind Blast is increased by 4% if your target is afflicted by your Shadow Word: Pain.", "Increases your spell power by 6% of your total Spirit, and your damage done by your Mind Flay and Mind Blast is increased by 6% if your target is afflicted by your Shadow Word: Pain.", "Increases your spell power by 8% of your total Spirit, and your damage done by your Mind Flay and Mind Blast is increased by 8% if your target is afflicted by your Shadow Word: Pain.", "Increases your spell power by 10% of your total Spirit, and your damage done by your Mind Flay and Mind Blast is increased by 10% if your target is afflicted by your Shadow Word: Pain." ]; i++; // Name: Dispersion rank[i] = [ "You disperse into pure Shadow energy, reducing all damage taken by 90%.  You are unable to attack or cast spells, but you regenerate 6% mana every 1 sec for 6 sec. Dispersion can be cast while stunned, feared or silenced and clears all snare and movement impairing effects when cast, and makes you immune to them while dispersed." ]; i++;