'use strict'; /* global -JQLitePrototype, -addEventListenerFn, -removeEventListenerFn, -BOOLEAN_ATTR */ ////////////////////////////////// //JQLite ////////////////////////////////// /** * @ngdoc function * @name angular.element * @module ng * @function * * @description * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. * * If jQuery is available, `angular.element` is an alias for the * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." * *
jqLite is a tiny, API-compatible subset of jQuery that allows * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most * commonly needed functionality with the goal of having a very small footprint.
* * To use jQuery, simply load it before `DOMContentLoaded` event fired. * *
**Note:** all element references in Angular are always wrapped with jQuery or * jqLite; they are never raw DOM references.
* * ## Angular's jqLite * jqLite provides only the following jQuery 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/) - Does not support namespaces, selectors or eventData * - [`children()`](http://api.jquery.com/children/) - Does not support selectors * - [`clone()`](http://api.jquery.com/clone/) * - [`contents()`](http://api.jquery.com/contents/) * - [`css()`](http://api.jquery.com/css/) * - [`data()`](http://api.jquery.com/data/) * - [`empty()`](http://api.jquery.com/empty/) * - [`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/) - Does not support selectors * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors * - [`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/) * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces * - [`val()`](http://api.jquery.com/val/) * - [`wrap()`](http://api.jquery.com/wrap/) * * ## jQuery/jqLite Extras * Angular also provides the following additional methods and events to both jQuery and jqLite: * * ### Events * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM * element before it is removed. * * ### Methods * - `controller(name)` - retrieves the controller of the current element or its parent. By default * retrieves controller associated with the `ngController` directive. If `name` is provided as * camelCase directive name, then the controller for this directive will be retrieved (e.g. * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current * element or its parent. * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the * current element. This getter should be used only on elements that contain a directive which starts a new isolate * scope. Calling `scope()` on this element always returns the original non-isolate scope. * - `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 = JQLite.cache = {}, jqName = JQLite.expando = '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); }); /* * !!! This is an undocumented "private" function !!! */ var jqData = JQLite._data = function(node) { //jQuery always returns an object on cache miss return this.cache[node[this.expando]] || {}; }; function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; var jqLiteMinErr = minErr('jqLite'); /** * 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, filterElems, getterIfNoArguments) { var originalJqFn = jQuery.fn[name]; originalJqFn = originalJqFn.$original || originalJqFn; removePatch.$original = originalJqFn; jQuery.fn[name] = removePatch; function removePatch(param) { // jshint -W040 var list = filterElems && param ? [this.filter(param)] : [this], fireEvent = dispatchThis, set, setIndex, setLength, element, childIndex, childLength, children; if (!getterIfNoArguments || param != null) { while(list.length) { set = list.shift(); for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { element = jqLite(set[setIndex]); if (fireEvent) { element.triggerHandler('$destroy'); } 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 JQLite(element) { if (element instanceof JQLite) { return element; } if (isString(element)) { element = trim(element); } if (!(this instanceof JQLite)) { if (isString(element) && element.charAt(0) != '<') { throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); } return new JQLite(element); } 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); var fragment = jqLite(document.createDocumentFragment()); fragment.append(this); // 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 jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); var events = jqLiteExpandoStore(element, 'events'), handle = jqLiteExpandoStore(element, 'handle'); if (!handle) return; //no listeners registered if (isUndefined(type)) { forEach(events, function(eventHandler, type) { removeEventListenerFn(element, type, eventHandler); delete events[type]; }); } else { forEach(type.split(' '), function(type) { if (isUndefined(fn)) { removeEventListenerFn(element, type, events[type]); delete events[type]; } else { arrayRemove(events[type] || [], fn); } }); } } function jqLiteRemoveData(element, name) { var expandoId = element[jqName], expandoStore = jqCache[expandoId]; if (expandoStore) { if (name) { delete jqCache[expandoId].data[name]; return; } if (expandoStore.handle) { expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); jqLiteOff(element); } delete jqCache[expandoId]; element[jqName] = undefined; // ie does not allow deletion of attributes on elements. } } function jqLiteExpandoStore(element, key, value) { var expandoId = element[jqName], expandoStore = jqCache[expandoId || -1]; if (isDefined(value)) { if (!expandoStore) { element[jqName] = expandoId = jqNextId(); expandoStore = jqCache[expandoId] = {}; } expandoStore[key] = value; } else { return expandoStore && expandoStore[key]; } } function jqLiteData(element, key, value) { var data = jqLiteExpandoStore(element, 'data'), isSetter = isDefined(value), keyDefined = !isSetter && isDefined(key), isSimpleGetter = keyDefined && !isObject(key); if (!data && !isSimpleGetter) { jqLiteExpandoStore(element, 'data', data = {}); } if (isSetter) { data[key] = value; } else { if (keyDefined) { if (isSimpleGetter) { // don't create data in this case. return data && data[key]; } else { extend(data, key); } } else { return data; } } } function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). indexOf( " " + selector + " " ) > -1); } function jqLiteRemoveClass(element, cssClasses) { if (cssClasses && element.setAttribute) { forEach(cssClasses.split(' '), function(cssClass) { element.setAttribute('class', trim( (" " + (element.getAttribute('class') || '') + " ") .replace(/[\n\t]/g, " ") .replace(" " + trim(cssClass) + " ", " ")) ); }); } } function jqLiteAddClass(element, cssClasses) { if (cssClasses && element.setAttribute) { var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') .replace(/[\n\t]/g, " "); forEach(cssClasses.split(' '), function(cssClass) { cssClass = trim(cssClass); if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { existingClasses += cssClass + ' '; } }); element.setAttribute('class', trim(existingClasses)); } } 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]); } } } function jqLiteController(element, name) { return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); } function jqLiteInheritedData(element, name, value) { element = jqLite(element); // if element is the document object work with the html element instead // this makes $(document).scope() possible if(element[0].nodeType == 9) { element = element.find('html'); } var names = isArray(name) ? name : [name]; while (element.length) { for (var i = 0, ii = names.length; i < ii; i++) { if ((value = element.data(names[i])) !== undefined) return value; } element = element.parent(); } } function jqLiteEmpty(element) { for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { jqLiteDealoc(childNodes[i]); } while (element.firstChild) { element.removeChild(element.firstChild); } } ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// var JQLitePrototype = JQLite.prototype = { ready: function(fn) { var fired = false; function trigger() { if (fired) return; fired = true; fn(); } // check if document already is loaded if (document.readyState === 'complete'){ setTimeout(trigger); } else { this.on('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. // jshint -W064 JQLite(window).on('load', trigger); // fallback to window.onload for others // jshint +W064 } }, 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,open'.split(','), function(value) { BOOLEAN_ATTR[lowercase(value)] = value; }); var BOOLEAN_ELEMENTS = {}; forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { BOOLEAN_ELEMENTS[uppercase(value)] = true; }); function getBooleanAttrName(element, name) { // check dom last since we will most likely fail on name var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; // booleanAttr is here twice to minimize DOM access return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; } forEach({ data: jqLiteData, inheritedData: jqLiteInheritedData, scope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); }, isolateScope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); }, controller: jqLiteController , injector: function(element) { return jqLiteInheritedData(element, '$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.attributes.getNamedItem(name)|| noop).specified) ? 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: (function() { var NODE_TYPE_TEXT_PROPERTY = []; if (msie < 9) { NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ } else { NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ } getText.$dv = ''; return getText; function getText(element, value) { var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; if (isUndefined(value)) { return textProp ? element[textProp] : ''; } element[textProp] = value; } })(), val: function(element, value) { if (isUndefined(value)) { if (nodeName_(element) === 'SELECT' && element.multiple) { var result = []; forEach(element.options, function (option) { if (option.selected) { result.push(option.value || option.text); } }); return result.length === 0 ? null : result; } 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; }, empty: jqLiteEmpty }, 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. // jqLiteEmpty takes no arguments but is a setter. if (fn !== jqLiteEmpty && (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? 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++) { if (fn === jqLiteData) { // data() takes the whole object in jQuery fn(this[i], arg1); } else { 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. var value = fn.$dv; // Only if we have $dv do we iterate over all, otherwise it is just the first element. var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; for (var j = 0; j < jj; j++) { var nodeValue = fn(this[j], arg1, arg2); value = value ? value + nodeValue : nodeValue; } return value; } } 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; } }; }); function createEventHandler(element, events) { var eventHandler = function (event, type) { 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 || event.returnValue === false; }; // Copy event handlers in case event handlers array is modified during execution. var eventHandlersCopy = shallowCopy(events[type || event.type] || []); forEach(eventHandlersCopy, function(fn) { fn.call(element, event); }); // Remove monkey-patched methods (IE), // as they would cause memory leaks in IE8. if (msie <= 8) { // IE7/8 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.elem = element; return eventHandler; } ////////////////////////////////////////// // Functions iterating traversal. // These functions chain results into a single // selector. ////////////////////////////////////////// forEach({ removeData: jqLiteRemoveData, dealoc: jqLiteDealoc, on: function onFn(element, type, fn, unsupported){ if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); var events = jqLiteExpandoStore(element, 'events'), handle = jqLiteExpandoStore(element, 'handle'); if (!events) jqLiteExpandoStore(element, 'events', events = {}); if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); forEach(type.split(' '), function(type){ var eventFns = events[type]; if (!eventFns) { if (type == 'mouseenter' || type == 'mouseleave') { var contains = document.body.contains || document.body.compareDocumentPosition ? function( a, b ) { // jshint bitwise: false var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; events[type] = []; // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; onFn(element, eventmap[type], function(event) { var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || (related !== target && !contains(target, related)) ){ handle(event, type); } }); } else { addEventListenerFn(element, type, handle); events[type] = []; } eventFns = events[type]; } eventFns.push(fn); }); }, off: jqLiteOff, one: function(element, type, fn) { element = jqLite(element); //add the listener twice so that when it is called //you can remove the original function and still be //able to call element.off(ev, fn) normally element.on(type, function onFn() { element.off(type, fn); element.off(type, onFn); }); element.on(type, 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.nodeType === 1) 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.nodeType === 11) { element.appendChild(child); } }); }, prepend: function(element, node) { if (element.nodeType === 1) { var index = element.firstChild; forEach(new JQLite(node), function(child){ element.insertBefore(child, index); }); } }, 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) { if (element.nextElementSibling) { return element.nextElementSibling; } // IE8 doesn't have nextElementSibling var elm = element.nextSibling; while (elm != null && elm.nodeType !== 1) { elm = elm.nextSibling; } return elm; }, find: function(element, selector) { if (element.getElementsByTagName) { return element.getElementsByTagName(selector); } else { return []; } }, clone: jqLiteClone, triggerHandler: function(element, eventName, eventData) { var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; eventData = eventData || []; var event = [{ preventDefault: noop, stopPropagation: noop }]; forEach(eventFns, function(fn) { fn.apply(element, event.concat(eventData)); }); } }, function(fn, name){ /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; for(var i=0; i < this.length; i++) { if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); if (isDefined(value)) { // any function which returns a value needs to be wrapped value = jqLite(value); } } else { jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); } } return isDefined(value) ? value : this; }; // bind legacy bind/unbind to on/off JQLite.prototype.bind = JQLite.prototype.on; JQLite.prototype.unbind = JQLite.prototype.off; });