saving a ref in order to remove the unload method
[mootools.git] / Source / Element / Element.js
blob2a59422064e9a808bd0718be200e2bfc29de2b41
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.from('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 Document.implement({
219         newElement: function(tag, props){
220                 if (props && props.checked != null) props.defaultChecked = props.checked;
221                 /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
222                 if (createElementAcceptsHTML && props){
223                         tag = '<' + tag;
224                         if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
225                         if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
226                         tag += '>';
227                         delete props.name;
228                         delete props.type;
229                 }
230                 /*</ltIE8>*/
231                 return this.id(this.createElement(tag)).set(props);
232         }
236 })();
238 (function(){
240 Slick.uidOf(window);
241 Slick.uidOf(document);
243 Document.implement({
245         newTextNode: function(text){
246                 return this.createTextNode(text);
247         },
249         getDocument: function(){
250                 return this;
251         },
253         getWindow: function(){
254                 return this.window;
255         },
257         id: (function(){
259                 var types = {
261                         string: function(id, nocash, doc){
262                                 id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
263                                 return (id) ? types.element(id, nocash) : null;
264                         },
266                         element: function(el, nocash){
267                                 Slick.uidOf(el);
268                                 if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
269                                         var fireEvent = el.fireEvent;
270                                         // wrapping needed in IE7, or else crash
271                                         el._fireEvent = function(type, event){
272                                                 return fireEvent(type, event);
273                                         };
274                                         Object.append(el, Element.Prototype);
275                                 }
276                                 return el;
277                         },
279                         object: function(obj, nocash, doc){
280                                 if (obj.toElement) return types.element(obj.toElement(doc), nocash);
281                                 return null;
282                         }
284                 };
286                 types.textnode = types.whitespace = types.window = types.document = function(zero){
287                         return zero;
288                 };
290                 return function(el, nocash, doc){
291                         if (el && el.$family && el.uniqueNumber) return el;
292                         var type = typeOf(el);
293                         return (types[type]) ? types[type](el, nocash, doc || document) : null;
294                 };
296         })()
300 if (window.$ == null) Window.implement('$', function(el, nc){
301         return document.id(el, nc, this.document);
304 Window.implement({
306         getDocument: function(){
307                 return this.document;
308         },
310         getWindow: function(){
311                 return this;
312         }
316 [Document, Element].invoke('implement', {
318         getElements: function(expression){
319                 return Slick.search(this, expression, new Elements);
320         },
322         getElement: function(expression){
323                 return document.id(Slick.find(this, expression));
324         }
328 var contains = {contains: function(element){
329         return Slick.contains(this, element);
332 if (!document.contains) Document.implement(contains);
333 if (!document.createElement('div').contains) Element.implement(contains);
335 //<1.2compat>
337 Element.implement('hasChild', function(element){
338         return this !== element && this.contains(element);
341 (function(search, find, match){
343         this.Selectors = {};
344         var pseudos = this.Selectors.Pseudo = new Hash();
346         var addSlickPseudos = function(){
347                 for (var name in pseudos) if (pseudos.hasOwnProperty(name)){
348                         Slick.definePseudo(name, pseudos[name]);
349                         delete pseudos[name];
350                 }
351         };
353         Slick.search = function(context, expression, append){
354                 addSlickPseudos();
355                 return search.call(this, context, expression, append);
356         };
358         Slick.find = function(context, expression){
359                 addSlickPseudos();
360                 return find.call(this, context, expression);
361         };
363         Slick.match = function(node, selector){
364                 addSlickPseudos();
365                 return match.call(this, node, selector);
366         };
368 })(Slick.search, Slick.find, Slick.match);
370 //</1.2compat>
372 // tree walking
374 var injectCombinator = function(expression, combinator){
375         if (!expression) return combinator;
377         expression = Object.clone(Slick.parse(expression));
379         var expressions = expression.expressions;
380         for (var i = expressions.length; i--;)
381                 expressions[i][0].combinator = combinator;
383         return expression;
386 Object.forEach({
387         getNext: '~',
388         getPrevious: '!~',
389         getParent: '!'
390 }, function(combinator, method){
391         Element.implement(method, function(expression){
392                 return this.getElement(injectCombinator(expression, combinator));
393         });
396 Object.forEach({
397         getAllNext: '~',
398         getAllPrevious: '!~',
399         getSiblings: '~~',
400         getChildren: '>',
401         getParents: '!'
402 }, function(combinator, method){
403         Element.implement(method, function(expression){
404                 return this.getElements(injectCombinator(expression, combinator));
405         });
408 Element.implement({
410         getFirst: function(expression){
411                 return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
412         },
414         getLast: function(expression){
415                 return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
416         },
418         getWindow: function(){
419                 return this.ownerDocument.window;
420         },
422         getDocument: function(){
423                 return this.ownerDocument;
424         },
426         getElementById: function(id){
427                 return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
428         },
430         match: function(expression){
431                 return !expression || Slick.match(this, expression);
432         }
436 //<1.2compat>
438 if (window.$$ == null) Window.implement('$$', function(selector){
439         var elements = new Elements;
440         if (arguments.length == 1 && typeof selector == 'string') return Slick.search(this.document, selector, elements);
441         var args = Array.flatten(arguments);
442         for (var i = 0, l = args.length; i < l; i++){
443                 var item = args[i];
444                 switch (typeOf(item)){
445                         case 'element': elements.push(item); break;
446                         case 'string': Slick.search(this.document, item, elements);
447                 }
448         }
449         return elements;
452 //</1.2compat>
454 if (window.$$ == null) Window.implement('$$', function(selector){
455         if (arguments.length == 1){
456                 if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
457                 else if (Type.isEnumerable(selector)) return new Elements(selector);
458         }
459         return new Elements(arguments);
462 // Inserters
464 var inserters = {
466         before: function(context, element){
467                 var parent = element.parentNode;
468                 if (parent) parent.insertBefore(context, element);
469         },
471         after: function(context, element){
472                 var parent = element.parentNode;
473                 if (parent) parent.insertBefore(context, element.nextSibling);
474         },
476         bottom: function(context, element){
477                 element.appendChild(context);
478         },
480         top: function(context, element){
481                 element.insertBefore(context, element.firstChild);
482         }
486 inserters.inside = inserters.bottom;
488 //<1.2compat>
490 Object.each(inserters, function(inserter, where){
492         where = where.capitalize();
494         var methods = {};
496         methods['inject' + where] = function(el){
497                 inserter(this, document.id(el, true));
498                 return this;
499         };
501         methods['grab' + where] = function(el){
502                 inserter(document.id(el, true), this);
503                 return this;
504         };
506         Element.implement(methods);
510 //</1.2compat>
512 // getProperty / setProperty
514 var propertyGetters = {}, propertySetters = {};
516 // properties
518 var properties = {};
519 Array.forEach([
520         'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
521         'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
522 ], function(property){
523         properties[property.toLowerCase()] = property;
526 properties.html = 'innerHTML';
527 properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
529 Object.forEach(properties, function(real, key){
530         propertySetters[key] = function(node, value){
531                 node[real] = value;
532         };
533         propertyGetters[key] = function(node){
534                 return node[real];
535         };
538 // Booleans
540 var bools = [
541         'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
542         'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
543         'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
544         'loop'
547 var booleans = {};
548 Array.forEach(bools, function(bool){
549         var lower = bool.toLowerCase();
550         booleans[lower] = bool;
551         propertySetters[lower] = function(node, value){
552                 node[bool] = !!value;
553         };
554         propertyGetters[lower] = function(node){
555                 return !!node[bool];
556         };
559 // Special cases
561 Object.append(propertySetters, {
563         'class': function(node, value){
564                 ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
565         },
567         'for': function(node, value){
568                 ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
569         },
571         'style': function(node, value){
572                 (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
573         },
575         'value': function(node, value){
576                 node.value = (value != null) ? value : '';
577         }
581 propertyGetters['class'] = function(node){
582         return ('className' in node) ? node.className || null : node.getAttribute('class');
585 /* <webkit> */
586 var el = document.createElement('button');
587 // IE sets type as readonly and throws
588 try { el.type = 'button'; } catch(e){}
589 if (el.type != 'button') propertySetters.type = function(node, value){
590         node.setAttribute('type', value);
592 el = null;
593 /* </webkit> */
595 /*<IE>*/
596 var input = document.createElement('input');
597 input.value = 't';
598 input.type = 'submit';
599 if (input.value != 't') propertySetters.type = function(node, type){
600         var value = node.value;
601         node.type = type;
602         node.value = value;
604 input = null;
605 /*</IE>*/
607 /* getProperty, setProperty */
609 /* <ltIE9> */
610 var pollutesGetAttribute = (function(div){
611         div.random = 'attribute';
612         return (div.getAttribute('random') == 'attribute');
613 })(document.createElement('div'));
614 /* </ltIE9> */
616 var hasClassList = !!document.createElement('div').classList;
618 Element.implement({
620         setProperty: function(name, value){
621                 var setter = propertySetters[name.toLowerCase()];
622                 if (setter){
623                         setter(this, value);
624                 } else {
625                         /* <ltIE9> */
626                         var attributeWhiteList;
627                         if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
628                         /* </ltIE9> */
630                         if (value == null){
631                                 this.removeAttribute(name);
632                                 /* <ltIE9> */
633                                 if (pollutesGetAttribute) delete attributeWhiteList[name];
634                                 /* </ltIE9> */
635                         } else {
636                                 this.setAttribute(name, '' + value);
637                                 /* <ltIE9> */
638                                 if (pollutesGetAttribute) attributeWhiteList[name] = true;
639                                 /* </ltIE9> */
640                         }
641                 }
642                 return this;
643         },
645         setProperties: function(attributes){
646                 for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
647                 return this;
648         },
650         getProperty: function(name){
651                 var getter = propertyGetters[name.toLowerCase()];
652                 if (getter) return getter(this);
653                 /* <ltIE9> */
654                 if (pollutesGetAttribute){
655                         var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
656                         if (!attr) return null;
657                         if (attr.expando && !attributeWhiteList[name]){
658                                 var outer = this.outerHTML;
659                                 // segment by the opening tag and find mention of attribute name
660                                 if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
661                                 attributeWhiteList[name] = true;
662                         }
663                 }
664                 /* </ltIE9> */
665                 var result = Slick.getAttribute(this, name);
666                 return (!result && !Slick.hasAttribute(this, name)) ? null : result;
667         },
669         getProperties: function(){
670                 var args = Array.from(arguments);
671                 return args.map(this.getProperty, this).associate(args);
672         },
674         removeProperty: function(name){
675                 return this.setProperty(name, null);
676         },
678         removeProperties: function(){
679                 Array.each(arguments, this.removeProperty, this);
680                 return this;
681         },
683         set: function(prop, value){
684                 var property = Element.Properties[prop];
685                 (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
686         }.overloadSetter(),
688         get: function(prop){
689                 var property = Element.Properties[prop];
690                 return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
691         }.overloadGetter(),
693         erase: function(prop){
694                 var property = Element.Properties[prop];
695                 (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
696                 return this;
697         },
699         hasClass: hasClassList ? function(className) {
700                 return this.classList.contains(className);
701         } : function(className){
702                 return this.className.clean().contains(className, ' ');
703         },
705         addClass: hasClassList ? function(className) {
706                 this.classList.add(className);
707                 return this;
708         } : function(className){
709                 if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
710                 return this;
711         },
713         removeClass: hasClassList ? function(className) {
714                 this.classList.remove(className);
715                 return this;
716         } : function(className){
717                 this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
718                 return this;
719         },
721         toggleClass: function(className, force){
722                 if (force == null) force = !this.hasClass(className);
723                 return (force) ? this.addClass(className) : this.removeClass(className);
724         },
726         adopt: function(){
727                 var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
728                 if (length > 1) parent = fragment = document.createDocumentFragment();
730                 for (var i = 0; i < length; i++){
731                         var element = document.id(elements[i], true);
732                         if (element) parent.appendChild(element);
733                 }
735                 if (fragment) this.appendChild(fragment);
737                 return this;
738         },
740         appendText: function(text, where){
741                 return this.grab(this.getDocument().newTextNode(text), where);
742         },
744         grab: function(el, where){
745                 inserters[where || 'bottom'](document.id(el, true), this);
746                 return this;
747         },
749         inject: function(el, where){
750                 inserters[where || 'bottom'](this, document.id(el, true));
751                 return this;
752         },
754         replaces: function(el){
755                 el = document.id(el, true);
756                 el.parentNode.replaceChild(this, el);
757                 return this;
758         },
760         wraps: function(el, where){
761                 el = document.id(el, true);
762                 return this.replaces(el).grab(el, where);
763         },
765         getSelected: function(){
766                 this.selectedIndex; // Safari 3.2.1
767                 return new Elements(Array.from(this.options).filter(function(option){
768                         return option.selected;
769                 }));
770         },
772         toQueryString: function(){
773                 var queryString = [];
774                 this.getElements('input, select, textarea').each(function(el){
775                         var type = el.type;
776                         if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
778                         var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
779                                 // IE
780                                 return document.id(opt).get('value');
781                         }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
783                         Array.from(value).each(function(val){
784                                 if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
785                         });
786                 });
787                 return queryString.join('&');
788         }
793 // appendHTML
795 var appendInserters = {
796         before: 'beforeBegin',
797         after: 'afterEnd',
798         bottom: 'beforeEnd',
799         top: 'afterBegin',
800         inside: 'beforeEnd'
803 Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
804         this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
805         return this;
806 } : function(html, where){
807         var temp = new Element('div', {html: html}),
808                 children = temp.childNodes,
809                 fragment = temp.firstChild;
811         if (!fragment) return this;
812         if (children.length > 1){
813                 fragment = document.createDocumentFragment();
814                 for (var i = 0, l = children.length; i < l; i++){
815                         fragment.appendChild(children[i]);
816                 }
817         }
819         inserters[where || 'bottom'](fragment, this);
820         return this;
823 var collected = {}, storage = {};
825 var get = function(uid){
826         return (storage[uid] || (storage[uid] = {}));
829 var clean = function(item){
830         var uid = item.uniqueNumber;
831         if (item.removeEvents) item.removeEvents();
832         if (item.clearAttributes) item.clearAttributes();
833         if (uid != null){
834                 delete collected[uid];
835                 delete storage[uid];
836         }
837         return item;
840 var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
842 Element.implement({
844         destroy: function(){
845                 var children = clean(this).getElementsByTagName('*');
846                 Array.each(children, clean);
847                 Element.dispose(this);
848                 return null;
849         },
851         empty: function(){
852                 Array.from(this.childNodes).each(Element.dispose);
853                 return this;
854         },
856         dispose: function(){
857                 return (this.parentNode) ? this.parentNode.removeChild(this) : this;
858         },
860         clone: function(contents, keepid){
861                 contents = contents !== false;
862                 var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
864                 if (contents){
865                         ce.append(Array.from(clone.getElementsByTagName('*')));
866                         te.append(Array.from(this.getElementsByTagName('*')));
867                 }
869                 for (i = ce.length; i--;){
870                         var node = ce[i], element = te[i];
871                         if (!keepid) node.removeAttribute('id');
872                         /*<ltIE9>*/
873                         if (node.clearAttributes){
874                                 node.clearAttributes();
875                                 node.mergeAttributes(element);
876                                 node.removeAttribute('uniqueNumber');
877                                 if (node.options){
878                                         var no = node.options, eo = element.options;
879                                         for (var j = no.length; j--;) no[j].selected = eo[j].selected;
880                                 }
881                         }
882                         /*</ltIE9>*/
883                         var prop = formProps[element.tagName.toLowerCase()];
884                         if (prop && element[prop]) node[prop] = element[prop];
885                 }
887                 /*<ltIE9>*/
888                 if (Browser.ie){
889                         var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
890                         for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
891                 }
892                 /*</ltIE9>*/
893                 return document.id(clone);
894         }
898 [Element, Window, Document].invoke('implement', {
900         addListener: function(type, fn){
901                 if (type == 'unload'){
902                         var old = fn, self = this;
903                         old.$ref = fn = function(){
904                                 self.removeListener('unload', fn);
905                                 old();
906                         };
907                 } else if (window.attachEvent && !window.addEventListener){
908                         collected[Slick.uidOf(this)] = this;
909                 }
910                 if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
911                 else this.attachEvent('on' + type, fn);
912                 return this;
913         },
915         removeListener: function(type, fn){
916                 if (fn.$ref){
917                         var old = fn;
918                         fn = fn.$ref;
919                         delete old.$ref;
920                 }
921                 if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
922                 else this.detachEvent('on' + type, fn);
923                 return this;
924         },
926         retrieve: function(property, dflt){
927                 var storage = get(Slick.uidOf(this)), prop = storage[property];
928                 if (dflt != null && prop == null) prop = storage[property] = dflt;
929                 return prop != null ? prop : null;
930         },
932         store: function(property, value){
933                 var storage = get(Slick.uidOf(this));
934                 storage[property] = value;
935                 return this;
936         },
938         eliminate: function(property){
939                 var storage = get(Slick.uidOf(this));
940                 delete storage[property];
941                 return this;
942         }
946 /*<ltIE9>*/
947 if (window.attachEvent && !window.addEventListener) window.addListener('unload', function(){
948         Object.each(collected, clean);
949         if (window.CollectGarbage) CollectGarbage();
951 /*</ltIE9>*/
953 Element.Properties = {};
955 //<1.2compat>
957 Element.Properties = new Hash;
959 //</1.2compat>
961 Element.Properties.style = {
963         set: function(style){
964                 this.style.cssText = style;
965         },
967         get: function(){
968                 return this.style.cssText;
969         },
971         erase: function(){
972                 this.style.cssText = '';
973         }
977 Element.Properties.tag = {
979         get: function(){
980                 return this.tagName.toLowerCase();
981         }
985 Element.Properties.html = {
987         set: function(html){
988                 if (html == null) html = '';
989                 else if (typeOf(html) == 'array') html = html.join('');
990                 this.innerHTML = html;
991         },
993         erase: function(){
994                 this.innerHTML = '';
995         }
999 var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
1001 /*<ltIE9>*/
1002 // technique by jdbarlett - http://jdbartlett.com/innershiv/
1003 var div = document.createElement('div');
1004 div.innerHTML = '<nav></nav>';
1005 supportsHTML5Elements = (div.childNodes.length == 1);
1006 if (!supportsHTML5Elements){
1007         var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
1008                 fragment = document.createDocumentFragment(), l = tags.length;
1009         while (l--) fragment.createElement(tags[l]);
1011 div = null;
1012 /*</ltIE9>*/
1014 /*<IE>*/
1015 supportsTableInnerHTML = Function.attempt(function(){
1016         var table = document.createElement('table');
1017         table.innerHTML = '<tr><td></td></tr>';
1018         return true;
1021 /*<ltFF4>*/
1022 var tr = document.createElement('tr'), html = '<td></td>';
1023 tr.innerHTML = html;
1024 supportsTRInnerHTML = (tr.innerHTML == html);
1025 tr = null;
1026 /*</ltFF4>*/
1028 if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
1030         Element.Properties.html.set = (function(set){
1032                 var translations = {
1033                         table: [1, '<table>', '</table>'],
1034                         select: [1, '<select>', '</select>'],
1035                         tbody: [2, '<table><tbody>', '</tbody></table>'],
1036                         tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
1037                 };
1039                 translations.thead = translations.tfoot = translations.tbody;
1041                 return function(html){
1042                         var wrap = translations[this.get('tag')];
1043                         if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
1044                         if (!wrap) return set.call(this, html);
1046                         var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
1047                         if (!supportsHTML5Elements) fragment.appendChild(wrapper);
1048                         wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
1049                         while (level--) target = target.firstChild;
1050                         this.empty().adopt(target.childNodes);
1051                         if (!supportsHTML5Elements) fragment.removeChild(wrapper);
1052                         wrapper = null;
1053                 };
1055         })(Element.Properties.html.set);
1057 /*</IE>*/
1059 /*<ltIE9>*/
1060 var testForm = document.createElement('form');
1061 testForm.innerHTML = '<select><option>s</option></select>';
1063 if (testForm.firstChild.value != 's') Element.Properties.value = {
1065         set: function(value){
1066                 var tag = this.get('tag');
1067                 if (tag != 'select') return this.setProperty('value', value);
1068                 var options = this.getElements('option');
1069                 value = String(value);
1070                 for (var i = 0; i < options.length; i++){
1071                         var option = options[i],
1072                                 attr = option.getAttributeNode('value'),
1073                                 optionValue = (attr && attr.specified) ? option.value : option.get('text');
1074                         if (optionValue === value) return option.selected = true;
1075                 }
1076         },
1078         get: function(){
1079                 var option = this, tag = option.get('tag');
1081                 if (tag != 'select' && tag != 'option') return this.getProperty('value');
1083                 if (tag == 'select' && !(option = option.getSelected()[0])) return '';
1085                 var attr = option.getAttributeNode('value');
1086                 return (attr && attr.specified) ? option.value : option.get('text');
1087         }
1090 testForm = null;
1091 /*</ltIE9>*/
1093 /*<IE>*/
1094 if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
1095         set: function(id){
1096                 this.id = this.getAttributeNode('id').value = id;
1097         },
1098         get: function(){
1099                 return this.id || null;
1100         },
1101         erase: function(){
1102                 this.id = this.getAttributeNode('id').value = '';
1103         }
1105 /*</IE>*/
1107 })();