Merge pull request #2789 from SergioCrisostomo/upgrade-karma-sauce-launcher
[mootools.git] / Source / Element / Element.js
bloba3911252ffc20327282034fd5e29ea22103c12be
1 /*
2 ---
4 name: Element
6 description: 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.
8 license: MIT-style license.
10 requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
12 provides: [Element, Elements, $, $$, IFrame, Selectors]
14 ...
17 var Element = this.Element = function(tag, props){
18         var konstructor = Element.Constructors[tag];
19         if (konstructor) return konstructor(props);
20         if (typeof tag != 'string') return document.id(tag).set(props);
22         if (!props) props = {};
24         if (!(/^[\w-]+$/).test(tag)){
25                 var parsed = Slick.parse(tag).expressions[0][0];
26                 tag = (parsed.tag == '*') ? 'div' : parsed.tag;
27                 if (parsed.id && props.id == null) props.id = parsed.id;
29                 var attributes = parsed.attributes;
30                 if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
31                         attr = attributes[i];
32                         if (props[attr.key] != null) continue;
34                         if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
35                         else if (!attr.value && !attr.operator) props[attr.key] = true;
36                 }
38                 if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
39         }
41         return document.newElement(tag, props);
45 if (Browser.Element){
46         Element.prototype = Browser.Element.prototype;
47         // IE8 and IE9 require the wrapping.
48         Element.prototype._fireEvent = (function(fireEvent){
49                 return function(type, event){
50                         return fireEvent.call(this, type, event);
51                 };
52         })(Element.prototype.fireEvent);
55 new Type('Element', Element).mirror(function(name){
56         if (Array.prototype[name]) return;
58         var obj = {};
59         obj[name] = function(){
60                 var results = [], args = arguments, elements = true;
61                 for (var i = 0, l = this.length; i < l; i++){
62                         var element = this[i], result = results[i] = element[name].apply(element, args);
63                         elements = (elements && typeOf(result) == 'element');
64                 }
65                 return (elements) ? new Elements(results) : results;
66         };
68         Elements.implement(obj);
69 });
71 if (!Browser.Element){
72         Element.parent = Object;
74         Element.Prototype = {
75                 '$constructor': Element,
76                 '$family': Function.convert('element').hide()
77         };
79         Element.mirror(function(name, method){
80                 Element.Prototype[name] = method;
81         });
84 Element.Constructors = {};
86 //<1.2compat>
88 Element.Constructors = new Hash;
90 //</1.2compat>
92 var IFrame = new Type('IFrame', function(){
93         var params = Array.link(arguments, {
94                 properties: Type.isObject,
95                 iframe: function(obj){
96                         return (obj != null);
97                 }
98         });
100         var props = params.properties || {}, iframe;
101         if (params.iframe) iframe = document.id(params.iframe);
102         var onload = props.onload || function(){};
103         delete props.onload;
104         props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
105         iframe = new Element(iframe || 'iframe', props);
107         var onLoad = function(){
108                 onload.call(iframe.contentWindow);
109         };
111         if (window.frames[props.id]) onLoad();
112         else iframe.addListener('load', onLoad);
113         return iframe;
116 var Elements = this.Elements = function(nodes){
117         if (nodes && nodes.length){
118                 var uniques = {}, node;
119                 for (var i = 0; node = nodes[i++];){
120                         var uid = Slick.uidOf(node);
121                         if (!uniques[uid]){
122                                 uniques[uid] = true;
123                                 this.push(node);
124                         }
125                 }
126         }
129 Elements.prototype = {length: 0};
130 Elements.parent = Array;
132 new Type('Elements', Elements).implement({
134         filter: function(filter, bind){
135                 if (!filter) return this;
136                 return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
137                         return item.match(filter);
138                 } : filter, bind));
139         }.protect(),
141         push: function(){
142                 var length = this.length;
143                 for (var i = 0, l = arguments.length; i < l; i++){
144                         var item = document.id(arguments[i]);
145                         if (item) this[length++] = item;
146                 }
147                 return (this.length = length);
148         }.protect(),
150         unshift: function(){
151                 var items = [];
152                 for (var i = 0, l = arguments.length; i < l; i++){
153                         var item = document.id(arguments[i]);
154                         if (item) items.push(item);
155                 }
156                 return Array.prototype.unshift.apply(this, items);
157         }.protect(),
159         concat: function(){
160                 var newElements = new Elements(this);
161                 for (var i = 0, l = arguments.length; i < l; i++){
162                         var item = arguments[i];
163                         if (Type.isEnumerable(item)) newElements.append(item);
164                         else newElements.push(item);
165                 }
166                 return newElements;
167         }.protect(),
169         append: function(collection){
170                 for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
171                 return this;
172         }.protect(),
174         empty: function(){
175                 while (this.length) delete this[--this.length];
176                 return this;
177         }.protect()
181 //<1.2compat>
183 Elements.alias('extend', 'append');
185 //</1.2compat>
187 (function(){
189 // FF, IE
190 var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
192 splice.call(object, 1, 1);
193 if (object[1] == 1) Elements.implement('splice', function(){
194         var length = this.length;
195         var result = splice.apply(this, arguments);
196         while (length >= this.length) delete this[length--];
197         return result;
198 }.protect());
200 Array.forEachMethod(function(method, name){
201         Elements.implement(name, method);
204 Array.mirror(Elements);
206 /*<ltIE8>*/
207 var createElementAcceptsHTML;
208 try {
209         createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
210 } catch (e){}
212 var escapeQuotes = function(html){
213         return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
215 /*</ltIE8>*/
217 /*<ltIE9>*/
218 // #2479 - IE8 Cannot set HTML of style element
219 var canChangeStyleHTML = (function(){
220         var div = document.createElement('style'),
221                 flag = false;
222         try {
223                 div.innerHTML = '#justTesing{margin: 0px;}';
224                 flag = !!div.innerHTML;
225         } catch (e){}
226         return flag;
227 })();
228 /*</ltIE9>*/
230 Document.implement({
232         newElement: function(tag, props){
233                 if (props){
234                         if (props.checked != null) props.defaultChecked = props.checked;
235                         if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
236                         /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
237                         if (!canChangeStyleHTML && tag == 'style'){
238                                 var styleElement = document.createElement('style');
239                                 styleElement.setAttribute('type', 'text/css');
240                                 if (props.type) delete props.type;
241                                 return this.id(styleElement).set(props);
242                         }
243                         /*</ltIE9>*/
244                         /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
245                         if (createElementAcceptsHTML){
246                                 tag = '<' + tag;
247                                 if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
248                                 if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
249                                 tag += '>';
250                                 delete props.name;
251                                 delete props.type;
252                         }
253                         /*</ltIE8>*/
254                 }
255                 return this.id(this.createElement(tag)).set(props);
256         }
260 })();
262 (function(){
264 Slick.uidOf(window);
265 Slick.uidOf(document);
267 Document.implement({
269         newTextNode: function(text){
270                 return this.createTextNode(text);
271         },
273         getDocument: function(){
274                 return this;
275         },
277         getWindow: function(){
278                 return this.window;
279         },
281         id: (function(){
283                 var types = {
285                         string: function(id, nocash, doc){
286                                 id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
287                                 return (id) ? types.element(id, nocash) : null;
288                         },
290                         element: function(el, nocash){
291                                 Slick.uidOf(el);
292                                 if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
293                                         var fireEvent = el.fireEvent;
294                                         // wrapping needed in IE7, or else crash
295                                         el._fireEvent = function(type, event){
296                                                 return fireEvent(type, event);
297                                         };
298                                         Object.append(el, Element.Prototype);
299                                 }
300                                 return el;
301                         },
303                         object: function(obj, nocash, doc){
304                                 if (obj.toElement) return types.element(obj.toElement(doc), nocash);
305                                 return null;
306                         }
308                 };
310                 types.textnode = types.whitespace = types.window = types.document = function(zero){
311                         return zero;
312                 };
314                 return function(el, nocash, doc){
315                         if (el && el.$family && el.uniqueNumber) return el;
316                         var type = typeOf(el);
317                         return (types[type]) ? types[type](el, nocash, doc || document) : null;
318                 };
320         })()
324 if (window.$ == null) Window.implement('$', function(el, nc){
325         return document.id(el, nc, this.document);
328 Window.implement({
330         getDocument: function(){
331                 return this.document;
332         },
334         getWindow: function(){
335                 return this;
336         }
340 [Document, Element].invoke('implement', {
342         getElements: function(expression){
343                 return Slick.search(this, expression, new Elements);
344         },
346         getElement: function(expression){
347                 return document.id(Slick.find(this, expression));
348         }
352 var contains = {contains: function(element){
353         return Slick.contains(this, element);
356 if (!document.contains) Document.implement(contains);
357 if (!document.createElement('div').contains) Element.implement(contains);
359 //<1.2compat>
361 Element.implement('hasChild', function(element){
362         return this !== element && this.contains(element);
365 (function(search, find, match){
367         this.Selectors = {};
368         var pseudos = this.Selectors.Pseudo = new Hash();
370         var addSlickPseudos = function(){
371                 for (var name in pseudos) if (pseudos.hasOwnProperty(name)){
372                         Slick.definePseudo(name, pseudos[name]);
373                         delete pseudos[name];
374                 }
375         };
377         Slick.search = function(context, expression, append){
378                 addSlickPseudos();
379                 return search.call(this, context, expression, append);
380         };
382         Slick.find = function(context, expression){
383                 addSlickPseudos();
384                 return find.call(this, context, expression);
385         };
387         Slick.match = function(node, selector){
388                 addSlickPseudos();
389                 return match.call(this, node, selector);
390         };
392 })(Slick.search, Slick.find, Slick.match);
394 //</1.2compat>
396 // tree walking
398 var injectCombinator = function(expression, combinator){
399         if (!expression) return combinator;
401         expression = Object.clone(Slick.parse(expression));
403         var expressions = expression.expressions;
404         for (var i = expressions.length; i--;)
405                 expressions[i][0].combinator = combinator;
407         return expression;
410 Object.forEach({
411         getNext: '~',
412         getPrevious: '!~',
413         getParent: '!'
414 }, function(combinator, method){
415         Element.implement(method, function(expression){
416                 return this.getElement(injectCombinator(expression, combinator));
417         });
420 Object.forEach({
421         getAllNext: '~',
422         getAllPrevious: '!~',
423         getSiblings: '~~',
424         getChildren: '>',
425         getParents: '!'
426 }, function(combinator, method){
427         Element.implement(method, function(expression){
428                 return this.getElements(injectCombinator(expression, combinator));
429         });
432 Element.implement({
434         getFirst: function(expression){
435                 return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
436         },
438         getLast: function(expression){
439                 return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
440         },
442         getWindow: function(){
443                 return this.ownerDocument.window;
444         },
446         getDocument: function(){
447                 return this.ownerDocument;
448         },
450         getElementById: function(id){
451                 return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
452         },
454         match: function(expression){
455                 return !expression || Slick.match(this, expression);
456         }
460 //<1.2compat>
462 if (window.$$ == null) Window.implement('$$', function(selector){
463         var elements = new Elements;
464         if (arguments.length == 1 && typeof selector == 'string') return Slick.search(this.document, selector, elements);
465         var args = Array.flatten(arguments);
466         for (var i = 0, l = args.length; i < l; i++){
467                 var item = args[i];
468                 switch (typeOf(item)){
469                         case 'element': elements.push(item); break;
470                         case 'string': Slick.search(this.document, item, elements);
471                 }
472         }
473         return elements;
476 //</1.2compat>
478 if (window.$$ == null) Window.implement('$$', function(selector){
479         if (arguments.length == 1){
480                 if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
481                 else if (Type.isEnumerable(selector)) return new Elements(selector);
482         }
483         return new Elements(arguments);
486 // Inserters
488 var inserters = {
490         before: function(context, element){
491                 var parent = element.parentNode;
492                 if (parent) parent.insertBefore(context, element);
493         },
495         after: function(context, element){
496                 var parent = element.parentNode;
497                 if (parent) parent.insertBefore(context, element.nextSibling);
498         },
500         bottom: function(context, element){
501                 element.appendChild(context);
502         },
504         top: function(context, element){
505                 element.insertBefore(context, element.firstChild);
506         }
510 inserters.inside = inserters.bottom;
512 //<1.2compat>
514 Object.each(inserters, function(inserter, where){
516         where = where.capitalize();
518         var methods = {};
520         methods['inject' + where] = function(el){
521                 inserter(this, document.id(el, true));
522                 return this;
523         };
525         methods['grab' + where] = function(el){
526                 inserter(document.id(el, true), this);
527                 return this;
528         };
530         Element.implement(methods);
534 //</1.2compat>
536 // getProperty / setProperty
538 var propertyGetters = {}, propertySetters = {};
540 // properties
542 var properties = {};
543 Array.forEach([
544         'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
545         'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
546 ], function(property){
547         properties[property.toLowerCase()] = property;
550 properties.html = 'innerHTML';
551 properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
553 Object.forEach(properties, function(real, key){
554         propertySetters[key] = function(node, value){
555                 node[real] = value;
556         };
557         propertyGetters[key] = function(node){
558                 return node[real];
559         };
562 /*<ltIE9>*/
563 propertySetters.text = (function(){
564         return function(node, value){
565                 if (node.get('tag') == 'style') node.set('html', value);
566                 else node[properties.text] = value;
567         };
568 })(propertySetters.text);
570 propertyGetters.text = (function(getter){
571         return function(node){
572                 return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
573         };
574 })(propertyGetters.text);
575 /*</ltIE9>*/
577 // Booleans
579 var bools = [
580         'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
581         'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
582         'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
583         'loop'
586 var booleans = {};
587 Array.forEach(bools, function(bool){
588         var lower = bool.toLowerCase();
589         booleans[lower] = bool;
590         propertySetters[lower] = function(node, value){
591                 node[bool] = !!value;
592         };
593         propertyGetters[lower] = function(node){
594                 return !!node[bool];
595         };
598 // Special cases
600 Object.append(propertySetters, {
602         'class': function(node, value){
603                 ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
604         },
606         'for': function(node, value){
607                 ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
608         },
610         'style': function(node, value){
611                 (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
612         },
614         'value': function(node, value){
615                 node.value = (value != null) ? value : '';
616         }
620 propertyGetters['class'] = function(node){
621         return ('className' in node) ? node.className || null : node.getAttribute('class');
624 /* <webkit> */
625 var el = document.createElement('button');
626 // IE sets type as readonly and throws
627 try { el.type = 'button'; } catch (e){}
628 if (el.type != 'button') propertySetters.type = function(node, value){
629         node.setAttribute('type', value);
631 el = null;
632 /* </webkit> */
634 /*<IE>*/
636 /*<ltIE9>*/
637 // #2479 - IE8 Cannot set HTML of style element
638 var canChangeStyleHTML = (function(){
639         var div = document.createElement('style'),
640                 flag = false;
641         try {
642                 div.innerHTML = '#justTesing{margin: 0px;}';
643                 flag = !!div.innerHTML;
644         } catch (e){}
645         return flag;
646 })();
647 /*</ltIE9>*/
649 var input = document.createElement('input'), volatileInputValue, html5InputSupport;
651 // #2178
652 input.value = 't';
653 input.type = 'submit';
654 volatileInputValue = input.value != 't';
656 // #2443 - IE throws "Invalid Argument" when trying to use html5 input types
657 try {
658         input.value = '';
659         input.type = 'email';
660         html5InputSupport = input.type == 'email';
661 } catch (e){}
663 input = null;
665 if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
666         try {
667                 var value = node.value;
668                 node.type = type;
669                 node.value = value;
670         } catch (e){}
672 /*</IE>*/
674 /* getProperty, setProperty */
676 /* <ltIE9> */
677 var pollutesGetAttribute = (function(div){
678         div.random = 'attribute';
679         return (div.getAttribute('random') == 'attribute');
680 })(document.createElement('div'));
682 var hasCloneBug = (function(test){
683         test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>';
684         return test.cloneNode(true).firstChild.childNodes.length != 1;
685 })(document.createElement('div'));
686 /* </ltIE9> */
688 var hasClassList = !!document.createElement('div').classList;
690 var classes = function(className){
691         var classNames = (className || '').clean().split(' '), uniques = {};
692         return classNames.filter(function(className){
693                 if (className !== '' && !uniques[className]) return uniques[className] = className;
694         });
697 var addToClassList = function(name){
698         this.classList.add(name);
701 var removeFromClassList = function(name){
702         this.classList.remove(name);
705 Element.implement({
707         setProperty: function(name, value){
708                 var setter = propertySetters[name.toLowerCase()];
709                 if (setter){
710                         setter(this, value);
711                 } else {
712                         /* <ltIE9> */
713                         var attributeWhiteList;
714                         if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
715                         /* </ltIE9> */
717                         if (value == null){
718                                 this.removeAttribute(name);
719                                 /* <ltIE9> */
720                                 if (pollutesGetAttribute) delete attributeWhiteList[name];
721                                 /* </ltIE9> */
722                         } else {
723                                 this.setAttribute(name, '' + value);
724                                 /* <ltIE9> */
725                                 if (pollutesGetAttribute) attributeWhiteList[name] = true;
726                                 /* </ltIE9> */
727                         }
728                 }
729                 return this;
730         },
732         setProperties: function(attributes){
733                 for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
734                 return this;
735         },
737         getProperty: function(name){
738                 var getter = propertyGetters[name.toLowerCase()];
739                 if (getter) return getter(this);
740                 /* <ltIE9> */
741                 if (pollutesGetAttribute){
742                         var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
743                         if (!attr) return null;
744                         if (attr.expando && !attributeWhiteList[name]){
745                                 var outer = this.outerHTML;
746                                 // segment by the opening tag and find mention of attribute name
747                                 if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
748                                 attributeWhiteList[name] = true;
749                         }
750                 }
751                 /* </ltIE9> */
752                 var result = Slick.getAttribute(this, name);
753                 return (!result && !Slick.hasAttribute(this, name)) ? null : result;
754         },
756         getProperties: function(){
757                 var args = Array.convert(arguments);
758                 return args.map(this.getProperty, this).associate(args);
759         },
761         removeProperty: function(name){
762                 return this.setProperty(name, null);
763         },
765         removeProperties: function(){
766                 Array.each(arguments, this.removeProperty, this);
767                 return this;
768         },
770         set: function(prop, value){
771                 var property = Element.Properties[prop];
772                 (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
773         }.overloadSetter(),
775         get: function(prop){
776                 var property = Element.Properties[prop];
777                 return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
778         }.overloadGetter(),
780         erase: function(prop){
781                 var property = Element.Properties[prop];
782                 (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
783                 return this;
784         },
786         hasClass: hasClassList ? function(className){
787                 return this.classList.contains(className);
788         } : function(className){
789                 return classes(this.className).contains(className);
790         },
792         addClass: hasClassList ? function(className){
793                 classes(className).forEach(addToClassList, this);
794                 return this;
795         } : function(className){
796                 this.className = classes(className + ' ' + this.className).join(' ');
797                 return this;
798         },
800         removeClass: hasClassList ? function(className){
801                 classes(className).forEach(removeFromClassList, this);
802                 return this;
803         } : function(className){
804                 var classNames = classes(this.className);
805                 classes(className).forEach(classNames.erase, classNames);
806                 this.className = classNames.join(' ');
807                 return this;
808         },
810         toggleClass: function(className, force){
811                 if (force == null) force = !this.hasClass(className);
812                 return (force) ? this.addClass(className) : this.removeClass(className);
813         },
815         adopt: function(){
816                 var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
817                 if (length > 1) parent = fragment = document.createDocumentFragment();
819                 for (var i = 0; i < length; i++){
820                         var element = document.id(elements[i], true);
821                         if (element) parent.appendChild(element);
822                 }
824                 if (fragment) this.appendChild(fragment);
826                 return this;
827         },
829         appendText: function(text, where){
830                 return this.grab(this.getDocument().newTextNode(text), where);
831         },
833         grab: function(el, where){
834                 inserters[where || 'bottom'](document.id(el, true), this);
835                 return this;
836         },
838         inject: function(el, where){
839                 inserters[where || 'bottom'](this, document.id(el, true));
840                 return this;
841         },
843         replaces: function(el){
844                 el = document.id(el, true);
845                 el.parentNode.replaceChild(this, el);
846                 return this;
847         },
849         wraps: function(el, where){
850                 el = document.id(el, true);
851                 return this.replaces(el).grab(el, where);
852         },
854         getSelected: function(){
855                 this.selectedIndex; // Safari 3.2.1
856                 return new Elements(Array.convert(this.options).filter(function(option){
857                         return option.selected;
858                 }));
859         },
861         toQueryString: function(){
862                 var queryString = [];
863                 this.getElements('input, select, textarea').each(function(el){
864                         var type = el.type;
865                         if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
867                         var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
868                                 // IE
869                                 return document.id(opt).get('value');
870                         }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
872                         Array.convert(value).each(function(val){
873                                 if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
874                         });
875                 });
876                 return queryString.join('&');
877         }
882 // appendHTML
884 var appendInserters = {
885         before: 'beforeBegin',
886         after: 'afterEnd',
887         bottom: 'beforeEnd',
888         top: 'afterBegin',
889         inside: 'beforeEnd'
892 Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
893         this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
894         return this;
895 } : function(html, where){
896         var temp = new Element('div', {html: html}),
897                 children = temp.childNodes,
898                 fragment = temp.firstChild;
900         if (!fragment) return this;
901         if (children.length > 1){
902                 fragment = document.createDocumentFragment();
903                 for (var i = 0, l = children.length; i < l; i++){
904                         fragment.appendChild(children[i]);
905                 }
906         }
908         inserters[where || 'bottom'](fragment, this);
909         return this;
912 var collected = {}, storage = {};
914 var get = function(uid){
915         return (storage[uid] || (storage[uid] = {}));
918 var clean = function(item){
919         var uid = item.uniqueNumber;
920         if (item.removeEvents) item.removeEvents();
921         if (item.clearAttributes) item.clearAttributes();
922         if (uid != null){
923                 delete collected[uid];
924                 delete storage[uid];
925         }
926         return item;
929 var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
931 Element.implement({
933         destroy: function(){
934                 var children = clean(this).getElementsByTagName('*');
935                 Array.each(children, clean);
936                 Element.dispose(this);
937                 return null;
938         },
940         empty: function(){
941                 Array.convert(this.childNodes).each(Element.dispose);
942                 return this;
943         },
945         dispose: function(){
946                 return (this.parentNode) ? this.parentNode.removeChild(this) : this;
947         },
949         clone: function(contents, keepid){
950                 contents = contents !== false;
951                 var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
953                 if (contents){
954                         ce.append(Array.convert(clone.getElementsByTagName('*')));
955                         te.append(Array.convert(this.getElementsByTagName('*')));
956                 }
958                 for (i = ce.length; i--;){
959                         var node = ce[i], element = te[i];
960                         if (!keepid) node.removeAttribute('id');
961                         /*<ltIE9>*/
962                         if (node.clearAttributes){
963                                 node.clearAttributes();
964                                 node.mergeAttributes(element);
965                                 node.removeAttribute('uniqueNumber');
966                                 if (node.options){
967                                         var no = node.options, eo = element.options;
968                                         for (var j = no.length; j--;) no[j].selected = eo[j].selected;
969                                 }
970                         }
971                         /*</ltIE9>*/
972                         var prop = formProps[element.tagName.toLowerCase()];
973                         if (prop && element[prop]) node[prop] = element[prop];
974                 }
976                 /*<ltIE9>*/
977                 if (hasCloneBug){
978                         var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
979                         for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
980                 }
981                 /*</ltIE9>*/
982                 return document.id(clone);
983         }
987 [Element, Window, Document].invoke('implement', {
989         addListener: function(type, fn){
990                 if (window.attachEvent && !window.addEventListener){
991                         collected[Slick.uidOf(this)] = this;
992                 }
993                 if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
994                 else this.attachEvent('on' + type, fn);
995                 return this;
996         },
998         removeListener: function(type, fn){
999                 if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
1000                 else this.detachEvent('on' + type, fn);
1001                 return this;
1002         },
1004         retrieve: function(property, dflt){
1005                 var storage = get(Slick.uidOf(this)), prop = storage[property];
1006                 if (dflt != null && prop == null) prop = storage[property] = dflt;
1007                 return prop != null ? prop : null;
1008         },
1010         store: function(property, value){
1011                 var storage = get(Slick.uidOf(this));
1012                 storage[property] = value;
1013                 return this;
1014         },
1016         eliminate: function(property){
1017                 var storage = get(Slick.uidOf(this));
1018                 delete storage[property];
1019                 return this;
1020         }
1024 /*<ltIE9>*/
1025 if (window.attachEvent && !window.addEventListener){
1026         var gc = function(){
1027                 Object.each(collected, clean);
1028                 if (window.CollectGarbage) CollectGarbage();
1029                 window.removeListener('unload', gc);
1030         };
1031         window.addListener('unload', gc);
1033 /*</ltIE9>*/
1035 Element.Properties = {};
1037 //<1.2compat>
1039 Element.Properties = new Hash;
1041 //</1.2compat>
1043 Element.Properties.style = {
1045         set: function(style){
1046                 this.style.cssText = style;
1047         },
1049         get: function(){
1050                 return this.style.cssText;
1051         },
1053         erase: function(){
1054                 this.style.cssText = '';
1055         }
1059 Element.Properties.tag = {
1061         get: function(){
1062                 return this.tagName.toLowerCase();
1063         }
1067 Element.Properties.html = {
1069         set: function(html){
1070                 if (html == null) html = '';
1071                 else if (typeOf(html) == 'array') html = html.join('');
1073                 /*<ltIE9>*/
1074                 if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
1075                 else /*</ltIE9>*/this.innerHTML = html;
1076         },
1077         erase: function(){
1078                 this.set('html', '');
1079         }
1083 var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
1085 /*<ltIE9>*/
1086 // technique by jdbarlett - http://jdbartlett.com/innershiv/
1087 var div = document.createElement('div');
1088 var fragment;
1089 div.innerHTML = '<nav></nav>';
1090 supportsHTML5Elements = (div.childNodes.length == 1);
1091 if (!supportsHTML5Elements){
1092         var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' ');
1093         fragment = document.createDocumentFragment(), l = tags.length;
1094         while (l--) fragment.createElement(tags[l]);
1096 div = null;
1097 /*</ltIE9>*/
1099 /*<IE>*/
1100 supportsTableInnerHTML = Function.attempt(function(){
1101         var table = document.createElement('table');
1102         table.innerHTML = '<tr><td></td></tr>';
1103         return true;
1106 /*<ltFF4>*/
1107 var tr = document.createElement('tr'), html = '<td></td>';
1108 tr.innerHTML = html;
1109 supportsTRInnerHTML = (tr.innerHTML == html);
1110 tr = null;
1111 /*</ltFF4>*/
1113 if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
1115         Element.Properties.html.set = (function(set){
1117                 var translations = {
1118                         table: [1, '<table>', '</table>'],
1119                         select: [1, '<select>', '</select>'],
1120                         tbody: [2, '<table><tbody>', '</tbody></table>'],
1121                         tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
1122                 };
1124                 translations.thead = translations.tfoot = translations.tbody;
1126                 return function(html){
1128                         /*<ltIE9>*/
1129                         if (this.styleSheet) return set.call(this, html);
1130                         /*</ltIE9>*/
1131                         var wrap = translations[this.get('tag')];
1132                         if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
1133                         if (!wrap) return set.call(this, html);
1135                         var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
1136                         if (!supportsHTML5Elements) fragment.appendChild(wrapper);
1137                         wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
1138                         while (level--) target = target.firstChild;
1139                         this.empty().adopt(target.childNodes);
1140                         if (!supportsHTML5Elements) fragment.removeChild(wrapper);
1141                         wrapper = null;
1142                 };
1144         })(Element.Properties.html.set);
1146 /*</IE>*/
1148 /*<ltIE9>*/
1149 var testForm = document.createElement('form');
1150 testForm.innerHTML = '<select><option>s</option></select>';
1152 if (testForm.firstChild.value != 's') Element.Properties.value = {
1154         set: function(value){
1155                 var tag = this.get('tag');
1156                 if (tag != 'select') return this.setProperty('value', value);
1157                 var options = this.getElements('option');
1158                 value = String(value);
1159                 for (var i = 0; i < options.length; i++){
1160                         var option = options[i],
1161                                 attr = option.getAttributeNode('value'),
1162                                 optionValue = (attr && attr.specified) ? option.value : option.get('text');
1163                         if (optionValue === value) return option.selected = true;
1164                 }
1165         },
1167         get: function(){
1168                 var option = this, tag = option.get('tag');
1170                 if (tag != 'select' && tag != 'option') return this.getProperty('value');
1172                 if (tag == 'select' && !(option = option.getSelected()[0])) return '';
1174                 var attr = option.getAttributeNode('value');
1175                 return (attr && attr.specified) ? option.value : option.get('text');
1176         }
1179 testForm = null;
1180 /*</ltIE9>*/
1182 /*<IE>*/
1183 if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
1184         set: function(id){
1185                 this.id = this.getAttributeNode('id').value = id;
1186         },
1187         get: function(){
1188                 return this.id || null;
1189         },
1190         erase: function(){
1191                 this.id = this.getAttributeNode('id').value = '';
1192         }
1194 /*</IE>*/
1196 })();