removing .toLowerCase calls on attribs
[mootools/dkf.git] / Source / Element / Element.js
blobee3c96ac5dc20a50101a6ce58ddf4ee1178352c1
1 /*
2 Script: Element.js
3         One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser,
4         time-saver methods to let you easily work with HTML Elements.
6 License:
7         MIT-style license.
8 */
10 var Element = new Native({
12         name: 'Element',
14         legacy: window.Element,
16         initialize: function(tag, props){
17                 var konstructor = Element.Constructors.get(tag);
18                 if (konstructor) return konstructor(props);
19                 if (typeof tag == 'string') return document.newElement(tag, props);
20                 return $(tag).set(props);
21         },
23         afterImplement: function(key, value){
24                 Element.Prototype[key] = value;
25                 if (Array[key]) return;
26                 Elements.implement(key, function(){
27                         var items = [], elements = true;
28                         for (var i = 0, j = this.length; i < j; i++){
29                                 var returns = this[i][key].apply(this[i], arguments);
30                                 items.push(returns);
31                                 if (elements) elements = ($type(returns) == 'element');
32                         }
33                         return (elements) ? new Elements(items) : items;
34                 });
35         }
37 });
39 Element.Prototype = {$family: {name: 'element'}};
41 Element.Constructors = new Hash;
43 var IFrame = new Native({
45         name: 'IFrame',
47         generics: false,
49         initialize: function(){
50                 var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
51                 var props = params.properties || {};
52                 var iframe = $(params.iframe) || false;
53                 var onload = props.onload || $empty;
54                 delete props.onload;
55                 props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time());
56                 iframe = new Element(iframe || 'iframe', props);
57                 var onFrameLoad = function(){
58                         var host = $try(function(){
59                                 return iframe.contentWindow.location.host;
60                         });
61                         if (host && host == window.location.host){
62                                 var win = new Window(iframe.contentWindow);
63                                 new Document(iframe.contentWindow.document);
64                                 $extend(win.Element.prototype, Element.Prototype);
65                         }
66                         onload.call(iframe.contentWindow, iframe.contentWindow.document);
67                 };
68                 (window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
69                 return iframe;
70         }
72 });
74 var Elements = new Native({
76         initialize: function(elements, options){
77                 options = $extend({ddup: true, cash: true}, options);
78                 elements = elements || [];
79                 if (options.ddup || options.cash){
80                         var uniques = {}, returned = [];
81                         for (var i = 0, l = elements.length; i < l; i++){
82                                 var el = $.element(elements[i], !options.cash);
83                                 if (options.ddup){
84                                         if (uniques[el.uid]) continue;
85                                         uniques[el.uid] = true;
86                                 }
87                                 returned.push(el);
88                         }
89                         elements = returned;
90                 }
91                 return (options.cash) ? $extend(elements, this) : elements;
92         }
94 });
96 Elements.implement({
98         filter: function(filter, bind){
99                 if (!filter) return this;
100                 return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
101                         return item.match(filter);
102                 } : filter, bind));
103         }
107 Document.implement({
109         newElement: function(tag, props){
110                 if (Browser.Engine.trident && props){
111                         ['name', 'type', 'checked'].each(function(attribute){
112                                 if (!props[attribute]) return;
113                                 tag += ' ' + attribute + '="' + props[attribute] + '"';
114                                 if (attribute != 'checked') delete props[attribute];
115                         });
116                         tag = '<' + tag + '>';
117                 }
118                 return $.element(this.createElement(tag)).set(props);
119         },
121         newTextNode: function(text){
122                 return this.createTextNode(text);
123         },
125         getDocument: function(){
126                 return this;
127         },
129         getWindow: function(){
130                 return this.window;
131         }
135 Window.implement({
137         $: function(el, nocash){
138                 if (el && el.$family && el.uid) return el;
139                 var type = $type(el);
140                 return ($[type]) ? $[type](el, nocash, this.document) : null;
141         },
143         $$: function(selector){
144                 if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
145                 var elements = [];
146                 var args = Array.flatten(arguments);
147                 for (var i = 0, l = args.length; i < l; i++){
148                         var item = args[i];
149                         switch ($type(item)){
150                                 case 'element': elements.push(item); break;
151                                 case 'string': elements.extend(this.document.getElements(item, true));
152                         }
153                 }
154                 return new Elements(elements);
155         },
157         getDocument: function(){
158                 return this.document;
159         },
161         getWindow: function(){
162                 return this;
163         }
167 $.string = function(id, nocash, doc){
168         id = doc.getElementById(id);
169         return (id) ? $.element(id, nocash) : null;
172 $.element = function(el, nocash){
173         $uid(el);
174         if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
175                 var proto = Element.Prototype;
176                 for (var p in proto) el[p] = proto[p];
177         };
178         return el;
181 $.object = function(obj, nocash, doc){
182         if (obj.toElement) return $.element(obj.toElement(doc), nocash);
183         return null;
186 $.textnode = $.whitespace = $.window = $.document = $arguments(0);
188 Native.implement([Element, Document], {
190         getElement: function(selector, nocash){
191                 return $(this.getElements(selector, true)[0] || null, nocash);
192         },
194         getElements: function(tags, nocash){
195                 tags = tags.split(',');
196                 var elements = [];
197                 var ddup = (tags.length > 1);
198                 tags.each(function(tag){
199                         var partial = this.getElementsByTagName(tag.trim());
200                         (ddup) ? elements.extend(partial) : elements = partial;
201                 }, this);
202                 return new Elements(elements, {ddup: ddup, cash: !nocash});
203         }
207 (function(){
209 var collected = {}, storage = {};
210 var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};
212 var get = function(uid){
213         return (storage[uid] || (storage[uid] = {}));
216 var clean = function(item, retain){
217         if (!item) return;
218         var uid = item.uid;
219         if (Browser.Engine.trident){
220                 if (item.clearAttributes){
221                         var clone = retain && item.cloneNode(false);
222                         item.clearAttributes();
223                         if (clone) item.mergeAttributes(clone);
224                 } else if (item.removeEvents){
225                         item.removeEvents();
226                 }
227                 if ((/object/i).test(item.tagName)){
228                         for (var p in item){
229                                 if (typeof item[p] == 'function') item[p] = $empty;
230                         }
231                         Element.dispose(item);
232                 }
233         }       
234         if (!uid) return;
235         collected[uid] = storage[uid] = null;
238 var purge = function(){
239         Hash.each(collected, clean);
240         if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
241         if (window.CollectGarbage) CollectGarbage();
242         collected = storage = null;
245 var walk = function(element, walk, start, match, all, nocash){
246         var el = element[start || walk];
247         var elements = [];
248         while (el){
249                 if (el.nodeType == 1 && (!match || Element.match(el, match))){
250                         if (!all) return $(el, nocash);
251                         elements.push(el);
252                 }
253                 el = el[walk];
254         }
255         return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
258 var attributes = {
259         'html': 'innerHTML',
260         'class': 'className',
261         'for': 'htmlFor',
262         'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
264 var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
265 var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
267 bools = bools.associate(bools);
269 Hash.extend(attributes, bools);
270 Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));
272 var inserters = {
274         before: function(context, element){
275                 if (element.parentNode) element.parentNode.insertBefore(context, element);
276         },
278         after: function(context, element){
279                 if (!element.parentNode) return;
280                 var next = element.nextSibling;
281                 (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
282         },
284         bottom: function(context, element){
285                 element.appendChild(context);
286         },
288         top: function(context, element){
289                 var first = element.firstChild;
290                 (first) ? element.insertBefore(context, first) : element.appendChild(context);
291         }
295 inserters.inside = inserters.bottom;
297 Hash.each(inserters, function(inserter, where){
299         where = where.capitalize();
301         Element.implement('inject' + where, function(el){
302                 inserter(this, $(el, true));
303                 return this;
304         });
306         Element.implement('grab' + where, function(el){
307                 inserter($(el, true), this);
308                 return this;
309         });
313 Element.implement({
315         set: function(prop, value){
316                 switch ($type(prop)){
317                         case 'object':
318                                 for (var p in prop) this.set(p, prop[p]);
319                                 break;
320                         case 'string':
321                                 var property = Element.Properties.get(prop);
322                                 (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
323                 }
324                 return this;
325         },
327         get: function(prop){
328                 var property = Element.Properties.get(prop);
329                 return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
330         },
332         erase: function(prop){
333                 var property = Element.Properties.get(prop);
334                 (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
335                 return this;
336         },
338         setProperty: function(attribute, value){
339                 var key = attributes[attribute];
340                 if (value == undefined) return this.removeProperty(attribute);
341                 if (key && bools[attribute]) value = !!value;
342                 (key) ? this[key] = value : this.setAttribute(attribute, '' + value);
343                 return this;
344         },
346         setProperties: function(attributes){
347                 for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
348                 return this;
349         },
351         getProperty: function(attribute){
352                 var key = attributes[attribute];
353                 var value = (key) ? this[key] : this.getAttribute(attribute, 2);
354                 return (bools[attribute]) ? !!value : (key) ? value : value || null;
355         },
357         getProperties: function(){
358                 var args = $A(arguments);
359                 return args.map(this.getProperty, this).associate(args);
360         },
362         removeProperty: function(attribute){
363                 var key = attributes[attribute];
364                 (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
365                 return this;
366         },
368         removeProperties: function(){
369                 Array.each(arguments, this.removeProperty, this);
370                 return this;
371         },
373         hasClass: function(className){
374                 return this.className.contains(className, ' ');
375         },
377         addClass: function(className){
378                 if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
379                 return this;
380         },
382         removeClass: function(className){
383                 this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
384                 return this;
385         },
387         toggleClass: function(className){
388                 return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
389         },
391         adopt: function(){
392                 Array.flatten(arguments).each(function(element){
393                         element = $(element, true);
394                         if (element) this.appendChild(element);
395                 }, this);
396                 return this;
397         },
399         appendText: function(text, where){
400                 return this.grab(this.getDocument().newTextNode(text), where);
401         },
403         grab: function(el, where){
404                 inserters[where || 'bottom']($(el, true), this);
405                 return this;
406         },
408         inject: function(el, where){
409                 inserters[where || 'bottom'](this, $(el, true));
410                 return this;
411         },
413         replaces: function(el){
414                 el = $(el, true);
415                 el.parentNode.replaceChild(this, el);
416                 return this;
417         },
419         wraps: function(el, where){
420                 el = $(el, true);
421                 return this.replaces(el).grab(el, where);
422         },
424         getPrevious: function(match, nocash){
425                 return walk(this, 'previousSibling', null, match, false, nocash);
426         },
428         getAllPrevious: function(match, nocash){
429                 return walk(this, 'previousSibling', null, match, true, nocash);
430         },
432         getNext: function(match, nocash){
433                 return walk(this, 'nextSibling', null, match, false, nocash);
434         },
436         getAllNext: function(match, nocash){
437                 return walk(this, 'nextSibling', null, match, true, nocash);
438         },
440         getFirst: function(match, nocash){
441                 return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
442         },
444         getLast: function(match, nocash){
445                 return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
446         },
448         getParent: function(match, nocash){
449                 return walk(this, 'parentNode', null, match, false, nocash);
450         },
452         getParents: function(match, nocash){
453                 return walk(this, 'parentNode', null, match, true, nocash);
454         },
455         
456         getSiblings: function(match, nocash) {
457                 return this.getParent().getChildren(match, nocash).erase(this);
458         },
460         getChildren: function(match, nocash){
461                 return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
462         },
464         getWindow: function(){
465                 return this.ownerDocument.window;
466         },
468         getDocument: function(){
469                 return this.ownerDocument;
470         },
472         getElementById: function(id, nocash){
473                 var el = this.ownerDocument.getElementById(id);
474                 if (!el) return null;
475                 for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
476                         if (!parent) return null;
477                 }
478                 return $.element(el, nocash);
479         },
481         getSelected: function(){
482                 return new Elements($A(this.options).filter(function(option){
483                         return option.selected;
484                 }));
485         },
487         getComputedStyle: function(property){
488                 if (this.currentStyle) return this.currentStyle[property.camelCase()];
489                 var computed = this.getDocument().defaultView.getComputedStyle(this, null);
490                 return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
491         },
493         toQueryString: function(){
494                 var queryString = [];
495                 this.getElements('input, select, textarea', true).each(function(el){
496                         if (!el.name || el.disabled) return;
497                         var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
498                                 return opt.value;
499                         }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
500                         $splat(value).each(function(val){
501                                 if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
502                         });
503                 });
504                 return queryString.join('&');
505         },
507         clone: function(contents, keepid){
508                 contents = contents !== false;
509                 var clone = this.cloneNode(contents);
510                 var clean = function(node, element){
511                         if (!keepid) node.removeAttribute('id');
512                         if (Browser.Engine.trident){
513                                 node.clearAttributes();
514                                 node.mergeAttributes(element);
515                                 node.removeAttribute('uid');
516                                 if (node.options){
517                                         var no = node.options, eo = element.options;
518                                         for (var j = no.length; j--;) no[j].selected = eo[j].selected;
519                                 }
520                         }
521                         var prop = props[element.tagName.toLowerCase()];
522                         if (prop && element[prop]) node[prop] = element[prop];
523                 };
525                 if (contents){
526                         var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
527                         for (var i = ce.length; i--;) clean(ce[i], te[i]);
528                 }
530                 clean(clone, this);
531                 return $(clone);
532         },
534         destroy: function(){
535                 Element.empty(this);
536                 Element.dispose(this);
537                 clean(this, true);
538                 return null;
539         },
541         empty: function(){
542                 $A(this.childNodes).each(function(node){
543                         Element.destroy(node);
544                 });
545                 return this;
546         },
548         dispose: function(){
549                 return (this.parentNode) ? this.parentNode.removeChild(this) : this;
550         },
552         hasChild: function(el){
553                 el = $(el, true);
554                 if (!el) return false;
555                 if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
556                 return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
557         },
559         match: function(tag){
560                 return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
561         }
565 Native.implement([Element, Window, Document], {
567         addListener: function(type, fn){
568                 if (type == 'unload'){
569                         var old = fn, self = this;
570                         fn = function(){
571                                 self.removeListener('unload', fn);
572                                 old();
573                         };
574                 } else {
575                         collected[this.uid] = this;
576                 }
577                 if (this.addEventListener) this.addEventListener(type, fn, false);
578                 else this.attachEvent('on' + type, fn);
579                 return this;
580         },
582         removeListener: function(type, fn){
583                 if (this.removeEventListener) this.removeEventListener(type, fn, false);
584                 else this.detachEvent('on' + type, fn);
585                 return this;
586         },
588         retrieve: function(property, dflt){
589                 var storage = get(this.uid), prop = storage[property];
590                 if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
591                 return $pick(prop);
592         },
594         store: function(property, value){
595                 var storage = get(this.uid);
596                 storage[property] = value;
597                 return this;
598         },
600         eliminate: function(property){
601                 var storage = get(this.uid);
602                 delete storage[property];
603                 return this;
604         }
608 window.addListener('unload', purge);
610 })();
612 Element.Properties = new Hash;
614 Element.Properties.style = {
616         set: function(style){
617                 this.style.cssText = style;
618         },
620         get: function(){
621                 return this.style.cssText;
622         },
624         erase: function(){
625                 this.style.cssText = '';
626         }
630 Element.Properties.tag = {
632         get: function(){
633                 return this.tagName.toLowerCase();
634         }
638 Element.Properties.html = (function(){
639         var wrapper = document.createElement('div');
641         var translations = {
642                 table: [1, '<table>', '</table>'],
643                 select: [1, '<select>', '</select>'],
644                 tbody: [2, '<table><tbody>', '</tbody></table>'],
645                 tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
646         };
647         translations.thead = translations.tfoot = translations.tbody;
649         var html = {
650                 set: function(){
651                         var html = Array.flatten(arguments).join('');
652                         var wrap = Browser.Engine.trident && translations[this.get('tag')];
653                         if (wrap){
654                                 var first = wrapper;
655                                 first.innerHTML = wrap[1] + html + wrap[2];
656                                 for (var i = wrap[0]; i--;) first = first.firstChild;
657                                 this.empty().adopt(first.childNodes);
658                         } else {
659                                 this.innerHTML = html;
660                         }
661                 }
662         };
664         html.erase = html.set;
666         return html;
667 })();
669 if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
670         get: function(){
671                 if (this.innerText) return this.innerText;
672                 var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
673                 var text = temp.innerText;
674                 temp.destroy();
675                 return text;
676         }