'use strict'; ////////////////////////////////// //JQLite ////////////////////////////////// /** * @ngdoc function * @name angular.element * @function * * @description * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite * implementation (commonly referred to as jqLite). * * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded` * event fired. * * jqLite is a tiny, API-compatible subset of jQuery that allows * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality * within a very small footprint, so only a subset of the jQuery API - methods, arguments and * invocation styles - are supported. * * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never * raw DOM references. * * ## Angular's jQuery lite provides the following methods: * * - [addClass()](http://api.jquery.com/addClass/) * - [after()](http://api.jquery.com/after/) * - [append()](http://api.jquery.com/append/) * - [attr()](http://api.jquery.com/attr/) * - [bind()](http://api.jquery.com/bind/) * - [children()](http://api.jquery.com/children/) * - [clone()](http://api.jquery.com/clone/) * - [contents()](http://api.jquery.com/contents/) * - [css()](http://api.jquery.com/css/) * - [data()](http://api.jquery.com/data/) * - [eq()](http://api.jquery.com/eq/) * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name. * - [hasClass()](http://api.jquery.com/hasClass/) * - [html()](http://api.jquery.com/html/) * - [next()](http://api.jquery.com/next/) * - [parent()](http://api.jquery.com/parent/) * - [prepend()](http://api.jquery.com/prepend/) * - [prop()](http://api.jquery.com/prop/) * - [ready()](http://api.jquery.com/ready/) * - [remove()](http://api.jquery.com/remove/) * - [removeAttr()](http://api.jquery.com/removeAttr/) * - [removeClass()](http://api.jquery.com/removeClass/) * - [removeData()](http://api.jquery.com/removeData/) * - [replaceWith()](http://api.jquery.com/replaceWith/) * - [text()](http://api.jquery.com/text/) * - [toggleClass()](http://api.jquery.com/toggleClass/) * - [unbind()](http://api.jquery.com/unbind/) * - [val()](http://api.jquery.com/val/) * - [wrap()](http://api.jquery.com/wrap/) * * ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite: * * - `scope()` - retrieves the current Angular scope of the element. * - `injector()` - retrieves the Angular injector associated with application that the element is * part of. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. * @returns {Object} jQuery object. */ var jqCache = {}, jqName = 'ng-' + new Date().getTime(), jqId = 1, addEventListenerFn = (window.document.addEventListener ? function(element, type, fn) {element.addEventListener(type, fn, false);} : function(element, type, fn) {element.attachEvent('on' + type, fn);}), removeEventListenerFn = (window.document.removeEventListener ? function(element, type, fn) {element.removeEventListener(type, fn, false); } : function(element, type, fn) {element.detachEvent('on' + type, fn); }); function jqNextId() { return (jqId++); } function getStyle(element) { var current = {}, style = element[0].style, value, name, i; if (typeof style.length == 'number') { for(i = 0; i < style.length; i++) { name = style[i]; current[name] = style[name]; } } else { for (name in style) { value = style[name]; if (1*name != name && name != 'cssText' && value && typeof value == 'string' && value !='false') current[name] = value; } } return current; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; /** * Converts snake_case to camelCase. * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function camelCase(name) { return name. replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { return offset ? letter.toUpperCase() : letter; }). replace(MOZ_HACK_REGEXP, 'Moz$1'); } ///////////////////////////////////////////// // jQuery mutation patch // // In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a // $destroy event on all DOM nodes being removed. // ///////////////////////////////////////////// function JQLitePatchJQueryRemove(name, dispatchThis) { var originalJqFn = jQuery.fn[name]; originalJqFn = originalJqFn.$original || originalJqFn; removePatch.$original = originalJqFn; jQuery.fn[name] = removePatch; function removePatch() { var list = [this], fireEvent = dispatchThis, set, setIndex, setLength, element, childIndex, childLength, children, fns, data; while(list.length) { set = list.shift(); for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { element = jqLite(set[setIndex]); if (fireEvent) { data = element.data('events'); if ( (fns = data && data.$destroy) ) { forEach(fns, function(fn){ fn.handler(); }); } } else { fireEvent = !fireEvent; } for(childIndex = 0, childLength = (children = element.children()).length; childIndex < childLength; childIndex++) { list.push(jQuery(children[childIndex])); } } } return originalJqFn.apply(this, arguments); } } ///////////////////////////////////////////// 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 = '
 
