/* 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 = "rogue"; var i = 0; var t = 0; var className = "Rogue Talents"; tree[i] = "Assassination"; i++; tree[i] = "Combat"; i++; tree[i] = "Subtlety"; i++; i = 0; talent[i] = [0, "Improved Eviscerate", 3, 1, 1]; i++; talent[i] = [0, "Remorseless Attacks", 2, 2, 1]; i++; talent[i] = [0, "Malice", 5, 3, 1]; i++; talent[i] = [0, "Ruthlessness", 3, 1, 2]; i++; talent[i] = [0, "Blood Spatter", 2, 2, 2]; i++; talent[i] = [0, "Puncturing Wounds", 3, 4, 2]; i++; talent[i] = [0, "Vigor", 1, 1, 3]; i++; talent[i] = [0, "Improved Expose Armor", 2, 2, 3]; i++; talent[i] = [0, "Lethality", 5, 3, 3, [getTalentID("Malice"),5]]; i++; talent[i] = [0, "Vile Poisons", 3, 2, 4]; i++; talent[i] = [0, "Improved Poisons", 5, 3, 4]; i++; talent[i] = [0, "Fleet Footed", 2, 1, 5]; i++; talent[i] = [0, "Cold Blood", 1, 2, 5]; i++; talent[i] = [0, "Improved Kidney Shot", 3, 3, 5]; i++; talent[i] = [0, "Quick Recovery", 2, 4, 5]; i++; talent[i] = [0, "Seal Fate", 5, 2, 6, [getTalentID("Cold Blood"),1]]; i++; talent[i] = [0, "Murder", 2, 3, 6]; i++; talent[i] = [0, "Deadly Brew", 2, 1, 7]; i++; talent[i] = [0, "Overkill", 1, 2, 7]; i++; talent[i] = [0, "Deadened Nerves", 3, 3, 7]; i++; talent[i] = [0, "Focused Attacks", 3, 1, 8]; i++; talent[i] = [0, "Find Weakness", 3, 3, 8]; i++; talent[i] = [0, "Master Poisoner", 3, 1, 9]; i++; talent[i] = [0, "Mutilate", 1, 2, 9, [getTalentID("Overkill"),1]]; i++; talent[i] = [0, "Turn the Tables", 3, 3, 9]; i++; talent[i] = [0, "Cut to the Chase", 5, 2, 10]; i++; talent[i] = [0, "Hunger For Blood", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [1, "Improved Gouge", 3, 1, 1]; i++; talent[i] = [1, "Improved Sinister Strike", 2, 2, 1]; i++; talent[i] = [1, "Dual Wield Specialization", 5, 3, 1]; i++; talent[i] = [1, "Improved Slice and Dice", 2, 1, 2]; i++; talent[i] = [1, "Deflection", 3, 2, 2]; i++; talent[i] = [1, "Precision", 5, 4, 2]; i++; talent[i] = [1, "Endurance", 2, 1, 3]; i++; talent[i] = [1, "Riposte", 1, 2, 3, [getTalentID("Deflection"),3]]; i++; talent[i] = [1, "Close Quarters Combat", 5, 3, 3, [getTalentID("Dual Wield Specialization"),5]]; i++; talent[i] = [1, "Improved Kick", 2, 1, 4]; i++; talent[i] = [1, "Improved Sprint", 2, 2, 4]; i++; talent[i] = [1, "Lightning Reflexes", 3, 3, 4]; i++; talent[i] = [1, "Aggression", 5, 4, 4]; i++; talent[i] = [1, "Mace Specialization", 5, 1, 5]; i++; talent[i] = [1, "Blade Flurry", 1, 2, 5]; i++; talent[i] = [1, "Sword Specialization", 5, 3, 5]; i++; talent[i] = [1, "Weapon Expertise", 2, 2, 6, [getTalentID("Blade Flurry"),1]]; i++; talent[i] = [1, "Blade Twisting", 2, 3, 6]; i++; talent[i] = [1, "Vitality", 3, 1, 7]; i++; talent[i] = [1, "Adrenaline Rush", 1, 2, 7]; i++; talent[i] = [1, "Nerves of Steel", 2, 3, 7]; i++; talent[i] = [1, "Throwing Specialization", 2, 1, 8]; i++; talent[i] = [1, "Combat Potency", 5, 3, 8]; i++; talent[i] = [1, "Unfair Advantage", 2, 1, 9]; i++; talent[i] = [1, "Surprise Attacks", 1, 2, 9, [getTalentID("Adrenaline Rush"),1]]; i++; talent[i] = [1, "Savage Combat", 2, 3, 9]; i++; talent[i] = [1, "Prey on the Weak", 5, 2, 10]; i++; talent[i] = [1, "Killing Spree", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; talent[i] = [2, "Relentless Strikes", 5, 1, 1]; i++; talent[i] = [2, "Master of Deception", 3, 2, 1]; i++; talent[i] = [2, "Opportunity", 2, 3, 1]; i++; talent[i] = [2, "Sleight of Hand", 2, 1, 2]; i++; talent[i] = [2, "Dirty Tricks", 2, 2, 2]; i++; talent[i] = [2, "Camouflage", 3, 3, 2]; i++; talent[i] = [2, "Elusiveness", 2, 1, 3]; i++; talent[i] = [2, "Ghostly Strike", 1, 2, 3]; i++; talent[i] = [2, "Serrated Blades", 3, 3, 3]; i++; talent[i] = [2, "Setup", 3, 1, 4]; i++; talent[i] = [2, "Initiative", 3, 2, 4]; i++; talent[i] = [2, "Improved Ambush", 2, 3, 4]; i++; talent[i] = [2, "Heightened Senses", 2, 1, 5]; i++; talent[i] = [2, "Preparation", 1, 2, 5]; i++; talent[i] = [2, "Dirty Deeds", 2, 3, 5]; i++; talent[i] = [2, "Hemorrhage", 1, 4, 5, [getTalentID("Serrated Blades"),3]]; i++; talent[i] = [2, "Master of Subtlety", 3, 1, 6]; i++; talent[i] = [2, "Deadliness", 5, 3, 6]; i++; talent[i] = [2, "Enveloping Shadows", 3, 1, 7]; i++; talent[i] = [2, "Premeditation", 1, 2, 7, [getTalentID("Preparation"),1]]; i++; talent[i] = [2, "Cheat Death", 3, 3, 7]; i++; talent[i] = [2, "Sinister Calling", 5, 2, 8, [getTalentID("Premeditation"),1]]; i++; talent[i] = [2, "Waylay", 2, 3, 8]; i++; talent[i] = [2, "Honor Among Thieves", 3, 1, 9]; i++; talent[i] = [2, "Shadowstep", 1, 2, 9]; i++; talent[i] = [2, "Filthy Tricks", 2, 3, 9]; i++; talent[i] = [2, "Slaughter from the Shadows", 5, 2, 10]; i++; talent[i] = [2, "Shadow Dance", 1, 2, 11]; i++; treeStartStop[t] = i - 1; t++; i = 0; // Name: Improved Eviscerate rank[i] = [ "Increases the damage done by your Eviscerate ability by 7%.", "Increases the damage done by your Eviscerate ability by 14%.", "Increases the damage done by your Eviscerate ability by 20%." ]; i++; // Name: Remorseless Attacks rank[i] = [ "After killing an opponent that yields experience or honor, gives you a 20% increased critical strike chance on your next Sinister Strike, Hemorrhage, Backstab, Mutilate, Ambush, or Ghostly Strike.  Lasts 20 sec.", "After killing an opponent that yields experience or honor, gives you a 40% increased critical strike chance on your next Sinister Strike, Hemorrhage, Backstab, Mutilate, Ambush, or Ghostly Strike.  Lasts 20 sec." ]; i++; // Name: Malice rank[i] = [ "Increases your critical strike chance by 1%.", "Increases your critical strike chance by 2%.", "Increases your critical strike chance by 3%.", "Increases your critical strike chance by 4%.", "Increases your critical strike chance by 5%." ]; i++; // Name: Ruthlessness rank[i] = [ "Gives your melee finishing moves a 20% chance to add a combo point to your target.", "Gives your melee finishing moves a 40% chance to add a combo point to your target.", "Gives your melee finishing moves a 60% chance to add a combo point to your target." ]; i++; // Name: Blood Spatter rank[i] = [ "Increases the damage caused by your Garrote and Rupture abilities by 15%.", "Increases the damage caused by your Garrote and Rupture abilities by 30%." ]; i++; // Name: Puncturing Wounds rank[i] = [ "Increases the critical strike chance of your Backstab ability by 10%, and the critical strike chance of your Mutilate ability by 5%.", "Increases the critical strike chance of your Backstab ability by 20%, and the critical strike chance of your Mutilate ability by 10%.", "Increases the critical strike chance of your Backstab ability by 30%, and the critical strike chance of your Mutilate ability by 15%." ]; i++; // Name: Vigor rank[i] = [ "Increases your maximum Energy by 10." ]; i++; // Name: Improved Expose Armor rank[i] = [ "Reduces the energy cost of your Expose Armor ability by 5.", "Reduces the energy cost of your Expose Armor ability by 10." ]; i++; // Name: Lethality rank[i] = [ "Increases the critical strike damage bonus of all combo point-generating abilities that do not require stealth by 6%.", "Increases the critical strike damage bonus of all combo point-generating abilities that do not require stealth by 12%..", "Increases the critical strike damage bonus of all combo point-generating abilities that do not require stealth by 18%.", "Increases the critical strike damage bonus of all combo point-generating abilities that do not require stealth by 24%.", "Increases the critical strike damage bonus of all combo point-generating abilities that do not require stealth by 30%." ]; i++; // Name: Vile Poisons rank[i] = [ "Increases the damage dealt by your poisons and Envenom ability by 7% and gives your damage over time poisons an additional 10% chance to resist dispel effects.", "Increases the damage dealt by your poisons and Envenom ability by 14% and gives your damage over time poisons an additional 20% chance to resist dispel effects.", "Increases the damage dealt by your poisons and Envenom ability by 20% and gives your damage over time poisons an additional 30% chance to resist dispel effects." ]; i++; // Name: Improved Poisons rank[i] = [ "Increases the chance to apply Deadly Poison to your target by 2% and the frequency of applying Instant Poison to your target by 10%.", "Increases the chance to apply Deadly Poison to your target by 4% and the frequency of applying Instant Poison to your target by 20%.", "Increases the chance to apply Deadly Poison to your target by 6% and the frequency of applying Instant Poison to your target by 30%.", "Increases the chance to apply Deadly Poison to your target by 8% and the frequency of applying Instant Poison to your target by 40%.", "Increases the chance to apply Deadly Poison to your target by 10% and the frequency of applying Instant Poison to your target by 50%." ]; i++; // Name: Fleet Footed rank[i] = [ "Reduces the duration of all movement impairing effects by 15% and increases your movement speed by 8%.  This does not stack with other movement speed increasing effects.", "Reduces the duration of all movement impairing effects by 30% and increases your movement speed by 15%.  This does not stack with other movement speed increasing effects." ]; i++; // Name: Cold Blood rank[i] = [ "When activated, increases the critical strike chance of your next offensive ability by 100%." ]; i++; // Name: Improved Kidney Shot rank[i] = [ "While affected by your Kidney Shot ability, the target receives an additional 3% damage from all sources.", "While affected by your Kidney Shot ability, the target receives an additional 6% damage from all sources.", "While affected by your Kidney Shot ability, the target receives an additional 9% damage from all sources." ]; i++; // Name: Quick Recovery rank[i] = [ "All healing effects on you are increased by 10%.  In addition, your finishing moves refund 40% of their Energy cost when they fail to hit.", "All healing effects on you are increased by 20%.  In addition, your finishing moves refund 80% of their Energy cost when they fail to hit." ]; i++; // Name: Seal Fate rank[i] = [ "Your critical strikes from abilities that add combo points have a 20% chance to add an additional combo point.", "Your critical strikes from abilities that add combo points have a 40% chance to add an additional combo point.", "Your critical strikes from abilities that add combo points have a 60% chance to add an additional combo point.", "Your critical strikes from abilities that add combo points have a 80% chance to add an additional combo point.", "Your critical strikes from abilities that add combo points have a 100% chance to add an additional combo point." ]; i++; // Name: Murder rank[i] = [ "Increases all damage caused against Humanoid, Giant, Beast and Dragonkin targets by 2%.", "Increases all damage caused against Humanoid, Giant, Beast and Dragonkin targets by 4%." ]; i++; // Name: Deadly Brew rank[i] = [ "When you apply Instant, Wound or Mind-Numbing poison to a target, you have a 50% chance to apply Crippling poison.", "When you apply Instant, Wound or Mind-Numbing poison to a target, you have a 100% chance to apply Crippling poison." ]; i++; // Name: Overkill rank[i] = [ "While stealthed, and for 20 seconds after breaking stealth, you regenerate 30% additional energy." ]; i++; // Name: Deadened Nerves rank[i] = [ "Reduces all damage taken by 2%.", "Reduces all damage taken by 4%.", "Reduces all damage taken by 6%." ]; i++; // Name: Focused Attacks rank[i] = [ "Your melee critical strikes have a 33% chance to give you 2 energy.", "Your melee critical strikes have a 66% chance to give you 2 energy.", "Your melee critical strikes have a 100% chance to give you 2 energy." ]; i++; // Name: Find Weakness rank[i] = [ "Offensive ability damage increased by 2%.", "Offensive ability damage increased by 4%.", "Offensive ability damage increased by 6%." ]; i++; // Name: Master Poisoner rank[i] = [ "Increases the critical hit chance of all attacks made against any target you have poisoned by 1%, reduces the duration of all Poison effects applied to you by 17% and increases the bonus chance to apply Deadly Poison when Envenom is used by an additional 15%.", "Increases the critical hit chance of all attacks made against any target you have poisoned by 2%, reduces the duration of all Poison effects applied to you by 34% and increases the bonus chance to apply Deadly Poison when Envenom is used by an additional 30%.", "Increases the critical hit chance of all attacks made against any target you have poisoned by 3%, reduces the duration of all Poison effects applied to you by 50% and increases the bonus chance to apply Deadly Poison when Envenom is used by an additional 45%." ]; i++; // Name: Mutilate rank[i] = [ "Instantly attacks with both weapons for an additional 44 with each weapon.  Damage is increased by 20% against Poisoned targets.  Awards 2 combo points." ]; i++; // Name: Turn the Tables rank[i] = [ "Whenever anyone in your party or raid blocks, dodges, or parries an attack your chance to critically hit with all combo moves is increased by 2% for 8 sec.", "Whenever anyone in your party or raid blocks, dodges, or parries an attack your chance to critically hit with all combo moves is increased by 4% for 8 sec.", "Whenever anyone in your party or raid blocks, dodges, or parries an attack your chance to critically hit with all combo moves is increased by 6% for 8 sec." ]; i++; // Name: Cut to the Chase rank[i] = [ "Your Eviscerate and Envenom abilities have a 20% chance to refresh your Slice and Dice duration to its 5 combo point maximum.", "Your Eviscerate and Envenom abilities have a 40% chance to refresh your Slice and Dice duration to its 5 combo point maximum.", "Your Eviscerate and Envenom abilities have a 60% chance to refresh your Slice and Dice duration to its 5 combo point maximum.", "Your Eviscerate and Envenom abilities have a 80% chance to refresh your Slice and Dice duration to its 5 combo point maximum.", "Your Eviscerate and Envenom abilities have a 100% chance to refresh your Slice and Dice duration to its 5 combo point maximum." ]; i++; // Name: Hunger For Blood rank[i] = [ "Enrages you, increasing all damage caused by 15%.  Requires a bleed effect to be active on the target.  Lasts 1 min." ]; i++; // Name: Improved Gouge rank[i] = [ "Increases the effect duration of your Gouge ability by 0.5 sec.", "Increases the effect duration of your Gouge ability by 1 sec.", "Increases the effect duration of your Gouge ability by 1.5 sec." ]; i++; // Name: Improved Sinister Strike rank[i] = [ "Reduces the Energy cost of your Sinister Strike ability by 3.", "Reduces the Energy cost of your Sinister Strike ability by 5." ]; i++; // Name: Dual Wield Specialization rank[i] = [ "Increases the damage done by your offhand weapon by 10%.", "Increases the damage done by your offhand weapon by 20%.", "Increases the damage done by your offhand weapon by 30%.", "Increases the damage done by your offhand weapon by 40%.", "Increases the damage done by your offhand weapon by 50%." ]; i++; // Name: Improved Slice and Dice rank[i] = [ "Increases the duration of your Slice and Dice ability by 25%.", "Increases the duration of your Slice and Dice ability by 50%." ]; i++; // Name: Deflection rank[i] = [ "Increases your Parry chance by 2%.", "Increases your Parry chance by 4%.", "Increases your Parry chance by 6%." ]; i++; // Name: Precision rank[i] = [ "Increases your chance to hit with weapon and poison attacks by 1%.", "Increases your chance to hit with weapon and poison attacks by 2%.", "Increases your chance to hit with weapon and poison attacks by 3%.", "Increases your chance to hit with weapon and poison attacks by 4%.", "Increases your chance to hit with weapon and poison attacks by 5%." ]; i++; // Name: Endurance rank[i] = [ "Reduces the cooldown of your Sprint and Evasion abilities by 30 sec and increases your total Stamina by 2%.", "Reduces the cooldown of your Sprint and Evasion abilities by 60 sec and increases your total Stamina by 4%." ]; i++; // Name: Riposte rank[i] = [ "A strike that becomes active after parrying an opponent\'s attack.  This attack deals 150% weapon damage and slows their melee attack speed by 20% for 30 sec.  Awards 1 combo point." ]; i++; // Name: Close Quarters Combat rank[i] = [ "Increases your chance to get a critical strike with Daggers and Fist Weapons by 1%.", "Increases your chance to get a critical strike with Daggers and Fist Weapons by 2%.", "Increases your chance to get a critical strike with Daggers and Fist Weapons by 3%.", "Increases your chance to get a critical strike with Daggers and Fist Weapons by 4%.", "Increases your chance to get a critical strike with Daggers and Fist Weapons by 5%." ]; i++; // Name: Improved Kick rank[i] = [ "Gives your Kick ability a 50% chance to silence the target for 2 sec.", "Gives your Kick ability a 100% chance to silence the target for 2 sec." ]; i++; // Name: Improved Sprint rank[i] = [ "Gives a 50% chance to remove all Movement Impairing effects when you activate your Sprint ability.", "Gives a 100% chance to remove all Movement Impairing effects when you activate your Sprint ability." ]; i++; // Name: Lightning Reflexes rank[i] = [ "Increases your Dodge chance by 2% and gives you 4% melee haste.", "Increases your Dodge chance by 4% and gives you 7% melee haste.", "Increases your Dodge chance by 6% and gives you 10% melee haste." ]; i++; // Name: Aggression rank[i] = [ "Increases the damage of your Sinister Strike, Backstab, and Eviscerate abilities by 3%.", "Increases the damage of your Sinister Strike, Backstab, and Eviscerate abilities by 6%.", "Increases the damage of your Sinister Strike, Backstab, and Eviscerate abilities by 9%.", "Increases the damage of your Sinister Strike, Backstab, and Eviscerate abilities by 12%.", "Increases the damage of your Sinister Strike, Backstab, and Eviscerate abilities by 15%." ]; i++; // Name: Mace Specialization rank[i] = [ "Your attacks with maces ignore up to 3% of your opponent\'s armor.", "Your attacks with maces ignore up to 6% of your opponent\'s armor.", "Your attacks with maces ignore up to 9% of your opponent\'s armor.", "Your attacks with maces ignore up to 12% of your opponent\'s armor.", "Your attacks with maces ignore up to 15% of your opponent\'s armor." ]; i++; // Name: Blade Flurry rank[i] = [ "Increases your attack speed by 20%.  In addition, attacks strike an additional nearby opponent.  Lasts 15 sec." ]; i++; // Name: Sword Specialization rank[i] = [ "Gives you a 1% chance to get an extra attack on the same target after hitting your target with your Sword or Axe.", "Gives you a 2% chance to get an extra attack on the same target after hitting your target with your Sword or Axe.", "Gives you a 3% chance to get an extra attack on the same target after hitting your target with your Sword or Axe.", "Gives you a 4% chance to get an extra attack on the same target after hitting your target with your Sword or Axe.", "Gives you a 5% chance to get an extra attack on the same target after hitting your target with your Sword or Axe." ]; i++; // Name: Weapon Expertise rank[i] = [ "Increases your expertise by 5.", "Increases your expertise by 10." ]; i++; // Name: Blade Twisting rank[i] = [ "Increases the damage dealt by Sinister Strike and Backstab by 5%, and your damaging melee attacks have a 10% chance to Daze the target for 4 sec.", "Increases the damage dealt by Sinister Strike and Backstab by 10%, and your damaging melee attacks have a 10% chance to Daze the target for 8 sec." ]; i++; // Name: Vitality rank[i] = [ "Increases your Energy regeneration rate by 8%.", "Increases your Energy regeneration rate by 16%.", "Increases your Energy regeneration rate by 25%." ]; i++; // Name: Adrenaline Rush rank[i] = [ "Increases your Energy regeneration rate by 100% for 15 sec." ]; i++; // Name: Nerves of Steel rank[i] = [ "Reduces damage taken while affected by Stun and Fear effects by 15%.", "Reduces damage taken while affected by Stun and Fear effects by 30%." ]; i++; // Name: Throwing Specialization rank[i] = [ "Increases the range of Throw and Deadly Throw by 2 yards and gives your Deadly Throw and Fan of Knives abilities a 50% chance to interrupt the target for 3 sec.", "Increases the range of Throw and Deadly Throw by 4 yards and gives your Deadly Throw and Fan of Knives abilities a 100% chance to interrupt the target for 3 sec." ]; i++; // Name: Combat Potency rank[i] = [ "Gives your successful off-hand melee attacks a 20% chance to generate 3 Energy.", "Gives your successful off-hand melee attacks a 20% chance to generate 6 Energy.", "Gives your successful off-hand melee attacks a 20% chance to generate 9 Energy.", "Gives your successful off-hand melee attacks a 20% chance to generate 12 Energy.", "Gives your successful off-hand melee attacks a 20% chance to generate 15 Energy." ]; i++; // Name: Unfair Advantage rank[i] = [ "Whenever you dodge an attack you gain an Unfair Advantage, striking back for 50% of your main hand weapon\'s damage.  This cannot occur more than once per second.", "Whenever you dodge an attack you gain an Unfair Advantage, striking back for 100% of your main hand weapon\'s damage.  This cannot occur more than once per second." ]; i++; // Name: Surprise Attacks rank[i] = [ "Your finishing moves can no longer be dodged, and the damage dealt by your Sinister Strike, Backstab, Shiv, Hemorrhage and Gouge abilities is increased by 10%." ]; i++; // Name: Savage Combat rank[i] = [ "Increases your total attack power by 2% and all physical damage caused to enemies you have poisoned is increased by 2%.", "Increases your total attack power by 4% and all physical damage caused to enemies you have poisoned is increased by 4%." ]; i++; // Name: Prey on the Weak rank[i] = [ "Your critical strike damage is increased by 4% when the target has less health than you (as a percentage of total health).", "Your critical strike damage is increased by 8% when the target has less health than you (as a percentage of total health).", "Your critical strike damage is increased by 12% when the target has less health than you (as a percentage of total health).", "Your critical strike damage is increased by 16% when the target has less health than you (as a percentage of total health).", "Your critical strike damage is increased by 20% when the target has less health than you (as a percentage of total health)." ]; i++; // Name: Killing Spree rank[i] = [ "Step through the shadows from enemy to enemy within 10 yards, attacking an enemy every .5 secs with both weapons until 5 assaults are made, and increasing all damage done by 20% for the duration.  Can hit the same target multiple times.  Cannot hit invisible or stealthed targets." ]; i++; // Name: Relentless Strikes rank[i] = [ "Your finishing moves have a 4% chance per combo point to restore 25 energy.", "Your finishing moves have a 8% chance per combo point to restore 25 energy.", "Your finishing moves have a 12% chance per combo point to restore 25 energy.", "Your finishing moves have a 16% chance per combo point to restore 25 energy.", "Your finishing moves have a 20% chance per combo point to restore 25 energy." ]; i++; // Name: Master of Deception rank[i] = [ "Reduces the chance enemies have to detect you while in Stealth mode.", "Reduces the chance enemies have to detect you while in Stealth mode.  More effective than Master of Deception (Rank 1).", "Reduces the chance enemies have to detect you while in Stealth mode.  More effective than Master of Deception (Rank 2)." ]; i++; // Name: Opportunity rank[i] = [ "Increases the damage dealt with your Backstab, Mutilate, Garrote and Ambush abilities by 10%.", "Increases the damage dealt with your Backstab, Mutilate, Garrote and Ambush abilities by 20%." ]; i++; // Name: Sleight of Hand rank[i] = [ "Reduces the chance you are critically hit by melee and ranged attacks by 1% and increases the threat reduction of your Feint ability by 10%.", "Reduces the chance you are critically hit by melee and ranged attacks by 2% and increases the threat reduction of your Feint ability by 20%." ]; i++; // Name: Dirty Tricks rank[i] = [ "Increases the range of your Blind and Sap abilities by 2 yards and reduces the energy cost of your Blind and Sap abilities by 25%.", "Increases the range of your Blind and Sap abilities by 5 yards and reduces the energy cost of your Blind and Sap abilities by 50%." ]; i++; // Name: Camouflage rank[i] = [ "Increases your speed while stealthed by 5% and reduces the cooldown of your Stealth ability by 2 sec.", "Increases your speed while stealthed by 10% and reduces the cooldown of your Stealth ability by 4 sec.", "Increases your speed while stealthed by 15% and reduces the cooldown of your Stealth ability by 6 sec." ]; i++; // Name: Elusiveness rank[i] = [ "Reduces the cooldown of your Vanish and Blind abilities by 30 sec and your Cloak of Shadows ability by 15 sec.", "Reduces the cooldown of your Vanish and Blind abilities by 60 sec and your Cloak of Shadows ability by 30 sec." ]; i++; // Name: Ghostly Strike rank[i] = [ "A strike that deals 125% weapon damage and increases your chance to dodge by 15% for 7 sec.  Awards 1 combo point." ]; i++; // Name: Serrated Blades rank[i] = [ "Causes your attacks to ignore 2.67 of your target\'s Armor and increases the damage dealt by your Rupture ability by 10%.  The amount of Armor reduced increases with your level.", "Causes your attacks to ignore 5.34 of your target\'s Armor and increases the damage dealt by your Rupture ability by 20%.  The amount of Armor reduced increases with your level.", "Causes your attacks to ignore 8 of your target\'s Armor and increases the damage dealt by your Rupture ability by 30%.  The amount of Armor reduced increases with your level." ]; i++; // Name: Setup rank[i] = [ "Gives you a 33% chance to add a combo point to your target after dodging their attack or fully resisting one of their spells.  This cannot happen more than once per second.", "Gives you a 66% chance to add a combo point to your target after dodging their attack or fully resisting one of their spells.  This cannot happen more than once per second.", "Gives you a 100% chance to add a combo point to your target after dodging their attack or fully resisting one of their spells.  This cannot happen more than once per second." ]; i++; // Name: Initiative rank[i] = [ "Gives you a 33% chance to add an additional combo point to your target when using your Ambush, Garrote, or Cheap Shot ability.", "Gives you a 66% chance to add an additional combo point to your target when using your Ambush, Garrote, or Cheap Shot ability.", "Gives you a 100% chance to add an additional combo point to your target when using your Ambush, Garrote, or Cheap Shot ability." ]; i++; // Name: Improved Ambush rank[i] = [ "Increases the critical strike chance of your Ambush ability by 25%.", "Increases the critical strike chance of your Ambush ability by 50%." ]; i++; // Name: Heightened Senses rank[i] = [ "Increases your Stealth detection and reduces the chance you are hit by spells and ranged attacks by 2%.", "Increases your Stealth detection and reduces the chance you are hit by spells and ranged attacks by 4%.  More effective than Heightened Senses (Rank 1)." ]; i++; // Name: Preparation rank[i] = [ "When activated, this ability immediately finishes the cooldown on your Evasion, Sprint, Vanish, Cold Blood and Shadowstep abilities." ]; i++; // Name: Dirty Deeds rank[i] = [ "Reduces the Energy cost of your Cheap Shot and Garrote abilities by 10.  Additionally, your special abilities cause 10% more damage against targets below 35% health.", "Reduces the Energy cost of your Cheap Shot and Garrote abilities by 20.  Additionally, your special abilities cause 20% more damage against targets below 35% health." ]; i++; // Name: Hemorrhage rank[i] = [ "An instant strike that deals 110% weapon damage and causes the target to hemorrhage, increasing any Physical damage dealt to the target by up to 13.  Lasts 10 charges or 15 sec.  Awards 1 combo point." ]; i++; // Name: Master of Subtlety rank[i] = [ "Attacks made while stealthed and for 6 seconds after breaking stealth cause an additional 4% damage.", "Attacks made while stealthed and for 6 seconds after breaking stealth cause an additional 7% damage.", "Attacks made while stealthed and for 6 seconds after breaking stealth cause an additional 10% damage." ]; i++; // Name: Deadliness rank[i] = [ "Increases your attack power by 2%.", "Increases your attack power by 4%.", "Increases your attack power by 6%.", "Increases your attack power by 8%.", "Increases your attack power by 10%." ]; i++; // Name: Enveloping Shadows rank[i] = [ "Reduces the damage taken by area of effect attacks by 10%.", "Reduces the damage taken by area of effect attacks by 20%.", "Reduces the damage taken by area of effect attacks by 30%." ]; i++; // Name: Premeditation rank[i] = [ "When used, adds 2 combo points to your target.  You must add to or use those combo points within 20 sec or the combo points are lost." ]; i++; // Name: Cheat Death rank[i] = [ "You have a 33% chance that an attack which would otherwise kill you will instead reduce you to 10% of your maximum health. In addition, all damage taken will be reduced by up to 90% for 3 sec (modified by resilience).  This effect cannot occur more than once per minute.", "You have a 66% chance that an attack which would otherwise kill you will instead reduce you to 10% of your maximum health. In addition, all damage taken will be reduced by up to 90% for 3 sec (modified by resilience).  This effect cannot occur more than once per minute.", "You have a 100% chance that an attack which would otherwise kill you will instead reduce you to 10% of your maximum health. In addition, all damage taken will be reduced by up to 90% for 3 sec (modified by resilience).  This effect cannot occur more than once per minute." ]; i++; // Name: Sinister Calling rank[i] = [ "Increases your total Agility by 3% and increases the percentage damage bonus of Backstab and Hemorrhage by an additional 2%.", "Increases your total Agility by 6% and increases the percentage damage bonus of Backstab and Hemorrhage by an additional 4%.", "Increases your total Agility by 9% and increases the percentage damage bonus of Backstab and Hemorrhage by an additional 6%.", "Increases your total Agility by 12% and increases the percentage damage bonus of Backstab and Hemorrhage by an additional 8%.", "Increases your total Agility by 15% and increases the percentage damage bonus of Backstab and Hemorrhage by an additional 10%." ]; i++; // Name: Waylay rank[i] = [ "Your Ambush critical hits have a 50% chance to reduce the target\'s melee and ranged attack speed by 20%, movement speed by 70% for 8 sec.", "Your Ambush critical hits have a 100% chance to reduce the target\'s melee and ranged attack speed by 20%, movement speed by 70% for 8 sec." ]; i++; // Name: Honor Among Thieves rank[i] = [ "When anyone in your group critically hits with a damage or healing spell or ability, you have a 33% chance to gain a combo point on your current target.  This effect cannot occur more than once every second.", "When anyone in your group critically hits with a damage or healing spell or ability, you have a 66% chance to gain a combo point on your current target.  This effect cannot occur more than once every second.", "When anyone in your group critically hits with a damage or healing spell or ability, you have a 100% chance to gain a combo point on your current target.  This effect cannot occur more than once every second." ]; i++; // Name: Shadowstep rank[i] = [ "Attempts to step through the shadows and reappear behind your enemy and increases movement speed by 70% for 3 sec.  The damage of your next ability is increased by 20% and the threat caused is reduced by 50%.  Lasts 10 sec." ]; i++; // Name: Filthy Tricks rank[i] = [ "Reduces the cooldown of your Tricks of the Trade and Distract abilities by 5 secs and Preparation by 2.5 min.", "Reduces the cooldown of your Tricks of the Trade and Distract abilities by 10 secs and Preparation by 5 min." ]; i++; // Name: Slaughter from the Shadows rank[i] = [ "Reduces the energy cost of your Backstab and Ambush abilities by 3 and the energy cost of your Hemorrhage by 1.", "Reduces the energy cost of your Backstab and Ambush abilities by 6 and the energy cost of your Hemorrhage by 2.", "Reduces the energy cost of your Backstab and Ambush abilities by 9 and the energy cost of your Hemorrhage by 3.", "Reduces the energy cost of your Backstab and Ambush abilities by 12 and the energy cost of your Hemorrhage by 4.", "Reduces the energy cost of your Backstab and Ambush abilities by 15 and the energy cost of your Hemorrhage by 5." ]; i++; // Name: Shadow Dance rank[i] = [ "Enter the Shadow Dance for 6 sec, allowing the use of Sap, Garrote, Ambush, Cheap Shot, Premeditation, Pickpocket and Disarm Trap regardless of being stealthed." ]; i++;