1 //MooTools, My Object Oriented Javascript Tools. Copyright (c) 2006-2007 Valerio Proietti, <http://mad4milk.net>, MIT Style License.
7 function $defined(obj){
8 return (obj != undefined);
12 if (!$defined(obj)) return false;
13 if (obj.htmlElement) return 'element';
14 var type = typeof obj;
15 if (type == 'object' && obj.nodeName){
17 case 1: return 'element';
18 case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
21 if (type == 'object' || type == 'function'){
22 switch(obj.constructor){
23 case Array: return 'array';
24 case RegExp: return 'regexp';
25 case Class: return 'class';
27 if (typeof obj.length == 'number'){
28 if (obj.item) return 'collection';
29 if (obj.callee) return 'arguments';
37 for (var i = 0; i < arguments.length; i++){
38 for (var property in arguments[i]){
39 var ap = arguments[i][property];
40 var mp = mix[property];
41 if (mp && $type(ap) == 'object' && $type(mp) == 'object') mix[property] = $merge(mp, ap);
42 else mix[property] = ap;
48 var $extend = function(){
50 if (!args[1]) args = [this, args[0]];
51 for (var property in args[1]) args[0][property] = args[1][property];
55 var $native = function(){
56 for (var i = 0, l = arguments.length; i < l; i++){
57 arguments[i].extend = function(props){
58 for (var prop in props){
59 if (!this.prototype[prop]) this.prototype[prop] = props[prop];
60 if (!this[prop]) this[prop] = $native.generic(prop);
66 $native.generic = function(prop){
67 return function(bind){
68 return this.prototype[prop].apply(bind, Array.prototype.slice.call(arguments, 1));
72 $native(Function, Array, String, Number);
75 return !!(obj || obj === 0);
78 function $pick(obj, picked){
79 return $defined(obj) ? obj : picked;
82 function $random(min, max){
83 return Math.floor(Math.random() * (max - min + 1) + min);
87 return new Date().getTime();
90 function $clear(timer){
96 var Abstract = function(obj){
102 var Window = new Abstract(window);
103 var Document = new Abstract(document);
104 document.head = document.getElementsByTagName('head')[0];
106 window.xpath = !!(document.evaluate);
107 if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
108 else if (document.childNodes && !document.all && !navigator.taintEnabled) window.webkit = window[window.xpath ? 'webkit420' : 'webkit419'] = true;
109 else if (document.getBoxObjectFor != null) window.gecko = true;
111 window.khtml = window.webkit;
113 Object.extend = $extend;
115 if (typeof HTMLElement == 'undefined'){
116 var HTMLElement = function(){};
117 if (window.webkit) document.createElement("iframe");
118 HTMLElement.prototype = (window.webkit) ? window["[[DOMElement.prototype]]"] : {};
120 HTMLElement.prototype.htmlElement = function(){};
122 if (window.ie6) try {document.execCommand("BackgroundImageCache", false, true);} catch(e){};
124 var Class = function(properties){
125 var klass = function(){
126 return (arguments[0] !== null && this.initialize && $type(this.initialize) == 'function') ? this.initialize.apply(this, arguments) : this;
128 $extend(klass, this);
129 klass.prototype = properties;
130 klass.constructor = Class;
134 Class.empty = function(){};
138 extend: function(properties){
139 var proto = new this(null);
140 for (var property in properties){
141 var pp = proto[property];
142 proto[property] = Class.Merge(pp, properties[property]);
144 return new Class(proto);
147 implement: function(){
148 for (var i = 0, l = arguments.length; i < l; i++) $extend(this.prototype, arguments[i]);
153 Class.Merge = function(previous, current){
154 if (previous && previous != current){
155 var type = $type(current);
156 if (type != $type(previous)) return current;
159 var merged = function(){
160 this.parent = arguments.callee.parent;
161 return current.apply(this, arguments);
163 merged.parent = previous;
165 case 'object': return $merge(previous, current);
171 var Chain = new Class({
174 this.chains = this.chains || [];
175 this.chains.push(fn);
179 callChain: function(){
180 if (this.chains && this.chains.length) this.chains.shift().delay(10, this);
183 clearChain: function(){
189 var Events = new Class({
191 addEvent: function(type, fn){
192 if (fn != Class.empty){
193 this.$events = this.$events || {};
194 this.$events[type] = this.$events[type] || [];
195 this.$events[type].include(fn);
200 fireEvent: function(type, args, delay){
201 if (this.$events && this.$events[type]){
202 this.$events[type].each(function(fn){
203 fn.create({'bind': this, 'delay': delay, 'arguments': args})();
209 removeEvent: function(type, fn){
210 if (this.$events && this.$events[type]) this.$events[type].remove(fn);
216 var Options = new Class({
218 setOptions: function(){
219 this.options = $merge.apply(null, [this.options].extend(arguments));
221 for (var option in this.options){
222 if ($type(this.options[option] == 'function') && (/^on[A-Z]/).test(option)) this.addEvent(option, this.options[option]);
232 forEach: function(fn, bind){
233 for (var i = 0, j = this.length; i < j; i++) fn.call(bind, this[i], i, this);
236 filter: function(fn, bind){
238 for (var i = 0, j = this.length; i < j; i++){
239 if (fn.call(bind, this[i], i, this)) results.push(this[i]);
244 map: function(fn, bind){
246 for (var i = 0, j = this.length; i < j; i++) results[i] = fn.call(bind, this[i], i, this);
250 every: function(fn, bind){
251 for (var i = 0, j = this.length; i < j; i++){
252 if (!fn.call(bind, this[i], i, this)) return false;
257 some: function(fn, bind){
258 for (var i = 0, j = this.length; i < j; i++){
259 if (fn.call(bind, this[i], i, this)) return true;
264 indexOf: function(item, from){
265 var len = this.length;
266 for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
267 if (this[i] === item) return i;
272 copy: function(start, length){
274 if (start < 0) start = this.length + start;
275 length = length || (this.length - start);
277 for (var i = 0; i < length; i++) newArray[i] = this[start++];
281 remove: function(item){
283 var len = this.length;
285 if (this[i] === item){
295 contains: function(item, from){
296 return this.indexOf(item, from) != -1;
299 associate: function(keys){
300 var obj = {}, length = Math.min(this.length, keys.length);
301 for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
305 extend: function(array){
306 for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
310 merge: function(array){
311 for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
315 include: function(item){
316 if (!this.contains(item)) this.push(item);
320 getRandom: function(){
321 return this[$random(0, this.length - 1)] || null;
325 return this[this.length - 1] || null;
330 Array.prototype.each = Array.prototype.forEach;
331 Array.each = Array.forEach;
334 return Array.copy(array);
337 function $each(iterable, fn, bind){
338 if (iterable && typeof iterable.length == 'number' && $type(iterable) != 'object'){
339 Array.forEach(iterable, fn, bind);
341 for (var name in iterable) fn.call(bind || iterable, iterable[name], name);
345 Array.prototype.test = Array.prototype.contains;
349 test: function(regex, params){
350 return (($type(regex) == 'string') ? new RegExp(regex, params) : regex).test(this);
354 return parseInt(this, 10);
358 return parseFloat(this);
361 camelCase: function(){
362 return this.replace(/-\D/g, function(match){
363 return match.charAt(1).toUpperCase();
367 hyphenate: function(){
368 return this.replace(/\w[A-Z]/g, function(match){
369 return (match.charAt(0) + '-' + match.charAt(1).toLowerCase());
373 capitalize: function(){
374 return this.replace(/\b[a-z]/g, function(match){
375 return match.toUpperCase();
380 return this.replace(/^\s+|\s+$/g, '');
384 return this.replace(/\s{2,}/g, ' ').trim();
387 rgbToHex: function(array){
388 var rgb = this.match(/\d{1,3}/g);
389 return (rgb) ? rgb.rgbToHex(array) : false;
392 hexToRgb: function(array){
393 var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
394 return (hex) ? hex.slice(1).hexToRgb(array) : false;
397 contains: function(string, s){
398 return (s) ? (s + this + s).indexOf(s + string + s) > -1 : this.indexOf(string) > -1;
401 escapeRegExp: function(){
402 return this.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
409 rgbToHex: function(array){
410 if (this.length < 3) return false;
411 if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
413 for (var i = 0; i < 3; i++){
414 var bit = (this[i] - 0).toString(16);
415 hex.push((bit.length == 1) ? '0' + bit : bit);
417 return array ? hex : '#' + hex.join('');
420 hexToRgb: function(array){
421 if (this.length != 3) return false;
423 for (var i = 0; i < 3; i++){
424 rgb.push(parseInt((this[i].length == 1) ? this[i] + this[i] : this[i], 16));
426 return array ? rgb : 'rgb(' + rgb.join(',') + ')';
433 create: function(options){
443 if ($chk(options.arguments) && $type(options.arguments) != 'array') options.arguments = [options.arguments];
444 return function(event){
447 event = event || window.event;
448 args = [(options.event === true) ? event : new options.event(event)];
449 if (options.arguments) args.extend(options.arguments);
451 else args = options.arguments || arguments;
452 var returns = function(){
453 return fn.apply($pick(options.bind, fn), args);
455 if (options.delay) return setTimeout(returns, options.delay);
456 if (options.periodical) return setInterval(returns, options.periodical);
457 if (options.attempt) try {return returns();} catch(err){return false;};
462 pass: function(args, bind){
463 return this.create({'arguments': args, 'bind': bind});
466 attempt: function(args, bind){
467 return this.create({'arguments': args, 'bind': bind, 'attempt': true})();
470 bind: function(bind, args){
471 return this.create({'bind': bind, 'arguments': args});
474 bindAsEventListener: function(bind, args){
475 return this.create({'bind': bind, 'event': true, 'arguments': args});
478 delay: function(delay, bind, args){
479 return this.create({'delay': delay, 'bind': bind, 'arguments': args})();
482 periodical: function(interval, bind, args){
483 return this.create({'periodical': interval, 'bind': bind, 'arguments': args})();
491 return parseInt(this);
495 return parseFloat(this);
498 limit: function(min, max){
499 return Math.min(max, Math.max(min, this));
502 round: function(precision){
503 precision = Math.pow(10, precision || 0);
504 return Math.round(this * precision) / precision;
508 for (var i = 0; i < this; i++) fn(i);
513 var Element = new Class({
515 initialize: function(el, props){
516 if ($type(el) == 'string'){
517 if (window.ie && props && (props.name || props.type)){
518 var name = (props.name) ? ' name="' + props.name + '"' : '';
519 var type = (props.type) ? ' type="' + props.type + '"' : '';
522 el = '<' + el + name + type + '>';
524 el = document.createElement(el);
527 return (!props || !el) ? el : el.set(props);
532 var Elements = new Class({
534 initialize: function(elements){
535 return (elements) ? $extend(elements, this) : this;
540 Elements.extend = function(props){
541 for (var prop in props){
542 this.prototype[prop] = props[prop];
543 this[prop] = $native.generic(prop);
548 if (!el) return null;
549 if (el.htmlElement) return Garbage.collect(el);
550 if ([window, document].contains(el)) return el;
551 var type = $type(el);
552 if (type == 'string'){
553 el = document.getElementById(el);
554 type = (el) ? 'element' : false;
556 if (type != 'element') return null;
557 if (el.htmlElement) return Garbage.collect(el);
558 if (['object', 'embed'].contains(el.tagName.toLowerCase())) return el;
559 $extend(el, Element.prototype);
560 el.htmlElement = function(){};
561 return Garbage.collect(el);
564 document.getElementsBySelector = document.getElementsByTagName;
568 for (var i = 0, j = arguments.length; i < j; i++){
569 var selector = arguments[i];
570 switch($type(selector)){
571 case 'element': elements.push(selector);
572 case 'boolean': break;
574 case 'string': selector = document.getElementsBySelector(selector, true);
575 default: elements.extend(selector);
578 return $$.unique(elements);
581 $$.unique = function(array){
583 for (var i = 0, l = array.length; i < l; i++){
584 if (array[i].$included) continue;
585 var element = $(array[i]);
586 if (element && !element.$included){
587 element.$included = true;
588 elements.push(element);
591 for (var n = 0, d = elements.length; n < d; n++) elements[n].$included = null;
592 return new Elements(elements);
595 Elements.Multi = function(property){
597 var args = arguments;
600 for (var i = 0, j = this.length, returns; i < j; i++){
601 returns = this[i][property].apply(this[i], args);
602 if ($type(returns) != 'element') elements = false;
605 return (elements) ? $$.unique(items) : items;
609 Element.extend = function(properties){
610 for (var property in properties){
611 HTMLElement.prototype[property] = properties[property];
612 Element.prototype[property] = properties[property];
613 Element[property] = $native.generic(property);
614 var elementsProperty = (Array.prototype[property]) ? property + 'Elements' : property;
615 Elements.prototype[elementsProperty] = Elements.Multi(property);
621 set: function(props){
622 for (var prop in props){
623 var val = props[prop];
625 case 'styles': this.setStyles(val); break;
626 case 'events': if (this.addEvents) this.addEvents(val); break;
627 case 'properties': this.setProperties(val); break;
628 default: this.setProperty(prop, val);
634 inject: function(el, where){
637 case 'before': el.parentNode.insertBefore(this, el); break;
639 var next = el.getNext();
640 if (!next) el.parentNode.appendChild(this);
641 else el.parentNode.insertBefore(this, next);
644 var first = el.firstChild;
646 el.insertBefore(this, first);
649 default: el.appendChild(this);
654 injectBefore: function(el){
655 return this.inject(el, 'before');
658 injectAfter: function(el){
659 return this.inject(el, 'after');
662 injectInside: function(el){
663 return this.inject(el, 'bottom');
666 injectTop: function(el){
667 return this.inject(el, 'top');
672 $each(arguments, function(argument){
673 elements = elements.concat(argument);
675 $$(elements).inject(this);
680 return this.parentNode.removeChild(this);
683 clone: function(contents){
684 var el = $(this.cloneNode(contents !== false));
685 if (!el.$events) return el;
687 for (var type in this.$events) el.$events[type] = {
688 'keys': $A(this.$events[type].keys),
689 'values': $A(this.$events[type].values)
691 return el.removeEvents();
694 replaceWith: function(el){
696 this.parentNode.replaceChild(el, this);
700 appendText: function(text){
701 this.appendChild(document.createTextNode(text));
705 hasClass: function(className){
706 return this.className.contains(className, ' ');
709 addClass: function(className){
710 if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
714 removeClass: function(className){
715 this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1').clean();
719 toggleClass: function(className){
720 return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
723 setStyle: function(property, value){
725 case 'opacity': return this.setOpacity(parseFloat(value));
726 case 'float': property = (window.ie) ? 'styleFloat' : 'cssFloat';
728 property = property.camelCase();
729 switch($type(value)){
730 case 'number': if (!['zIndex', 'zoom'].contains(property)) value += 'px'; break;
731 case 'array': value = 'rgb(' + value.join(',') + ')';
733 this.style[property] = value;
737 setStyles: function(source){
738 switch($type(source)){
739 case 'object': Element.setMany(this, 'setStyle', source); break;
740 case 'string': this.style.cssText = source;
745 setOpacity: function(opacity){
747 if (this.style.visibility != "hidden") this.style.visibility = "hidden";
749 if (this.style.visibility != "visible") this.style.visibility = "visible";
751 if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
752 if (window.ie) this.style.filter = (opacity == 1) ? '' : "alpha(opacity=" + opacity * 100 + ")";
753 this.style.opacity = this.$tmp.opacity = opacity;
757 getStyle: function(property){
758 property = property.camelCase();
759 var result = this.style[property];
761 if (property == 'opacity') return this.$tmp.opacity;
763 for (var style in Element.Styles){
764 if (property == style){
765 Element.Styles[style].each(function(s){
766 var style = this.getStyle(s);
767 result.push(parseInt(style) ? style : '0px');
769 if (property == 'border'){
770 var every = result.every(function(bit){
771 return (bit == result[0]);
773 return (every) ? result[0] : false;
775 return result.join(' ');
778 if (property.contains('border')){
779 if (Element.Styles.border.contains(property)){
780 return ['Width', 'Style', 'Color'].map(function(p){
781 return this.getStyle(property + p);
783 } else if (Element.borderShort.contains(property)){
784 return ['Top', 'Right', 'Bottom', 'Left'].map(function(p){
785 return this.getStyle('border' + p + property.replace('border', ''));
789 if (document.defaultView) result = document.defaultView.getComputedStyle(this, null).getPropertyValue(property.hyphenate());
790 else if (this.currentStyle) result = this.currentStyle[property];
792 if (window.ie) result = Element.fixStyle(property, result, this);
793 if (result && property.test(/color/i) && result.contains('rgb')){
794 return result.split('rgb').splice(1,4).map(function(color){
795 return color.rgbToHex();
801 getStyles: function(){
802 return Element.getMany(this, 'getStyle', arguments);
805 walk: function(brother, start){
806 brother += 'Sibling';
807 var el = (start) ? this[start] : this[brother];
808 while (el && $type(el) != 'element') el = el[brother];
812 getPrevious: function(){
813 return this.walk('previous');
817 return this.walk('next');
820 getFirst: function(){
821 return this.walk('next', 'firstChild');
825 return this.walk('previous', 'lastChild');
828 getParent: function(){
829 return $(this.parentNode);
832 getChildren: function(){
833 return $$(this.childNodes);
836 hasChild: function(el){
837 return !!$A(this.getElementsByTagName('*')).contains(el);
840 getProperty: function(property){
841 var index = Element.Properties[property];
842 if (index) return this[index];
843 var flag = Element.PropertiesIFlag[property] || 0;
844 if (!window.ie || flag) return this.getAttribute(property, flag);
845 var node = this.attributes[property];
846 return (node) ? node.nodeValue : null;
849 removeProperty: function(property){
850 var index = Element.Properties[property];
851 if (index) this[index] = '';
852 else this.removeAttribute(property);
856 getProperties: function(){
857 return Element.getMany(this, 'getProperty', arguments);
860 setProperty: function(property, value){
861 var index = Element.Properties[property];
862 if (index) this[index] = value;
863 else this.setAttribute(property, value);
867 setProperties: function(source){
868 return Element.setMany(this, 'setProperty', source);
872 this.innerHTML = $A(arguments).join('');
876 setText: function(text){
877 var tag = this.getTag();
878 if (['style', 'script'].contains(tag)){
880 if (tag == 'style') this.styleSheet.cssText = text;
881 else if (tag == 'script') this.setProperty('text', text);
884 this.removeChild(this.firstChild);
885 return this.appendText(text);
888 this[$defined(this.innerText) ? 'innerText' : 'textContent'] = text;
893 var tag = this.getTag();
894 if (['style', 'script'].contains(tag)){
896 if (tag == 'style') return this.styleSheet.cssText;
897 else if (tag == 'script') return this.getProperty('text');
899 return this.innerHTML;
902 return ($pick(this.innerText, this.textContent));
906 return this.tagName.toLowerCase();
910 Garbage.trash(this.getElementsByTagName('*'));
911 return this.setHTML('');
916 Element.fixStyle = function(property, result, element){
917 if ($chk(parseInt(result))) return result;
918 if (['height', 'width'].contains(property)){
919 var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'];
921 values.each(function(value){
922 size += element.getStyle('border-' + value + '-width').toInt() + element.getStyle('padding-' + value).toInt();
924 return element['offset' + property.capitalize()] - size + 'px';
925 } else if (property.test(/border(.+)Width|margin|padding/)){
931 Element.Styles = {'border': [], 'padding': [], 'margin': []};
932 ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
933 for (var style in Element.Styles) Element.Styles[style].push(style + direction);
936 Element.borderShort = ['borderWidth', 'borderStyle', 'borderColor'];
938 Element.getMany = function(el, method, keys){
940 $each(keys, function(key){
941 result[key] = el[method](key);
946 Element.setMany = function(el, method, pairs){
947 for (var key in pairs) el[method](key, pairs[key]);
951 Element.Properties = new Abstract({
952 'class': 'className', 'for': 'htmlFor', 'colspan': 'colSpan', 'rowspan': 'rowSpan',
953 'accesskey': 'accessKey', 'tabindex': 'tabIndex', 'maxlength': 'maxLength',
954 'readonly': 'readOnly', 'frameborder': 'frameBorder', 'value': 'value',
955 'disabled': 'disabled', 'checked': 'checked', 'multiple': 'multiple', 'selected': 'selected'
957 Element.PropertiesIFlag = {
963 addListener: function(type, fn){
964 if (this.addEventListener) this.addEventListener(type, fn, false);
965 else this.attachEvent('on' + type, fn);
969 removeListener: function(type, fn){
970 if (this.removeEventListener) this.removeEventListener(type, fn, false);
971 else this.detachEvent('on' + type, fn);
977 window.extend(Element.Methods.Listeners);
978 document.extend(Element.Methods.Listeners);
979 Element.extend(Element.Methods.Listeners);
985 collect: function(el){
987 Garbage.elements.push(el);
988 el.$tmp = {'opacity': 1};
993 trash: function(elements){
994 for (var i = 0, j = elements.length, el; i < j; i++){
995 if (!(el = elements[i]) || !el.$tmp) continue;
996 if (el.$events) el.fireEvent('trash').removeEvents();
997 for (var p in el.$tmp) el.$tmp[p] = null;
998 for (var d in Element.prototype) el[d] = null;
999 Garbage.elements[Garbage.elements.indexOf(el)] = null;
1000 el.htmlElement = el.$tmp = el = null;
1002 Garbage.elements.remove(null);
1006 Garbage.collect(window);
1007 Garbage.collect(document);
1008 Garbage.trash(Garbage.elements);
1013 window.addListener('beforeunload', function(){
1014 window.addListener('unload', Garbage.empty);
1015 if (window.ie) window.addListener('unload', CollectGarbage);
1018 var Event = new Class({
1020 initialize: function(event){
1021 if (event && event.$extended) return event;
1022 this.$extended = true;
1023 event = event || window.event;
1025 this.type = event.type;
1026 this.target = event.target || event.srcElement;
1027 if (this.target.nodeType == 3) this.target = this.target.parentNode;
1028 this.shift = event.shiftKey;
1029 this.control = event.ctrlKey;
1030 this.alt = event.altKey;
1031 this.meta = event.metaKey;
1032 if (['DOMMouseScroll', 'mousewheel'].contains(this.type)){
1033 this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
1034 } else if (this.type.contains('key')){
1035 this.code = event.which || event.keyCode;
1036 for (var name in Event.keys){
1037 if (Event.keys[name] == this.code){
1042 if (this.type == 'keydown'){
1043 var fKey = this.code - 111;
1044 if (fKey > 0 && fKey < 13) this.key = 'f' + fKey;
1046 this.key = this.key || String.fromCharCode(this.code).toLowerCase();
1047 } else if (this.type.test(/(click|mouse|menu)/)){
1049 'x': event.pageX || event.clientX + document.documentElement.scrollLeft,
1050 'y': event.pageY || event.clientY + document.documentElement.scrollTop
1053 'x': event.pageX ? event.pageX - window.pageXOffset : event.clientX,
1054 'y': event.pageY ? event.pageY - window.pageYOffset : event.clientY
1056 this.rightClick = (event.which == 3) || (event.button == 2);
1058 case 'mouseover': this.relatedTarget = event.relatedTarget || event.fromElement; break;
1059 case 'mouseout': this.relatedTarget = event.relatedTarget || event.toElement;
1061 this.fixRelatedTarget();
1067 return this.stopPropagation().preventDefault();
1070 stopPropagation: function(){
1071 if (this.event.stopPropagation) this.event.stopPropagation();
1072 else this.event.cancelBubble = true;
1076 preventDefault: function(){
1077 if (this.event.preventDefault) this.event.preventDefault();
1078 else this.event.returnValue = false;
1086 relatedTarget: function(){
1087 if (this.relatedTarget && this.relatedTarget.nodeType == 3) this.relatedTarget = this.relatedTarget.parentNode;
1090 relatedTargetGecko: function(){
1091 try {Event.fix.relatedTarget.call(this);} catch(e){this.relatedTarget = this.target;}
1096 Event.prototype.fixRelatedTarget = (window.gecko) ? Event.fix.relatedTargetGecko : Event.fix.relatedTarget;
1098 Event.keys = new Abstract({
1111 Element.Methods.Events = {
1113 addEvent: function(type, fn){
1114 this.$events = this.$events || {};
1115 this.$events[type] = this.$events[type] || {'keys': [], 'values': []};
1116 if (this.$events[type].keys.contains(fn)) return this;
1117 this.$events[type].keys.push(fn);
1118 var realType = type;
1119 var custom = Element.Events[type];
1121 if (custom.add) custom.add.call(this, fn);
1122 if (custom.map) fn = custom.map;
1123 if (custom.type) realType = custom.type;
1125 if (!this.addEventListener) fn = fn.create({'bind': this, 'event': true});
1126 this.$events[type].values.push(fn);
1127 return (Element.NativeEvents.contains(realType)) ? this.addListener(realType, fn) : this;
1130 removeEvent: function(type, fn){
1131 if (!this.$events || !this.$events[type]) return this;
1132 var pos = this.$events[type].keys.indexOf(fn);
1133 if (pos == -1) return this;
1134 var key = this.$events[type].keys.splice(pos,1)[0];
1135 var value = this.$events[type].values.splice(pos,1)[0];
1136 var custom = Element.Events[type];
1138 if (custom.remove) custom.remove.call(this, fn);
1139 if (custom.type) type = custom.type;
1141 return (Element.NativeEvents.contains(type)) ? this.removeListener(type, value) : this;
1144 addEvents: function(source){
1145 return Element.setMany(this, 'addEvent', source);
1148 removeEvents: function(type){
1149 if (!this.$events) return this;
1151 for (var evType in this.$events) this.removeEvents(evType);
1152 this.$events = null;
1153 } else if (this.$events[type]){
1154 this.$events[type].keys.each(function(fn){
1155 this.removeEvent(type, fn);
1157 this.$events[type] = null;
1162 fireEvent: function(type, args, delay){
1163 if (this.$events && this.$events[type]){
1164 this.$events[type].keys.each(function(fn){
1165 fn.create({'bind': this, 'delay': delay, 'arguments': args})();
1171 cloneEvents: function(from, type){
1172 if (!from.$events) return this;
1174 for (var evType in from.$events) this.cloneEvents(from, evType);
1175 } else if (from.$events[type]){
1176 from.$events[type].keys.each(function(fn){
1177 this.addEvent(type, fn);
1185 window.extend(Element.Methods.Events);
1186 document.extend(Element.Methods.Events);
1187 Element.extend(Element.Methods.Events);
1189 Element.Events = new Abstract({
1193 map: function(event){
1194 event = new Event(event);
1195 if (event.relatedTarget != this && !this.hasChild(event.relatedTarget)) this.fireEvent('mouseenter', event);
1201 map: function(event){
1202 event = new Event(event);
1203 if (event.relatedTarget != this && !this.hasChild(event.relatedTarget)) this.fireEvent('mouseleave', event);
1208 type: (window.gecko) ? 'DOMMouseScroll' : 'mousewheel'
1213 Element.NativeEvents = [
1214 'click', 'dblclick', 'mouseup', 'mousedown',
1215 'mousewheel', 'DOMMouseScroll',
1216 'mouseover', 'mouseout', 'mousemove',
1217 'keydown', 'keypress', 'keyup',
1218 'load', 'unload', 'beforeunload', 'resize', 'move',
1219 'focus', 'blur', 'change', 'submit', 'reset', 'select',
1220 'error', 'abort', 'contextmenu', 'scroll'
1225 bindWithEvent: function(bind, args){
1226 return this.create({'bind': bind, 'arguments': args, 'event': Event});
1233 filterByTag: function(tag){
1234 return new Elements(this.filter(function(el){
1235 return (Element.getTag(el) == tag);
1239 filterByClass: function(className, nocash){
1240 var elements = this.filter(function(el){
1241 return (el.className && el.className.contains(className, ' '));
1243 return (nocash) ? elements : new Elements(elements);
1246 filterById: function(id, nocash){
1247 var elements = this.filter(function(el){
1248 return (el.id == id);
1250 return (nocash) ? elements : new Elements(elements);
1253 filterByAttribute: function(name, operator, value, nocash){
1254 var elements = this.filter(function(el){
1255 var current = Element.getProperty(el, name);
1256 if (!current) return false;
1257 if (!operator) return true;
1259 case '=': return (current == value);
1260 case '*=': return (current.contains(value));
1261 case '^=': return (current.substr(0, value.length) == value);
1262 case '$=': return (current.substr(current.length - value.length) == value);
1263 case '!=': return (current != value);
1264 case '~=': return current.contains(value, ' ');
1268 return (nocash) ? elements : new Elements(elements);
1273 function $E(selector, filter){
1274 return ($(filter) || document).getElement(selector);
1277 function $ES(selector, filter){
1278 return ($(filter) || document).getElementsBySelector(selector);
1283 'regexp': /^(\w*|\*)(?:#([\w-]+)|\.([\w-]+))?(?:\[(\w+)(?:([!*^$]?=)["']?([^"'\]]*)["']?)?])?$/,
1287 getParam: function(items, context, param, i){
1288 var temp = [context.namespaceURI ? 'xhtml:' : '', param[1]];
1289 if (param[2]) temp.push('[@id="', param[2], '"]');
1290 if (param[3]) temp.push('[contains(concat(" ", @class, " "), " ', param[3], ' ")]');
1292 if (param[5] && param[6]){
1294 case '*=': temp.push('[contains(@', param[4], ', "', param[6], '")]'); break;
1295 case '^=': temp.push('[starts-with(@', param[4], ', "', param[6], '")]'); break;
1296 case '$=': temp.push('[substring(@', param[4], ', string-length(@', param[4], ') - ', param[6].length, ' + 1) = "', param[6], '"]'); break;
1297 case '=': temp.push('[@', param[4], '="', param[6], '"]'); break;
1298 case '!=': temp.push('[@', param[4], '!="', param[6], '"]');
1301 temp.push('[@', param[4], ']');
1304 items.push(temp.join(''));
1308 getItems: function(items, context, nocash){
1310 var xpath = document.evaluate('.//' + items.join('//'), context, $$.shared.resolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
1311 for (var i = 0, j = xpath.snapshotLength; i < j; i++) elements.push(xpath.snapshotItem(i));
1312 return (nocash) ? elements : new Elements(elements.map($));
1319 getParam: function(items, context, param, i){
1322 var el = context.getElementById(param[2]);
1323 if (!el || ((param[1] != '*') && (Element.getTag(el) != param[1]))) return false;
1326 items = $A(context.getElementsByTagName(param[1]));
1329 items = $$.shared.getElementsByTagName(items, param[1]);
1330 if (param[2]) items = Elements.filterById(items, param[2], true);
1332 if (param[3]) items = Elements.filterByClass(items, param[3], true);
1333 if (param[4]) items = Elements.filterByAttribute(items, param[4], param[5], param[6], true);
1337 getItems: function(items, context, nocash){
1338 return (nocash) ? items : $$.unique(items);
1343 resolver: function(prefix){
1344 return (prefix == 'xhtml') ? 'http://www.w3.org/1999/xhtml' : false;
1347 getElementsByTagName: function(context, tagName){
1349 for (var i = 0, j = context.length; i < j; i++) found.extend(context[i].getElementsByTagName(tagName));
1355 $$.shared.method = (window.xpath) ? 'xpath' : 'normal';
1357 Element.Methods.Dom = {
1359 getElements: function(selector, nocash){
1361 selector = selector.trim().split(' ');
1362 for (var i = 0, j = selector.length; i < j; i++){
1363 var sel = selector[i];
1364 var param = sel.match($$.shared.regexp);
1366 param[1] = param[1] || '*';
1367 var temp = $$.shared[$$.shared.method].getParam(items, this, param, i);
1371 return $$.shared[$$.shared.method].getItems(items, this, nocash);
1374 getElement: function(selector){
1375 return $(this.getElements(selector, true)[0] || false);
1378 getElementsBySelector: function(selector, nocash){
1380 selector = selector.split(',');
1381 for (var i = 0, j = selector.length; i < j; i++) elements = elements.concat(this.getElements(selector[i], true));
1382 return (nocash) ? elements : $$.unique(elements);
1389 getElementById: function(id){
1390 var el = document.getElementById(id);
1391 if (!el) return false;
1392 for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
1393 if (!parent) return false;
1398 getElementsByClassName: function(className){
1399 return this.getElements('.' + className);
1404 document.extend(Element.Methods.Dom);
1405 Element.extend(Element.Methods.Dom);
1409 getValue: function(){
1410 switch(this.getTag()){
1413 $each(this.options, function(option){
1414 if (option.selected) values.push($pick(option.value, option.text));
1416 return (this.multiple) ? values : values[0];
1417 case 'input': if (!(this.checked && ['checkbox', 'radio'].contains(this.type)) && !['hidden', 'text', 'password'].contains(this.type)) break;
1418 case 'textarea': return this.value;
1423 getFormElements: function(){
1424 return $$(this.getElementsByTagName('input'), this.getElementsByTagName('select'), this.getElementsByTagName('textarea'));
1427 toQueryString: function(){
1428 var queryString = [];
1429 this.getFormElements().each(function(el){
1431 var value = el.getValue();
1432 if (value === false || !name || el.disabled) return;
1433 var qs = function(val){
1434 queryString.push(name + '=' + encodeURIComponent(val));
1436 if ($type(value) == 'array') value.each(qs);
1439 return queryString.join('&');
1446 scrollTo: function(x, y){
1447 this.scrollLeft = x;
1451 getSize: function(){
1453 'scroll': {'x': this.scrollLeft, 'y': this.scrollTop},
1454 'size': {'x': this.offsetWidth, 'y': this.offsetHeight},
1455 'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight}
1459 getPosition: function(overflown){
1460 overflown = overflown || [];
1461 var el = this, left = 0, top = 0;
1463 left += el.offsetLeft || 0;
1464 top += el.offsetTop || 0;
1465 el = el.offsetParent;
1467 overflown.each(function(element){
1468 left -= element.scrollLeft || 0;
1469 top -= element.scrollTop || 0;
1471 return {'x': left, 'y': top};
1474 getTop: function(overflown){
1475 return this.getPosition(overflown).y;
1478 getLeft: function(overflown){
1479 return this.getPosition(overflown).x;
1482 getCoordinates: function(overflown){
1483 var position = this.getPosition(overflown);
1485 'width': this.offsetWidth,
1486 'height': this.offsetHeight,
1490 obj.right = obj.left + obj.width;
1491 obj.bottom = obj.top + obj.height;
1497 Element.Events.domready = {
1504 var domReady = function(){
1505 if (window.loaded) return;
1506 window.loaded = true;
1507 window.timer = $clear(window.timer);
1508 this.fireEvent('domready');
1510 if (document.readyState && window.webkit){
1511 window.timer = function(){
1512 if (['loaded','complete'].contains(document.readyState)) domReady();
1514 } else if (document.readyState && window.ie){
1515 if (!$('ie_ready')){
1516 var src = (window.location.protocol == 'https:') ? '://0' : 'javascript:void(0)';
1517 document.write('<script id="ie_ready" defer src="' + src + '"><\/script>');
1518 $('ie_ready').onreadystatechange = function(){
1519 if (this.readyState == 'complete') domReady();
1523 window.addListener("load", domReady);
1524 document.addListener("DOMContentLoaded", domReady);
1530 window.onDomReady = function(fn){
1531 return this.addEvent('domready', fn);
1536 getWidth: function(){
1537 if (this.webkit419) return this.innerWidth;
1538 if (this.opera) return document.body.clientWidth;
1539 return document.documentElement.clientWidth;
1542 getHeight: function(){
1543 if (this.webkit419) return this.innerHeight;
1544 if (this.opera) return document.body.clientHeight;
1545 return document.documentElement.clientHeight;
1548 getScrollWidth: function(){
1549 if (this.ie) return Math.max(document.documentElement.offsetWidth, document.documentElement.scrollWidth);
1550 if (this.webkit) return document.body.scrollWidth;
1551 return document.documentElement.scrollWidth;
1554 getScrollHeight: function(){
1555 if (this.ie) return Math.max(document.documentElement.offsetHeight, document.documentElement.scrollHeight);
1556 if (this.webkit) return document.body.scrollHeight;
1557 return document.documentElement.scrollHeight;
1560 getScrollLeft: function(){
1561 return this.pageXOffset || document.documentElement.scrollLeft;
1564 getScrollTop: function(){
1565 return this.pageYOffset || document.documentElement.scrollTop;
1568 getSize: function(){
1570 'size': {'x': this.getWidth(), 'y': this.getHeight()},
1571 'scrollSize': {'x': this.getScrollWidth(), 'y': this.getScrollHeight()},
1572 'scroll': {'x': this.getScrollLeft(), 'y': this.getScrollTop()}
1575 getPosition: function(){return {'x': 0, 'y': 0};}