' + 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 from the temporary DOM div. } else { JQLiteAddNodes(this, element); } } function JQLiteClone(element) { return element.cloneNode(true); } function JQLiteDealoc(element){ JQLiteRemoveData(element); for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { JQLiteDealoc(children[i]); } } function JQLiteRemoveData(element) { var cacheId = element[jqName], cache = jqCache[cacheId]; if (cache) { if (cache.bind) { forEach(cache.bind, function(fn, type){ if (type == '$destroy') { fn({}); } else { removeEventListenerFn(element, type, fn); } }); } delete jqCache[cacheId]; element[jqName] = undefined; // ie does not allow deletion of attributes on elements. } } 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] = {}; } cache[key] = value; } else { return cache ? cache[key] : null; } } function JQLiteHasClass(element, selector) { return ((" " + element.className + " ").replace(/[\n\t]/g, " "). indexOf( " " + selector + " " ) > -1); } function JQLiteRemoveClass(element, selector) { if (selector) { forEach(selector.split(' '), function(cssClass) { element.className = trim( (" " + element.className + " ") .replace(/[\n\t]/g, " ") .replace(" " + trim(cssClass) + " ", " ") ); }); } } function JQLiteAddClass(element, selector) { if (selector) { forEach(selector.split(' '), function(cssClass) { if (!JQLiteHasClass(element, cssClass)) { element.className = trim(element.className + ' ' + trim(cssClass)); } }); } } function JQLiteAddNodes(root, elements) { if (elements) { elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) ? elements : [ elements ]; for(var i=0; i < elements.length; i++) { root.push(elements[i]); } } } ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// var JQLitePrototype = JQLite.prototype = { ready: function(fn) { var fired = false; function trigger() { if (fired) return; fired = true; fn(); } 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. 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(', ') + ']'; }, eq: function(index) { return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); }, length: 0, push: push, sort: [].sort, splice: [].splice }; ////////////////////////////////////////// // Functions iterating getter/setters. // these functions return self on setter and // value on get. ////////////////////////////////////////// var BOOLEAN_ATTR = {}; forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) { BOOLEAN_ATTR[lowercase(value)] = value; }); var BOOLEAN_ELEMENTS = {}; forEach('input,select,option,textarea,button'.split(','), function(value) { BOOLEAN_ELEMENTS[uppercase(value)] = true; }); function isBooleanAttr(element, name) { return BOOLEAN_ELEMENTS[element.nodeName] && BOOLEAN_ATTR[name.toLowerCase()]; } forEach({ data: JQLiteData, inheritedData: function(element, name, value) { element = jqLite(element); while (element.length) { if (value = element.data(name)) return value; element = element.parent(); } }, scope: function(element) { return jqLite(element).inheritedData('$scope'); }, injector: function(element) { return jqLite(element).inheritedData('$injector'); }, removeAttr: function(element,name) { element.removeAttribute(name); }, hasClass: JQLiteHasClass, css: function(element, name, value) { name = camelCase(name); if (isDefined(value)) { element.style[name] = value; } else { var val; if (msie <= 8) { // this is some IE specific weirdness that jQuery 1.6.4 does not sure why val = element.currentStyle && element.currentStyle[name]; if (val === '') val = 'auto'; } val = val || element.style[name]; if (msie <= 8) { // jquery weirdness :-/ val = (val === '') ? undefined : val; } return val; } }, attr: function(element, name, value){ var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { if (!!value) { element[name] = true; element.setAttribute(name, lowercasedName); } else { element[name] = false; element.removeAttribute(lowercasedName); } } else { return (element[name] || element.getAttribute(name) !== null && (msie < 9 ? element.getAttribute(name) !== '' : true)) ? lowercasedName : undefined; } } else 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 var ret = element.getAttribute(name, 2); // normalize non-existing attributes to undefined (as jQuery) return ret === null ? undefined : ret; } }, prop: function(element, name, value) { if (isDefined(value)) { element[name] = value; } else { return element[name]; } }, text: extend((msie < 9) ? function(element, value) { if (element.nodeType == 1 /** Element */) { if (isUndefined(value)) return element.innerText; element.innerText = value; } else { if (isUndefined(value)) return element.nodeValue; element.nodeValue = 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) { var i, key; // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it // in a way that survives minification. if (((fn.length == 2 && fn !== JQLiteHasClass) ? arg1 : arg2) === undefined) { if (isObject(arg1)) { // we are a write, but the object properties are the key/values for(i=0; i < this.length; i++) { for (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(i=0; i < this.length; i++) { fn(this[i], arg1, arg2); } // return self for chaining return this; } return fn.$dv; }; }); function createEventHandler(element) { var eventHandler = function (event) { if (!event.preventDefault) { event.preventDefault = function() { event.returnValue = false; //ie }; } if (!event.stopPropagation) { event.stopPropagation = function() { event.cancelBubble = true; //ie }; } if (!event.target) { event.target = event.srcElement || document; } if (isUndefined(event.defaultPrevented)) { var prevent = event.preventDefault; event.preventDefault = function() { event.defaultPrevented = true; prevent.call(event); }; event.defaultPrevented = false; } event.isDefaultPrevented = function() { return event.defaultPrevented; }; forEach(eventHandler.fns, function(fn){ fn.call(element, event); }); // Remove monkey-patched methods (IE), // as they would cause memory leaks in IE8. if (msie < 8) { // IE7 does not allow to delete property on native object event.preventDefault = null; event.stopPropagation = null; event.isDefaultPrevented = null; } else { // It shouldn't affect normal browsers (native methods are defined on prototype). delete event.preventDefault; delete event.stopPropagation; delete event.isDefaultPrevented; } }; eventHandler.fns = []; return eventHandler; }; ////////////////////////////////////////// // Functions iterating traversal. // These functions chain results into a single // selector. ////////////////////////////////////////// forEach({ removeData: JQLiteRemoveData, dealoc: JQLiteDealoc, bind: function bindFn(element, type, fn){ var bind = JQLiteData(element, 'bind'); if (!bind) JQLiteData(element, 'bind', bind = {}); forEach(type.split(' '), function(type){ var eventHandler = bind[type]; if (!eventHandler) { if (type == 'mouseenter' || type == 'mouseleave') { var mouseenter = bind.mouseenter = createEventHandler(element); var mouseleave = bind.mouseleave = createEventHandler(element); var counter = 0; bindFn(element, 'mouseover', function(event) { counter++; if (counter == 1) { event.type = 'mouseenter'; mouseenter(event); } }); bindFn(element, 'mouseout', function(event) { counter --; if (counter == 0) { event.type = 'mouseleave'; mouseleave(event); } }); eventHandler = bind[type]; } else { eventHandler = bind[type] = createEventHandler(element); addEventListenerFn(element, type, eventHandler); } } eventHandler.fns.push(fn); }); }, unbind: function(element, type, fn) { var bind = JQLiteData(element, 'bind'); if (!bind) return; //no listeners registered if (isUndefined(type)) { forEach(bind, function(eventHandler, type) { removeEventListenerFn(element, type, eventHandler); delete bind[type]; }); } else { if (isUndefined(fn)) { removeEventListenerFn(element, type, bind[type]); delete bind[type]; } else { arrayRemove(bind[type].fns, fn); } } }, 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; }); }, children: function(element) { var children = []; forEach(element.childNodes, function(element){ if (element.nodeName != '#text') children.push(element); }); return children; }, contents: function(element) { return element.childNodes; }, append: function(element, node) { forEach(new JQLite(node), function(child){ if (element.nodeType === 1) element.appendChild(child); }); }, prepend: function(element, node) { if (element.nodeType === 1) { var index = element.firstChild; forEach(new JQLite(node), function(child){ if (index) { element.insertBefore(child, index); } else { element.appendChild(child); index = child; } }); } }, wrap: function(element, wrapNode) { wrapNode = jqLite(wrapNode)[0]; var parent = element.parentNode; if (parent) { parent.replaceChild(wrapNode, element); } wrapNode.appendChild(element); }, remove: function(element) { JQLiteDealoc(element); var parent = element.parentNode; if (parent) parent.removeChild(element); }, after: function(element, newElement) { var index = element, parent = element.parentNode; forEach(new JQLite(newElement), function(node){ parent.insertBefore(node, index.nextSibling); index = node; }); }, addClass: JQLiteAddClass, removeClass: JQLiteRemoveClass, toggleClass: function(element, selector, condition) { if (isUndefined(condition)) { condition = !JQLiteHasClass(element, selector); } (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector); }, parent: function(element) { var parent = element.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, next: function(element) { return element.nextSibling; }, find: function(element, selector) { return element.getElementsByTagName(selector); }, clone: JQLiteClone }, 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 { JQLiteAddNodes(value, fn(this[i], arg1, arg2)); } } return value == undefined ? this : value; }; });