'use strict'; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* global JQLitePrototype: true, addEventListenerFn: true, removeEventListenerFn: true, BOOLEAN_ATTR: true, ALIASED_ATTR: true */ ////////////////////////////////// //JQLite ////////////////////////////////// /** * @ngdoc function * @name angular.element * @module ng * @kind 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 ensure it is loaded before the `angular.js` file. You can also use the * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a * specific version of jQuery if multiple versions exist on the page. * *
**Note:** All element references in Angular are always wrapped with jQuery or * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.
* *
**Note:** Keep in mind that this function will not find elements * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.
* * ## Angular's jqLite * jqLite provides only the following jQuery methods: * * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters * - [`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/) - Only retrieves inline-styles, does not call `getComputedStyle()`. * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. * - [`data()`](http://api.jquery.com/data/) * - [`detach()`](http://api.jquery.com/detach/) * - [`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, selectors or event object as parameter * - [`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/) - Does not support multiple attributes * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument * - [`removeData()`](http://api.jquery.com/removeData/) * - [`replaceWith()`](http://api.jquery.com/replaceWith/) * - [`text()`](http://api.jquery.com/text/) * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter * - [`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. Requires {@link guide/production#disabling-debug-data Debug Data} to * be enabled. * - `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. * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See * https://github.com/angular/angular.js/issues/14251 for more information. * * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. * @returns {Object} jQuery object. */ JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, jqId = 1, addEventListenerFn = function(element, type, fn) { element.addEventListener(type, fn, false); }, removeEventListenerFn = function(element, type, fn) { element.removeEventListener(type, fn, false); }; /* * !!! This is an undocumented "private" function !!! */ 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 MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; 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'); } var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; var HTML_REGEXP = /<|&#?\w+;/; var TAG_NAME_REGEXP = /<([\w:-]+)/; var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; var wrapMap = { 'option': [1, ''], 'thead': [1, '', '
'], 'col': [2, '', '
'], 'tr': [2, '', '
'], 'td': [3, '', '
'], '_default': [0, '', ''] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html); } function jqLiteAcceptsData(node) { // The window object can accept data but has no nodeType // Otherwise we are only interested in elements (1) and documents (9) var nodeType = node.nodeType; return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; } function jqLiteHasData(node) { for (var key in jqCache[node.ng339]) { return true; } return false; } function jqLiteCleanData(nodes) { for (var i = 0, ii = nodes.length; i < ii; i++) { jqLiteRemoveData(nodes[i]); } } function jqLiteBuildFragment(html, context) { var tmp, tag, wrap, fragment = context.createDocumentFragment(), nodes = [], i; if (jqLiteIsTextNode(html)) { // Convert non-html into a text node nodes.push(context.createTextNode(html)); } else { // Convert html into DOM nodes tmp = fragment.appendChild(context.createElement('div')); tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase(); wrap = wrapMap[tag] || wrapMap._default; tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1>') + wrap[2]; // Descend through wrappers to the right content i = wrap[0]; while (i--) { tmp = tmp.lastChild; } nodes = concat(nodes, tmp.childNodes); tmp = fragment.firstChild; tmp.textContent = ''; } // Remove wrapper from fragment fragment.textContent = ''; fragment.innerHTML = ''; // Clear inner HTML forEach(nodes, function(node) { fragment.appendChild(node); }); return fragment; } function jqLiteParseHTML(html, context) { context = context || window.document; var parsed; if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { return [context.createElement(parsed[1])]; } if ((parsed = jqLiteBuildFragment(html, context))) { return parsed.childNodes; } return []; } function jqLiteWrapNode(node, wrapper) { var parent = node.parentNode; if (parent) { parent.replaceChild(wrapper, node); } wrapper.appendChild(node); } // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) { // eslint-disable-next-line no-bitwise return !!(this.compareDocumentPosition(arg) & 16); }; ///////////////////////////////////////////// function JQLite(element) { if (element instanceof JQLite) { return element; } var argIsString; if (isString(element)) { element = trim(element); argIsString = true; } if (!(this instanceof JQLite)) { if (argIsString && 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 (argIsString) { jqLiteAddNodes(this, jqLiteParseHTML(element)); } else { jqLiteAddNodes(this, element); } } function jqLiteClone(element) { return element.cloneNode(true); } function jqLiteDealoc(element, onlyDescendants) { if (!onlyDescendants) jqLiteRemoveData(element); if (element.querySelectorAll) { var descendants = element.querySelectorAll('*'); for (var i = 0, l = descendants.length; i < l; i++) { jqLiteRemoveData(descendants[i]); } } } function jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); var expandoStore = jqLiteExpandoStore(element); var events = expandoStore && expandoStore.events; var handle = expandoStore && expandoStore.handle; if (!handle) return; //no listeners registered if (!type) { for (type in events) { if (type !== '$destroy') { removeEventListenerFn(element, type, handle); } delete events[type]; } } else { var removeHandler = function(type) { var listenerFns = events[type]; if (isDefined(fn)) { arrayRemove(listenerFns || [], fn); } if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { removeEventListenerFn(element, type, handle); delete events[type]; } }; forEach(type.split(' '), function(type) { removeHandler(type); if (MOUSE_EVENT_MAP[type]) { removeHandler(MOUSE_EVENT_MAP[type]); } }); } } function jqLiteRemoveData(element, name) { var expandoId = element.ng339; var expandoStore = expandoId && jqCache[expandoId]; if (expandoStore) { if (name) { delete expandoStore.data[name]; return; } if (expandoStore.handle) { if (expandoStore.events.$destroy) { expandoStore.handle({}, '$destroy'); } jqLiteOff(element); } delete jqCache[expandoId]; element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it } } function jqLiteExpandoStore(element, createIfNecessary) { var expandoId = element.ng339, expandoStore = expandoId && jqCache[expandoId]; if (createIfNecessary && !expandoStore) { element.ng339 = expandoId = jqNextId(); expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; } return expandoStore; } function jqLiteData(element, key, value) { if (jqLiteAcceptsData(element)) { var isSimpleSetter = isDefined(value); var isSimpleGetter = !isSimpleSetter && key && !isObject(key); var massGetter = !key; var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); var data = expandoStore && expandoStore.data; if (isSimpleSetter) { // data('key', value) data[key] = value; } else { if (massGetter) { // data() return data; } else { if (isSimpleGetter) { // data('key') // don't force creation of expandoStore if it doesn't exist yet return data && data[key]; } else { // mass-setter: data({key1: val1, key2: val2}) extend(data, key); } } } } } 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) { // THIS CODE IS VERY HOT. Don't make changes without benchmarking. if (elements) { // if a Node (the most common case) if (elements.nodeType) { root[root.length++] = elements; } else { var length = elements.length; // if an Array or NodeList and not a Window if (typeof length === 'number' && elements.window !== elements) { if (length) { for (var i = 0; i < length; i++) { root[root.length++] = elements[i]; } } } else { root[root.length++] = elements; } } } } function jqLiteController(element, name) { return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); } function jqLiteInheritedData(element, name, value) { // if element is the document object work with the html element instead // this makes $(document).scope() possible if (element.nodeType === NODE_TYPE_DOCUMENT) { element = element.documentElement; } var names = isArray(name) ? name : [name]; while (element) { for (var i = 0, ii = names.length; i < ii; i++) { if (isDefined(value = jqLite.data(element, names[i]))) return value; } // If dealing with a document fragment node with a host element, and no parent, use the host // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM // to lookup parent controllers. element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); } } function jqLiteEmpty(element) { jqLiteDealoc(element, true); while (element.firstChild) { element.removeChild(element.firstChild); } } function jqLiteRemove(element, keepData) { if (!keepData) jqLiteDealoc(element); var parent = element.parentNode; if (parent) parent.removeChild(element); } function jqLiteDocumentLoaded(action, win) { win = win || window; if (win.document.readyState === 'complete') { // Force the action to be run async for consistent behavior // from the action's point of view // i.e. it will definitely not be in a $apply win.setTimeout(action); } else { // No need to unbind this handler as load is only ever called once jqLite(win).on('load', action); } } ////////////////////////////////////////// // 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 is already loaded if (window.document.readyState === 'complete') { window.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. // eslint-disable-next-line new-cap JQLite(window).on('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,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[value] = true; }); var ALIASED_ATTR = { 'ngMinlength': 'minlength', 'ngMaxlength': 'maxlength', 'ngMin': 'min', 'ngMax': 'max', 'ngPattern': 'pattern' }; 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[nodeName_(element)] && booleanAttr; } function getAliasedAttrName(name) { return ALIASED_ATTR[name]; } forEach({ data: jqLiteData, removeData: jqLiteRemoveData, hasData: jqLiteHasData, cleanData: jqLiteCleanData }, function(fn, name) { JQLite[name] = fn; }); forEach({ data: jqLiteData, inheritedData: jqLiteInheritedData, scope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); }, isolateScope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$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 { return element.style[name]; } }, attr: function(element, name, value) { var nodeType = element.nodeType; if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { return; } 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() { getText.$dv = ''; return getText; function getText(element, value) { if (isUndefined(value)) { var nodeType = element.nodeType; return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; } element.textContent = value; } })(), val: function(element, value) { if (isUndefined(value)) { if (element.multiple && nodeName_(element) === 'select') { 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; } jqLiteDealoc(element, true); 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; var nodeCount = this.length; // 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 && (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { if (isObject(arg1)) { // we are a write, but the object properties are the key/values for (i = 0; i < nodeCount; 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. // TODO: do we still need this? var value = fn.$dv; // Only if we have $dv do we iterate over all, otherwise it is just the first element. var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 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 < nodeCount; i++) { fn(this[i], arg1, arg2); } // return self for chaining return this; } }; }); function createEventHandler(element, events) { var eventHandler = function(event, type) { // jQuery specific api event.isDefaultPrevented = function() { return event.defaultPrevented; }; var eventFns = events[type || event.type]; var eventFnsLength = eventFns ? eventFns.length : 0; if (!eventFnsLength) return; if (isUndefined(event.immediatePropagationStopped)) { var originalStopImmediatePropagation = event.stopImmediatePropagation; event.stopImmediatePropagation = function() { event.immediatePropagationStopped = true; if (event.stopPropagation) { event.stopPropagation(); } if (originalStopImmediatePropagation) { originalStopImmediatePropagation.call(event); } }; } event.isImmediatePropagationStopped = function() { return event.immediatePropagationStopped === true; }; // Some events have special handlers that wrap the real handler var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; // Copy event handlers in case event handlers array is modified during execution. if ((eventFnsLength > 1)) { eventFns = shallowCopy(eventFns); } for (var i = 0; i < eventFnsLength; i++) { if (!event.isImmediatePropagationStopped()) { handlerWrapper(element, event, eventFns[i]); } } }; // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all // events on `element` eventHandler.elem = element; return eventHandler; } function defaultHandlerWrapper(element, event, handler) { handler.call(element, event); } function specialMouseHandlerWrapper(target, event, handler) { // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 var 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 && !jqLiteContains.call(target, related))) { handler.call(target, event); } } ////////////////////////////////////////// // Functions iterating traversal. // These functions chain results into a single // selector. ////////////////////////////////////////// forEach({ removeData: jqLiteRemoveData, on: function jqLiteOn(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); // Do not add event handlers to non-elements because they will not be cleaned up. if (!jqLiteAcceptsData(element)) { return; } var expandoStore = jqLiteExpandoStore(element, true); var events = expandoStore.events; var handle = expandoStore.handle; if (!handle) { handle = expandoStore.handle = createEventHandler(element, events); } // http://jsperf.com/string-indexof-vs-split var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; var i = types.length; var addHandler = function(type, specialHandlerWrapper, noEventListener) { var eventFns = events[type]; if (!eventFns) { eventFns = events[type] = []; eventFns.specialHandlerWrapper = specialHandlerWrapper; if (type !== '$destroy' && !noEventListener) { addEventListenerFn(element, type, handle); } } eventFns.push(fn); }; while (i--) { type = types[i]; if (MOUSE_EVENT_MAP[type]) { addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); addHandler(type, undefined, true); } else { addHandler(type); } } }, 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 === NODE_TYPE_ELEMENT) { children.push(element); } }); return children; }, contents: function(element) { return element.contentDocument || element.childNodes || []; }, append: function(element, node) { var nodeType = element.nodeType; if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; node = new JQLite(node); for (var i = 0, ii = node.length; i < ii; i++) { var child = node[i]; element.appendChild(child); } }, prepend: function(element, node) { if (element.nodeType === NODE_TYPE_ELEMENT) { var index = element.firstChild; forEach(new JQLite(node), function(child) { element.insertBefore(child, index); }); } }, wrap: function(element, wrapNode) { jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); }, remove: jqLiteRemove, detach: function(element) { jqLiteRemove(element, true); }, after: function(element, newElement) { var index = element, parent = element.parentNode; if (parent) { newElement = new JQLite(newElement); for (var i = 0, ii = newElement.length; i < ii; i++) { var node = newElement[i]; parent.insertBefore(node, index.nextSibling); index = node; } } }, addClass: jqLiteAddClass, removeClass: jqLiteRemoveClass, toggleClass: function(element, selector, condition) { if (selector) { forEach(selector.split(' '), function(className) { var classCondition = condition; if (isUndefined(classCondition)) { classCondition = !jqLiteHasClass(element, className); } (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); }); } }, parent: function(element) { var parent = element.parentNode; return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; }, next: function(element) { return element.nextElementSibling; }, find: function(element, selector) { if (element.getElementsByTagName) { return element.getElementsByTagName(selector); } else { return []; } }, clone: jqLiteClone, triggerHandler: function(element, event, extraParameters) { var dummyEvent, eventFnsCopy, handlerArgs; var eventName = event.type || event; var expandoStore = jqLiteExpandoStore(element); var events = expandoStore && expandoStore.events; var eventFns = events && events[eventName]; if (eventFns) { // Create a dummy event to pass to the handlers dummyEvent = { preventDefault: function() { this.defaultPrevented = true; }, isDefaultPrevented: function() { return this.defaultPrevented === true; }, stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, stopPropagation: noop, type: eventName, target: element }; // If a custom event was provided then extend our dummy event with it if (event.type) { dummyEvent = extend(dummyEvent, event); } // Copy event handlers in case event handlers array is modified during execution. eventFnsCopy = shallowCopy(eventFns); handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; forEach(eventFnsCopy, function(fn) { if (!dummyEvent.isImmediatePropagationStopped()) { fn.apply(element, handlerArgs); } }); } } }, function(fn, name) { /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; for (var i = 0, ii = this.length; i < ii; 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; // Provider for private $$jqLite service /** @this */ function $$jqLiteProvider() { this.$get = function $$jqLite() { return extend(JQLite, { hasClass: function(node, classes) { if (node.attr) node = node[0]; return jqLiteHasClass(node, classes); }, addClass: function(node, classes) { if (node.attr) node = node[0]; return jqLiteAddClass(node, classes); }, removeClass: function(node, classes) { if (node.attr) node = node[0]; return jqLiteRemoveClass(node, classes); } }); }; }