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, Number, Slick.Parser, Slick.Finder]
12 provides: [Element, Elements, $, $$, Iframe]
17 // it needs to be this.Element cause IE8 erases the Element Object while pre-processing this script
18 this.Element = function(tag, props){
19 var konstructor = Element.Constructors[tag];
20 if (konstructor) return konstructor(props);
21 if (typeof tag != 'string') return document.id(tag).set(props);
23 if (!props) props = {};
25 if (!tag.test(/^[\w-]+$/)){
26 var parsed = Slick.parse(tag).expressions[0][0];
27 tag = (parsed.tag == '*') ? 'div' : parsed.tag;
28 if (parsed.id && props.id == null) props.id = parsed.id;
30 var attributes = parsed.attributes;
31 if (attributes) for (var i = 0, l = attributes.length; i < l; i++){
32 var attr = attributes[i];
33 if (attr.value != null && attr.operator == '=' && props[attr.key] == null)
34 props[attr.key] = attr.value;
37 if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
40 return document.newElement(tag, props);
44 Element.prototype = Browser.Element.prototype;
47 new Type('Element', Element).mirror(function(name, method){
49 obj[name] = function(){
50 var results = [], args = arguments, elements = true;
51 for (var i = 0, l = this.length; i < l; i++){
52 var element = this[i], result = results[i] = element[name].apply(element, args);
53 elements = (elements && typeOf(result) == 'element');
55 return (elements) ? new Elements(results) : results;
58 Elements.implement(obj);
61 if (!Browser.Element){
62 Element.parent = Object;
64 Element.ProtoType = {'$family': Function.from('element').hide()};
66 Element.mirror(function(name, method){
67 Element.ProtoType[name] = method;
71 Element.Constructors = {};
75 Element.Constructors = new Hash;
79 var IFrame = new Type('IFrame', function(){
80 var params = Array.link(arguments, {
81 properties: Type.isObject,
82 iframe: function(obj){
86 var props = params.properties || {};
87 var iframe = document.id(params.iframe);
88 var onload = props.onload || function(){};
90 props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + Date.now()].pick();
91 iframe = new Element(iframe || 'iframe', props);
92 var onFrameLoad = function(){
93 var host = Function.attempt(function(){
94 return iframe.contentWindow.location.host;
96 if (!host || host == window.location.host){
97 var win = new Window(iframe.contentWindow);
98 new Document(iframe.contentWindow.document);
99 Object.append(win.Element.prototype, Element.ProtoType);
101 onload.call(iframe.contentWindow, iframe.contentWindow.document);
103 var contentWindow = Function.attempt(function(){
104 return iframe.contentWindow;
106 ((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
110 var Elements = this.Elements = function(nodes){
111 if (nodes && nodes.length){
112 var uniques = {}, node;
113 for (var i = 0; node = nodes[i++];){
114 var uid = Slick.uidOf(node);
123 Elements.prototype = {length: 0};
124 Elements.parent = Array;
126 new Type('Elements', Elements).implement({
128 filter: function(filter, bind){
129 if (!filter) return this;
130 return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
131 return item.match(filter);
136 var length = this.length;
137 for (var i = 0, l = arguments.length; i < l; i++){
138 var item = document.id(arguments[i]);
139 if (item) this[length++] = item;
141 return (this.length = length);
144 }).implement(Array.prototype);
146 Array.mirror(Elements);
150 newElement: function(tag, props){
151 if (props && props.checked != null) props.defaultChecked = props.checked;
152 return this.id(this.createElement(tag)).set(props);
155 newTextNode: function(text){
156 return this.createTextNode(text);
159 getDocument: function(){
163 getWindow: function(){
171 string: function(id, nocash, doc){
172 id = Slick.find(doc, '#' + id);
173 return (id) ? types.element(id, nocash) : null;
176 element: function(el, nocash){
178 if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
179 Object.append(el, Element.ProtoType);
184 object: function(obj, nocash, doc){
185 if (obj.toElement) return types.element(obj.toElement(doc), nocash);
191 types.textnode = types.whitespace = types.window = types.document = function(zero){
195 return function(el, nocash, doc){
196 if (el && el.$family && el.uid) return el;
197 var type = typeOf(el);
198 return (types[type]) ? types[type](el, nocash, doc || document) : null;
205 if (window.$ == null) Window.implement('$', function(el, nc){
206 return document.id(el, nc, this.document);
211 getDocument: function(){
212 return this.document;
215 getWindow: function(){
221 [Document, Element].invoke('implement', {
223 getElements: function(expression){
224 return Slick.search(this, expression, new Elements);
227 getElement: function(expression){
228 return document.id(Slick.find(this, expression));
235 (function(search, find, match){
238 var pseudos = this.Selectors.Pseudo = new Hash();
240 var addSlickPseudos = function(){
241 for (var name in pseudos) if (pseudos.hasOwnProperty(name)){
242 Slick.definePseudo(name, pseudos[name])
243 delete pseudos[name];
247 Slick.search = function(context, expression, append){
249 return search.call(this, context, expression, append);
252 Slick.find = function(context, expression){
254 return find.call(this, context, expression);
257 Slick.match = function(node, selector){
259 return match.call(this, node, selector);
262 })(Slick.search, Slick.find, Slick.match);
264 if (window.$$ == null) Window.implement('$$', function(selector){
265 var elements = new Elements;
266 if (arguments.length == 1 && typeof selector == 'string') return Slick.search(this.document, selector, elements);
267 var args = Array.flatten(arguments);
268 for (var i = 0, l = args.length; i < l; i++){
270 switch (typeOf(item)){
271 case 'element': elements.push(item); break;
272 case 'string': Slick.search(this.document, item, elements);
280 if (window.$$ == null) Window.implement('$$', function(selector){
281 if (arguments.length == 1){
282 if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
283 else if (Type.isEnumerable(selector)) return new Elements(selector);
285 return new Elements(arguments);
290 var collected = {}, storage = {};
291 var props = {input: 'checked', option: 'selected', textarea: 'value'};
293 var get = function(uid){
294 return (storage[uid] || (storage[uid] = {}));
297 var clean = function(item){
298 if (item.removeEvents) item.removeEvents();
299 if (item.clearAttributes) item.clearAttributes();
302 delete collected[uid];
308 var camels = ['defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly',
309 'rowSpan', 'tabIndex', 'useMap'
311 var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readOnly', 'multiple', 'selected',
316 'class': 'className',
319 var temp = document.createElement('div');
320 return (temp.innerText == null) ? 'textContent' : 'innerText';
323 var readOnly = ['type'];
324 var expandos = ['value', 'defaultValue'];
325 var uriAttrs = /^href|src|usemap$/i;
327 bools = bools.associate(bools);
328 camels = camels.associate(camels.map(String.toLowerCase));
329 readOnly = readOnly.associate(readOnly);
331 Object.append(attributes, expandos.associate(expandos));
335 before: function(context, element){
336 var parent = element.parentNode;
337 if (parent) parent.insertBefore(context, element);
340 after: function(context, element){
341 var parent = element.parentNode;
342 if (parent) parent.insertBefore(context, element.nextSibling);
345 bottom: function(context, element){
346 element.appendChild(context);
349 top: function(context, element){
350 element.insertBefore(context, element.firstChild);
355 inserters.inside = inserters.bottom;
359 Object.each(inserters, function(inserter, where){
361 where = where.capitalize();
365 methods['inject' + where] = function(el){
366 inserter(this, document.id(el, true));
370 methods['grab' + where] = function(el){
371 inserter(document.id(el, true), this);
375 Element.implement(methods);
383 set: function(prop, value){
384 var property = Element.Properties[prop];
385 (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
389 var property = Element.Properties[prop];
390 return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
393 erase: function(prop){
394 var property = Element.Properties[prop];
395 (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
399 setProperty: function(attribute, value){
400 attribute = camels[attribute] || attribute;
401 if (value == null) return this.removeProperty(attribute);
402 var key = attributes[attribute];
403 (key) ? this[key] = value :
404 (bools[attribute]) ? this[attribute] = !!value : this.setAttribute(attribute, '' + value);
408 setProperties: function(attributes){
409 for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
413 getProperty: function(attribute){
414 attribute = camels[attribute] || attribute;
415 var key = attributes[attribute] || readOnly[attribute];
416 return (key) ? this[key] :
417 (bools[attribute]) ? !!this[attribute] :
418 (uriAttrs.test(attribute) ? this.getAttribute(attribute, 2) :
419 (key = this.getAttributeNode(attribute)) ? key.nodeValue : null) || null;
422 getProperties: function(){
423 var args = Array.from(arguments);
424 return args.map(this.getProperty, this).associate(args);
427 removeProperty: function(attribute){
428 attribute = camels[attribute] || attribute;
429 var key = attributes[attribute];
430 (key) ? this[key] = '' :
431 (bools[attribute]) ? this[attribute] = false : this.removeAttribute(attribute);
435 removeProperties: function(){
436 Array.each(arguments, this.removeProperty, this);
440 hasClass: function(className){
441 return this.className.contains(className, ' ');
444 addClass: function(className){
445 if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
449 removeClass: function(className){
450 this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
454 toggleClass: function(className, force){
455 if (force == null) force = !this.hasClass(className);
456 return (force) ? this.addClass(className) : this.removeClass(className);
460 var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
461 if (length > 1) parent = fragment = document.createDocumentFragment();
463 for (var i = 0; i < length; i++){
464 var element = document.id(elements[i], true);
465 if (element) parent.appendChild(element);
468 if (fragment) this.appendChild(fragment);
473 appendText: function(text, where){
474 return this.grab(this.getDocument().newTextNode(text), where);
477 grab: function(el, where){
478 inserters[where || 'bottom'](document.id(el, true), this);
482 inject: function(el, where){
483 inserters[where || 'bottom'](this, document.id(el, true));
487 replaces: function(el){
488 el = document.id(el, true);
489 el.parentNode.replaceChild(this, el);
493 wraps: function(el, where){
494 el = document.id(el, true);
495 return this.replaces(el).grab(el, where);
498 getPrevious: function(match){
499 return document.id(Slick.find(this, '!~ ' + (match || '')));
502 getAllPrevious: function(match){
503 return Slick.search(this, '!~ ' + (match || ''), new Elements);
506 getNext: function(match){
507 return document.id(Slick.find(this, '~ ' + (match || '')));
510 getAllNext: function(match){
511 return Slick.search(this, '~ ' + (match || ''), new Elements);
514 getFirst: function(match){
515 return document.id(Slick.find(this, '> ' + (match || '')));
518 getLast: function(match){
519 return document.id(Slick.find(this, '!^ ' + (match || '')));
522 getParent: function(match){
523 return document.id(Slick.find(this, '! ' + (match || '')));
526 getParents: function(match){
527 return Slick.search(this, '! ' + (match || ''), new Elements);
530 getSiblings: function(match){
531 return Slick.search(this, '~~ ' + (match || ''), new Elements);
534 getChildren: function(match){
535 return Slick.search(this, '> ' + (match || ''), new Elements);
538 getWindow: function(){
539 return this.ownerDocument.window;
542 getDocument: function(){
543 return this.ownerDocument;
546 getElementById: function(id){
547 return document.id(Slick.find(this, '#' + id));
550 getSelected: function(){
551 this.selectedIndex; // Safari 3.2.1
552 return new Elements(Array.from(this.options).filter(function(option){
553 return option.selected;
557 toQueryString: function(){
558 var queryString = [];
559 this.getElements('input, select, textarea').each(function(el){
561 if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
563 var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
565 return document.id(opt).get('value');
566 }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
568 Array.from(value).each(function(val){
569 if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
572 return queryString.join('&');
575 clone: function(contents, keepid){
576 contents = contents !== false;
577 var clone = this.cloneNode(contents);
578 var clean = function(node, element){
579 if (!keepid) node.removeAttribute('id');
581 node.clearAttributes();
582 node.mergeAttributes(element);
583 node.removeAttribute('uid');
585 var no = node.options, eo = element.options;
586 for (var j = no.length; j--;) no[j].selected = eo[j].selected;
589 var prop = props[element.tagName.toLowerCase()];
590 if (prop && element[prop]) node[prop] = element[prop];
594 var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
595 for (var i = ce.length; i--;) clean(ce[i], te[i]);
599 return document.id(clone);
603 var children = clean(this).getElementsByTagName('*');
604 Array.each(children, clean);
605 Element.dispose(this);
610 Array.from(this.childNodes).each(Element.dispose);
615 return (this.parentNode) ? this.parentNode.removeChild(this) : this;
618 match: function(expression){
619 return !expression || Slick.match(this, expression);
624 var contains = {contains: function(element){
625 return Slick.contains(this, element);
628 if (!document.contains) Document.implement(contains);
629 if (!document.createElement('div').contains) Element.implement(contains);
633 Element.implement('hasChild', function(element){
634 return this !== element && this.contains(element);
639 [Element, Window, Document].invoke('implement', {
641 addListener: function(type, fn){
642 if (type == 'unload'){
643 var old = fn, self = this;
645 self.removeListener('unload', fn);
649 collected[this.uid] = this;
651 if (this.addEventListener) this.addEventListener(type, fn, false);
652 else this.attachEvent('on' + type, fn);
656 removeListener: function(type, fn){
657 if (this.removeEventListener) this.removeEventListener(type, fn, false);
658 else this.detachEvent('on' + type, fn);
662 retrieve: function(property, dflt){
663 var storage = get(this.uid), prop = storage[property];
664 if (dflt != null && prop == null) prop = storage[property] = dflt;
665 return prop != null ? prop : null;
668 store: function(property, value){
669 var storage = get(this.uid);
670 storage[property] = value;
674 eliminate: function(property){
675 var storage = get(this.uid);
676 delete storage[property];
684 window.addListener('unload', function(){
685 Object.each(collected, clean);
686 if (window.CollectGarbage) CollectGarbage();
691 Element.Properties = {};
695 Element.Properties = new Hash;
699 Element.Properties.style = {
701 set: function(style){
702 this.style.cssText = style;
706 return this.style.cssText;
710 this.style.cssText = '';
715 Element.Properties.tag = {
718 return this.tagName.toLowerCase();
723 (function(maxLength){
724 if (maxLength != null) Element.Properties.maxlength = Element.Properties.maxLength = {
726 var maxlength = this.getAttribute('maxLength');
727 return maxlength == maxLength ? null : maxlength;
730 })(document.createElement('input').getAttribute('maxLength'));
732 Element.Properties.html = (function(){
734 var tableTest = Function.attempt(function(){
735 var table = document.createElement('table');
736 table.innerHTML = '<tr><td></td></tr>';
739 var wrapper = document.createElement('div');
742 table: [1, '<table>', '</table>'],
743 select: [1, '<select>', '</select>'],
744 tbody: [2, '<table><tbody>', '</tbody></table>'],
745 tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
747 translations.thead = translations.tfoot = translations.tbody;
751 var html = Array.flatten(arguments).join('');
752 var wrap = (!tableTest && translations[this.get('tag')]);
755 first.innerHTML = wrap[1] + html + wrap[2];
756 for (var i = wrap[0]; i--;) first = first.firstChild;
757 this.empty().adopt(first.childNodes);
759 this.innerHTML = html;
764 html.erase = html.set;