diff options
| author | Misko Hevery | 2011-02-10 11:20:49 -0800 |
|---|---|---|
| committer | Misko Hevery | 2011-02-16 08:59:42 -0500 |
| commit | 00cc9eb32a9387040d0175fcfd21cf9dcab6514f (patch) | |
| tree | 0edaac339a3ec69ff769e20b28b6ebe51d040272 /src/jqLite.js | |
| parent | ef4bb28be13e99f96c9ace5936cf26a174a0e5f0 (diff) | |
| download | angular.js-00cc9eb32a9387040d0175fcfd21cf9dcab6514f.tar.bz2 | |
rewrite of JQuery lite implementation, which now better supports selected sets
Diffstat (limited to 'src/jqLite.js')
| -rw-r--r-- | src/jqLite.js | 461 |
1 files changed, 293 insertions, 168 deletions
diff --git a/src/jqLite.js b/src/jqLite.js index 01a563b6..40994161 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -14,20 +14,6 @@ var jqCache = {}, function jqNextId() { return (jqId++); } -function jqClearData(element) { - var cacheId = element[jqName], - cache = jqCache[cacheId]; - if (cache) { - forEach(cache.bind || {}, function(fn, type){ - removeEventListenerFn(element, type, fn); - }); - delete jqCache[cacheId]; - if (msie) - element[jqName] = ''; // ie does not allow deletion of attributes on elements. - else - delete element[jqName]; - } -} function getStyle(element) { var current = {}, style = element[0].style, value, name, i; @@ -46,56 +32,120 @@ function getStyle(element) { return current; } -function JQLite(element) { - if (!isElement(element) && isDefined(element.length) && element.item && !isWindow(element)) { - for(var i=0; i < element.length; i++) { - this[i] = element[i]; +if (msie) { + extend(JQLite.prototype, { + text: function(value) { + var e = this[0]; + // NodeType == 3 is text node + if (e.nodeType == 3) { + if (isDefined(value)) e.nodeValue = value; + return e.nodeValue; + } else { + if (isDefined(value)) e.innerText = value; + return e.innerText; + } } - this.length = element.length; + }); +} + +///////////////////////////////////////////// +function jqLiteWrap(element) { + if (isString(element) && element.charAt(0) != '<') { + throw new Error('selectors not implemented'); + } + return new JQLite(element); +} + +function JQLite(element) { + if (element instanceof JQLite) { + return element; + } else if (isString(element)) { + var div = document.createElement('div'); + // Read about the NoScope elements here: + // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx + div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work! + div.removeChild(div.firstChild); // remove the superfluous div + JQLiteAddNodes(this, div.childNodes); + this.remove(); // detach the elements form the temporary DOM div. } else { - this[0] = element; - this.length = 1; + JQLiteAddNodes(this, element); } } -JQLite.prototype = { - data: function(key, value) { - var element = this[0], - cacheId = element[jqName], - cache = jqCache[cacheId || -1]; - if (isDefined(value)) { - if (!cache) { - element[jqName] = cacheId = jqNextId(); - cache = jqCache[cacheId] = {}; - } - cache[key] = value; - } else { - return cache ? cache[key] : _null; - } - }, +function JQLiteCloneNode(element) { + return element.cloneNode(true); +} - removeData: function(){ - jqClearData(this[0]); - }, +function JQLiteDealoc(element){ + JQLiteRemoveData(element); + for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { + JQLiteDealoc(children[i]); + } +} - dealoc: function(){ - (function dealoc(element){ - jqClearData(element); - for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { - dealoc(children[i]); - } - })(this[0]); - }, +function JQLiteRemoveData(element) { + var cacheId = element[jqName], + cache = jqCache[cacheId]; + if (cache) { + forEach(cache.bind || {}, function(fn, type){ + removeEventListenerFn(element, type, fn); + }); + delete jqCache[cacheId]; + element[jqName] = undefined; // ie does not allow deletion of attributes on elements. + } +} - scope: function() { - var scope, element = this; - while (element && element.length && !(scope = element.data($$scope))) { - element = element.parent(); +function JQLiteData(element, key, value) { + var cacheId = element[jqName], + cache = jqCache[cacheId || -1]; + if (isDefined(value)) { + if (!cache) { + element[jqName] = cacheId = jqNextId(); + cache = jqCache[cacheId] = {}; } - return scope; - }, + cache[key] = value; + } else { + return cache ? cache[key] : _null; + } +} + +function JQLiteHasClass(element, selector, _) { + // the argument '_' is important, since it makes the function have 3 arguments, which + // is neede for delegate function to realize the this is a getter. + var className = " " + selector + " "; + return ((" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1); +} + +function JQLiteRemoveClass(element, selector) { + element.className = trim( + (" " + element.className + " ") + .replace(/[\n\t]/g, " ") + .replace(" " + selector + " ", "") + ); +} +function JQLiteAddClass(element, selector ) { + if (!JQLiteHasClass(element, selector)) { + element.className = trim(element.className + ' ' + selector); + } +} + +function JQLiteAddNodes(root, elements) { + if (elements) { + elements = (!elements.nodeName && isDefined(elements.length)) + ? elements + : [ elements ]; + for(var i=0; i < elements.length; i++) { + if (elements[i].nodeType != 11) + root.push(elements[i]); + } + } +} +////////////////////////////////////////// +// Functions which are declared directly. +////////////////////////////////////////// +var JQLitePrototype = JQLite.prototype = extend([], { ready: function(fn) { var fired = false; @@ -107,15 +157,137 @@ JQLite.prototype = { this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9 // we can not use jqLite since we are not done loading and jQuery could be loaded later. - new JQLite(window).bind('load', trigger); // fallback to window.onload for others + jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others + }, + toString: function(){ + var value = []; + forEach(this, function(e){ value.push('' + e);}); + return '[' + value.join(', ') + ']'; + } +}); + +////////////////////////////////////////// +// Functions iterating getter/setters. +// these functions return self on setter and +// value on get. +////////////////////////////////////////// +forEach({ + data: JQLiteData, + + scope: function(element) { + var scope; + while (element && !(scope = jqLite(element).data($$scope))) { + element = element.parentNode; + } + return scope; }, - bind: function(type, fn){ - var self = this, - element = self[0], - bind = self.data('bind'), + removeAttr: function(element,name) { + element.removeAttribute(name); + }, + + hasClass: JQLiteHasClass, + + css: function(element, name, value) { + if (isDefined(value)) { + element.style[name] = value; + } else { + return element.style[name]; + } + }, + + attr: function(element, name, value){ + if (isDefined(value)) { + element.setAttribute(name, value); + } else if (element.getAttribute) { + // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code + // some elements (e.g. Document) don't have get attribute, so return undefined + return element.getAttribute(name, 2); + } + }, + + text: extend(msie + ? function(element, value) { + // NodeType == 3 is text node + if (element.nodeType == 3) { + if (isUndefined(value)) + return element.nodeValue; + element.nodeValue = value; + } else { + if (isUndefined(value)) + return element.innerText; + element.innerText = value; + } + } + : function(element, value) { + if (isUndefined(value)) { + return element.textContent; + } + element.textContent = value; + }, {$dv:''}), + + val: function(element, value) { + if (isUndefined(value)) { + return element.value; + } + element.value = value; + }, + + html: function(element, value) { + if (isUndefined(value)) { + return element.innerHTML; + } + for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { + JQLiteDealoc(childNodes[i]); + } + element.innerHTML = value; + } +}, function(fn, name){ + /** + * Properties: writes return selection, reads return first value + */ + JQLite.prototype[name] = function(arg1, arg2) { + if ((fn.length == 2 ? arg1 : arg2) === undefined) { + if (isObject(arg1)) { + // we are a write, but the object properties are the key/values + for(var i=0; i < this.length; i++) { + for ( var key in arg1) { + fn(this[i], key, arg1[key]); + } + } + // return self for chaining + return this; + } else { + // we are a read, so read the first child. + if (this.length) + return fn(this[0], arg1, arg2); + } + } else { + // we are a write, so apply to all children + for(var i=0; i < this.length; i++) { + fn(this[i], arg1, arg2); + } + // return self for chaining + return this; + } + return fn.$dv; + }; +}); + +////////////////////////////////////////// +// Functions iterating traversal. +// These functions chain results into a single +// selector. +////////////////////////////////////////// +forEach({ + removeData: JQLiteRemoveData, + + dealoc: JQLiteDealoc, + + bind: function(element, type, fn){ + var bind = JQLiteData(element, 'bind'), eventHandler; - if (!bind) this.data('bind', bind = {}); + if (!bind) JQLiteData(element, 'bind', bind = {}); forEach(type.split(' '), function(type){ eventHandler = bind[type]; if (!eventHandler) { @@ -131,7 +303,7 @@ JQLite.prototype = { }; } forEach(eventHandler.fns, function(fn){ - fn.call(self, event); + fn.call(element, event); }); }; eventHandler.fns = []; @@ -141,133 +313,86 @@ JQLite.prototype = { }); }, - replaceWith: function(replaceNode) { - this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]); - }, - - children: function() { - return new JQLite(this[0].childNodes); - }, - - append: function(node) { - var self = this[0]; - node = jqLite(node); - forEach(node, function(child){ - self.appendChild(child); + replaceWith: function(element, replaceNode) { + var index, parent = element.parentNode; + JQLiteDealoc(element); + forEach(new JQLite(replaceNode), function(node){ + if (index) { + parent.insertBefore(node, index.nextSibling); + } else { + parent.replaceChild(node, element); + } + index = node; }); }, - remove: function() { - this.dealoc(); - var parentNode = this[0].parentNode; - if (parentNode) parentNode.removeChild(this[0]); - }, - - removeAttr: function(name) { - this[0].removeAttribute(name); - }, - - after: function(element) { - this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling); - }, - - hasClass: function(selector) { - var className = " " + selector + " "; - if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { - return true; - } - return false; - }, - - removeClass: function(selector) { - this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", "")); + children: function(element) { + var children = []; + forEach(element.childNodes, function(element){ + if (element.nodeName != '#text') + children.push(element); + }); + return children; }, - toggleClass: function(selector, condition) { - var self = this; - (condition ? self.addClass : self.removeClass).call(self, selector); + append: function(element, node) { + forEach(new JQLite(node), function(child){ + element.appendChild(child); + }); }, - addClass: function( selector ) { - if (!this.hasClass(selector)) { - this[0].className = trim(this[0].className + ' ' + selector); - } + remove: function(element) { + JQLiteDealoc(element); + var parent = element.parentNode; + if (parent) parent.removeChild(element); }, - css: function(name, value) { - var style = this[0].style; - if (isString(name)) { - if (isDefined(value)) { - style[name] = value; - } else { - return style[name]; - } - } else { - extend(style, name); - } + after: function(element, newElement) { + var index = element, parent = element.parentNode; + forEach(new JQLite(newElement), function(node){ + parent.insertBefore(node, index.nextSibling); + index = node; + }); }, - attr: function(name, value){ - var e = this[0]; - if (isObject(name)) { - forEach(name, function(value, name){ - e.setAttribute(name, value); - }); - } else if (isDefined(value)) { - e.setAttribute(name, value); - } else { - // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code - // some elements (e.g. Document) don't have get attribute, so return undefined - if (e.getAttribute) return e.getAttribute(name, 2); - } - }, + addClass: JQLiteAddClass, + removeClass: JQLiteRemoveClass, - text: function(value) { - if (isDefined(value)) { - this[0].textContent = value; + toggleClass: function(element, selector, condition) { + if (isUndefined(condition)) { + condition = !JQLiteHasClass(element, selector); } - return this[0].textContent; + (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector); }, - val: function(value) { - if (isDefined(value)) { - this[0].value = value; - } - return this[0].value; + parent: function(element) { + // in IE it returns undefined, but we need differentiate it from functions which have no return + return element.parentNode || null; }, - html: function(value) { - if (isDefined(value)) { - var i = 0, childNodes = this[0].childNodes; - for ( ; i < childNodes.length; i++) { - jqLite(childNodes[i]).dealoc(); - } - this[0].innerHTML = value; - } - return this[0].innerHTML; + find: function(element, selector) { + return element.getElementsByTagName(selector); }, - parent: function() { - return jqLite(this[0].parentNode); - }, - - clone: cloneNode, - cloneNode: cloneNode -}; -function cloneNode() { return jqLite(this[0].cloneNode(true)); } - -if (msie) { - extend(JQLite.prototype, { - text: function(value) { - var e = this[0]; - // NodeType == 3 is text node - if (e.nodeType == 3) { - if (isDefined(value)) e.nodeValue = value; - return e.nodeValue; + clone: JQLiteCloneNode, + cloneNode: JQLiteCloneNode +}, function(fn, name){ + /** + * chaining functions + */ + JQLite.prototype[name] = function(arg1, arg2) { + var value; + for(var i=0; i < this.length; i++) { + if (value == undefined) { + value = fn(this[i], arg1, arg2); + if (value !== undefined) { + // any function which returns a value needs to be wrapped + value = jqLite(value); + } } else { - if (isDefined(value)) e.innerText = value; - return e.innerText; + JQLiteAddNodes(value, fn(this[i], arg1, arg2)); } } - }); -} + return value == undefined ? this : value; + }; +}); |
