mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
This makes it easy to use jqLite's nicer class API (compared to jQuery) in modules like ngAnimate.
1027 lines
31 KiB
JavaScript
1027 lines
31 KiB
JavaScript
'use strict';
|
|
|
|
/* 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."
|
|
*
|
|
* <div class="alert alert-success">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.</div>
|
|
*
|
|
* To use jQuery, simply load it before `DOMContentLoaded` event fired.
|
|
*
|
|
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
|
|
* jqLite; they are never raw DOM references.</div>
|
|
*
|
|
* ## 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/) - 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()`
|
|
* - [`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 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. 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.
|
|
*
|
|
* @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, '<select multiple="multiple">', '</select>'],
|
|
|
|
'thead': [1, '<table>', '</table>'],
|
|
'col': [2, '<table><colgroup>', '</colgroup></table>'],
|
|
'tr': [2, '<table><tbody>', '</tbody></table>'],
|
|
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
|
|
'_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 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 = 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></$2>") + 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 || 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 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 {
|
|
forEach(type.split(' '), function(type) {
|
|
if (isDefined(fn)) {
|
|
var listenerFns = events[type];
|
|
arrayRemove(listenerFns || [], fn);
|
|
if (listenerFns && listenerFns.length > 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
removeEventListenerFn(element, type, handle);
|
|
delete events[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 ((value = jqLite.data(element, names[i])) !== undefined) 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 behaviour
|
|
// 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 (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[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(element, name) {
|
|
var nodeName = element.nodeName;
|
|
return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
|
|
}
|
|
|
|
forEach({
|
|
data: jqLiteData,
|
|
removeData: jqLiteRemoveData
|
|
}, 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 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 &&
|
|
(((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 < 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 = (value === undefined) ? 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;
|
|
};
|
|
|
|
// 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()) {
|
|
eventFns[i].call(element, event);
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
|
|
// events on `element`
|
|
eventHandler.elem = element;
|
|
return eventHandler;
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
// 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;
|
|
|
|
while (i--) {
|
|
type = types[i];
|
|
var eventFns = events[type];
|
|
|
|
if (!eventFns) {
|
|
events[type] = [];
|
|
|
|
if (type === 'mouseenter' || type === 'mouseleave') {
|
|
// Refer to jQuery's implementation of mouseenter & mouseleave
|
|
// Read about mouseenter and mouseleave:
|
|
// http://www.quirksmode.org/js/events_mouse.html#link8
|
|
|
|
jqLiteOn(element, MOUSE_EVENT_MAP[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 && !target.contains(related))) {
|
|
handle(event, type);
|
|
}
|
|
});
|
|
|
|
} else {
|
|
if (type !== '$destroy') {
|
|
addEventListenerFn(element, type, handle);
|
|
}
|
|
}
|
|
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 === 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) {
|
|
wrapNode = jqLite(wrapNode).eq(0).clone()[0];
|
|
var parent = element.parentNode;
|
|
if (parent) {
|
|
parent.replaceChild(wrapNode, element);
|
|
}
|
|
wrapNode.appendChild(element);
|
|
},
|
|
|
|
remove: jqLiteRemove,
|
|
|
|
detach: function(element) {
|
|
jqLiteRemove(element, true);
|
|
},
|
|
|
|
after: function(element, newElement) {
|
|
var index = element, parent = element.parentNode;
|
|
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
|
|
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);
|
|
}
|
|
});
|
|
};
|
|
}
|