mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-09 09:00:34 +08:00
978 lines
30 KiB
JavaScript
978 lines
30 KiB
JavaScript
'use strict';
|
|
|
|
/* global
|
|
|
|
-JQLitePrototype,
|
|
-addEventListenerFn,
|
|
-removeEventListenerFn,
|
|
-BOOLEAN_ATTR,
|
|
-ALIASED_ATTR
|
|
*/
|
|
|
|
//////////////////////////////////
|
|
//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/)
|
|
* - [`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.
|
|
*/
|
|
|
|
JQLite.expando = 'ng339';
|
|
|
|
var jqCache = JQLite.cache = {},
|
|
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');
|
|
}
|
|
|
|
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 jqLiteBuildFragment(html, context) {
|
|
var elem, 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;
|
|
}
|
|
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)) {
|
|
jqLiteAddNodes(this, jqLiteParseHTML(element));
|
|
} else {
|
|
jqLiteAddNodes(this, element);
|
|
}
|
|
}
|
|
|
|
function jqLiteClone(element) {
|
|
return element.cloneNode(true);
|
|
}
|
|
|
|
function jqLiteDealoc(element){
|
|
jqLiteRemoveData(element);
|
|
var childElement;
|
|
for ( var i = 0, children = element.children, l = (children && children.length) || 0; i < l; i++) {
|
|
childElement = children[i];
|
|
jqLiteDealoc(childElement);
|
|
}
|
|
}
|
|
|
|
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.ng339,
|
|
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.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
|
|
}
|
|
}
|
|
|
|
function jqLiteExpandoStore(element, key, value) {
|
|
var expandoId = element.ng339,
|
|
expandoStore = jqCache[expandoId || -1];
|
|
|
|
if (isDefined(value)) {
|
|
if (!expandoStore) {
|
|
element.ng339 = 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) {
|
|
// set data only on Elements and Documents
|
|
if (element.nodeType === 1 || element.nodeType === 9) {
|
|
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) {
|
|
// 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) {
|
|
if (elements.item) {
|
|
// convert NodeList to an Array to make PhantomJS 1.x happy
|
|
elements = slice.call(elements);
|
|
}
|
|
push.apply(root, elements);
|
|
}
|
|
} else {
|
|
root[root.length++] = elements;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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) {
|
|
var node = element[0];
|
|
for (var i = 0, ii = names.length; i < ii; i++) {
|
|
if ((value = element.data(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 = jqLite(node.parentNode || (node.nodeType === 11 && node.host));
|
|
}
|
|
}
|
|
|
|
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[value] = true;
|
|
});
|
|
var ALIASED_ATTR = {
|
|
'ngMinlength' : 'minlength',
|
|
'ngMaxlength' : 'maxlength',
|
|
'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,
|
|
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() {
|
|
getText.$dv = '';
|
|
return getText;
|
|
|
|
function getText(element, value) {
|
|
if (isUndefined(value)) {
|
|
var nodeType = element.nodeType;
|
|
return (nodeType === 1 || nodeType === 3) ? 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;
|
|
}
|
|
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;
|
|
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) {
|
|
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.contentDocument || 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 (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 !== 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;
|
|
});
|