mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-04-09 17:41:01 +00:00
If an event handler unbinds itself, the next event handler on the same event and element doesn't get executed. This works fine in jQuery, and since jqLite doesn't support .one, this might be a common use case.
888 lines
28 KiB
JavaScript
888 lines
28 KiB
JavaScript
'use strict';
|
|
|
|
/* global
|
|
|
|
-JQLitePrototype,
|
|
-addEventListenerFn,
|
|
-removeEventListenerFn,
|
|
-BOOLEAN_ATTR
|
|
*/
|
|
|
|
//////////////////////////////////
|
|
//JQLite
|
|
//////////////////////////////////
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name angular.element
|
|
* @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/on/) - 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
|
|
* - [`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/off/) - 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 api/ng.$rootScope.Scope scope} of the current
|
|
* element or its parent.
|
|
* - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the
|
|
* current element. This getter should be used only on elements that contain a directive which starts a new isolate
|
|
* scope. Calling `scope()` on this element always returns the original non-isolate scope.
|
|
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
|
|
* parent element is reached.
|
|
*
|
|
* @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
|
|
* @returns {Object} jQuery object.
|
|
*/
|
|
|
|
var jqCache = JQLite.cache = {},
|
|
jqName = JQLite.expando = 'ng-' + new Date().getTime(),
|
|
jqId = 1,
|
|
addEventListenerFn = (window.document.addEventListener
|
|
? function(element, type, fn) {element.addEventListener(type, fn, false);}
|
|
: function(element, type, fn) {element.attachEvent('on' + type, fn);}),
|
|
removeEventListenerFn = (window.document.removeEventListener
|
|
? function(element, type, fn) {element.removeEventListener(type, fn, false); }
|
|
: function(element, type, fn) {element.detachEvent('on' + type, fn); });
|
|
|
|
function jqNextId() { return ++jqId; }
|
|
|
|
|
|
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
|
|
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
|
|
var jqLiteMinErr = minErr('jqLite');
|
|
|
|
/**
|
|
* Converts snake_case to camelCase.
|
|
* Also there is special case for Moz prefix starting with upper case letter.
|
|
* @param name Name to normalize
|
|
*/
|
|
function camelCase(name) {
|
|
return name.
|
|
replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
|
|
return offset ? letter.toUpperCase() : letter;
|
|
}).
|
|
replace(MOZ_HACK_REGEXP, 'Moz$1');
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
// jQuery mutation patch
|
|
//
|
|
// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
|
|
// $destroy event on all DOM nodes being removed.
|
|
//
|
|
/////////////////////////////////////////////
|
|
|
|
function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) {
|
|
var originalJqFn = jQuery.fn[name];
|
|
originalJqFn = originalJqFn.$original || originalJqFn;
|
|
removePatch.$original = originalJqFn;
|
|
jQuery.fn[name] = removePatch;
|
|
|
|
function removePatch(param) {
|
|
// jshint -W040
|
|
var list = filterElems && param ? [this.filter(param)] : [this],
|
|
fireEvent = dispatchThis,
|
|
set, setIndex, setLength,
|
|
element, childIndex, childLength, children;
|
|
|
|
if (!getterIfNoArguments || param != null) {
|
|
while(list.length) {
|
|
set = list.shift();
|
|
for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
|
|
element = jqLite(set[setIndex]);
|
|
if (fireEvent) {
|
|
element.triggerHandler('$destroy');
|
|
} else {
|
|
fireEvent = !fireEvent;
|
|
}
|
|
for(childIndex = 0, childLength = (children = element.children()).length;
|
|
childIndex < childLength;
|
|
childIndex++) {
|
|
list.push(jQuery(children[childIndex]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return originalJqFn.apply(this, arguments);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
function JQLite(element) {
|
|
if (element instanceof JQLite) {
|
|
return element;
|
|
}
|
|
if (!(this instanceof JQLite)) {
|
|
if (isString(element) && element.charAt(0) != '<') {
|
|
throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
|
|
}
|
|
return new JQLite(element);
|
|
}
|
|
|
|
if (isString(element)) {
|
|
var div = document.createElement('div');
|
|
// Read about the NoScope elements here:
|
|
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
|
|
div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work!
|
|
div.removeChild(div.firstChild); // remove the superfluous div
|
|
jqLiteAddNodes(this, div.childNodes);
|
|
var fragment = jqLite(document.createDocumentFragment());
|
|
fragment.append(this); // detach the elements from the temporary DOM div.
|
|
} else {
|
|
jqLiteAddNodes(this, element);
|
|
}
|
|
}
|
|
|
|
function jqLiteClone(element) {
|
|
return element.cloneNode(true);
|
|
}
|
|
|
|
function jqLiteDealoc(element){
|
|
jqLiteRemoveData(element);
|
|
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
|
|
jqLiteDealoc(children[i]);
|
|
}
|
|
}
|
|
|
|
function jqLiteOff(element, type, fn, unsupported) {
|
|
if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
|
|
|
|
var events = jqLiteExpandoStore(element, 'events'),
|
|
handle = jqLiteExpandoStore(element, 'handle');
|
|
|
|
if (!handle) return; //no listeners registered
|
|
|
|
if (isUndefined(type)) {
|
|
forEach(events, function(eventHandler, type) {
|
|
removeEventListenerFn(element, type, eventHandler);
|
|
delete events[type];
|
|
});
|
|
} else {
|
|
forEach(type.split(' '), function(type) {
|
|
if (isUndefined(fn)) {
|
|
removeEventListenerFn(element, type, events[type]);
|
|
delete events[type];
|
|
} else {
|
|
arrayRemove(events[type] || [], fn);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function jqLiteRemoveData(element, name) {
|
|
var expandoId = element[jqName],
|
|
expandoStore = jqCache[expandoId];
|
|
|
|
if (expandoStore) {
|
|
if (name) {
|
|
delete jqCache[expandoId].data[name];
|
|
return;
|
|
}
|
|
|
|
if (expandoStore.handle) {
|
|
expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
|
|
jqLiteOff(element);
|
|
}
|
|
delete jqCache[expandoId];
|
|
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
|
|
}
|
|
}
|
|
|
|
function jqLiteExpandoStore(element, key, value) {
|
|
var expandoId = element[jqName],
|
|
expandoStore = jqCache[expandoId || -1];
|
|
|
|
if (isDefined(value)) {
|
|
if (!expandoStore) {
|
|
element[jqName] = expandoId = jqNextId();
|
|
expandoStore = jqCache[expandoId] = {};
|
|
}
|
|
expandoStore[key] = value;
|
|
} else {
|
|
return expandoStore && expandoStore[key];
|
|
}
|
|
}
|
|
|
|
function jqLiteData(element, key, value) {
|
|
var data = jqLiteExpandoStore(element, 'data'),
|
|
isSetter = isDefined(value),
|
|
keyDefined = !isSetter && isDefined(key),
|
|
isSimpleGetter = keyDefined && !isObject(key);
|
|
|
|
if (!data && !isSimpleGetter) {
|
|
jqLiteExpandoStore(element, 'data', data = {});
|
|
}
|
|
|
|
if (isSetter) {
|
|
data[key] = value;
|
|
} else {
|
|
if (keyDefined) {
|
|
if (isSimpleGetter) {
|
|
// don't create data in this case.
|
|
return data && data[key];
|
|
} else {
|
|
extend(data, key);
|
|
}
|
|
} else {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
|
|
function jqLiteHasClass(element, selector) {
|
|
if (!element.getAttribute) return false;
|
|
return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
|
|
indexOf( " " + selector + " " ) > -1);
|
|
}
|
|
|
|
function jqLiteRemoveClass(element, cssClasses) {
|
|
if (cssClasses && element.setAttribute) {
|
|
forEach(cssClasses.split(' '), function(cssClass) {
|
|
element.setAttribute('class', trim(
|
|
(" " + (element.getAttribute('class') || '') + " ")
|
|
.replace(/[\n\t]/g, " ")
|
|
.replace(" " + trim(cssClass) + " ", " "))
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
function jqLiteAddClass(element, cssClasses) {
|
|
if (cssClasses && element.setAttribute) {
|
|
var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
|
|
.replace(/[\n\t]/g, " ");
|
|
|
|
forEach(cssClasses.split(' '), function(cssClass) {
|
|
cssClass = trim(cssClass);
|
|
if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
|
|
existingClasses += cssClass + ' ';
|
|
}
|
|
});
|
|
|
|
element.setAttribute('class', trim(existingClasses));
|
|
}
|
|
}
|
|
|
|
function jqLiteAddNodes(root, elements) {
|
|
if (elements) {
|
|
elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
|
|
? elements
|
|
: [ elements ];
|
|
for(var i=0; i < elements.length; i++) {
|
|
root.push(elements[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function jqLiteController(element, name) {
|
|
return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
|
|
}
|
|
|
|
function jqLiteInheritedData(element, name, value) {
|
|
element = jqLite(element);
|
|
|
|
// if element is the document object work with the html element instead
|
|
// this makes $(document).scope() possible
|
|
if(element[0].nodeType == 9) {
|
|
element = element.find('html');
|
|
}
|
|
var names = isArray(name) ? name : [name];
|
|
|
|
while (element.length) {
|
|
|
|
for (var i = 0, ii = names.length; i < ii; i++) {
|
|
if ((value = element.data(names[i])) !== undefined) return value;
|
|
}
|
|
element = element.parent();
|
|
}
|
|
}
|
|
|
|
function jqLiteEmpty(element) {
|
|
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
|
|
jqLiteDealoc(childNodes[i]);
|
|
}
|
|
while (element.firstChild) {
|
|
element.removeChild(element.firstChild);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
// Functions which are declared directly.
|
|
//////////////////////////////////////////
|
|
var JQLitePrototype = JQLite.prototype = {
|
|
ready: function(fn) {
|
|
var fired = false;
|
|
|
|
function trigger() {
|
|
if (fired) return;
|
|
fired = true;
|
|
fn();
|
|
}
|
|
|
|
// check if document already is loaded
|
|
if (document.readyState === 'complete'){
|
|
setTimeout(trigger);
|
|
} else {
|
|
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
|
|
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
|
|
// jshint -W064
|
|
JQLite(window).on('load', trigger); // fallback to window.onload for others
|
|
// jshint +W064
|
|
}
|
|
},
|
|
toString: function() {
|
|
var value = [];
|
|
forEach(this, function(e){ value.push('' + e);});
|
|
return '[' + value.join(', ') + ']';
|
|
},
|
|
|
|
eq: function(index) {
|
|
return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
|
|
},
|
|
|
|
length: 0,
|
|
push: push,
|
|
sort: [].sort,
|
|
splice: [].splice
|
|
};
|
|
|
|
//////////////////////////////////////////
|
|
// Functions iterating getter/setters.
|
|
// these functions return self on setter and
|
|
// value on get.
|
|
//////////////////////////////////////////
|
|
var BOOLEAN_ATTR = {};
|
|
forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
|
|
BOOLEAN_ATTR[lowercase(value)] = value;
|
|
});
|
|
var BOOLEAN_ELEMENTS = {};
|
|
forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
|
|
BOOLEAN_ELEMENTS[uppercase(value)] = true;
|
|
});
|
|
|
|
function getBooleanAttrName(element, name) {
|
|
// check dom last since we will most likely fail on name
|
|
var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
|
|
|
|
// booleanAttr is here twice to minimize DOM access
|
|
return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr;
|
|
}
|
|
|
|
forEach({
|
|
data: jqLiteData,
|
|
inheritedData: jqLiteInheritedData,
|
|
|
|
scope: function(element) {
|
|
// Can't use jqLiteData here directly so we stay compatible with jQuery!
|
|
return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
|
|
},
|
|
|
|
isolateScope: function(element) {
|
|
// Can't use jqLiteData here directly so we stay compatible with jQuery!
|
|
return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate');
|
|
},
|
|
|
|
controller: jqLiteController ,
|
|
|
|
injector: function(element) {
|
|
return jqLiteInheritedData(element, '$injector');
|
|
},
|
|
|
|
removeAttr: function(element,name) {
|
|
element.removeAttribute(name);
|
|
},
|
|
|
|
hasClass: jqLiteHasClass,
|
|
|
|
css: function(element, name, value) {
|
|
name = camelCase(name);
|
|
|
|
if (isDefined(value)) {
|
|
element.style[name] = value;
|
|
} else {
|
|
var val;
|
|
|
|
if (msie <= 8) {
|
|
// this is some IE specific weirdness that jQuery 1.6.4 does not sure why
|
|
val = element.currentStyle && element.currentStyle[name];
|
|
if (val === '') val = 'auto';
|
|
}
|
|
|
|
val = val || element.style[name];
|
|
|
|
if (msie <= 8) {
|
|
// jquery weirdness :-/
|
|
val = (val === '') ? undefined : val;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
},
|
|
|
|
attr: function(element, name, value){
|
|
var lowercasedName = lowercase(name);
|
|
if (BOOLEAN_ATTR[lowercasedName]) {
|
|
if (isDefined(value)) {
|
|
if (!!value) {
|
|
element[name] = true;
|
|
element.setAttribute(name, lowercasedName);
|
|
} else {
|
|
element[name] = false;
|
|
element.removeAttribute(lowercasedName);
|
|
}
|
|
} else {
|
|
return (element[name] ||
|
|
(element.attributes.getNamedItem(name)|| noop).specified)
|
|
? lowercasedName
|
|
: undefined;
|
|
}
|
|
} else if (isDefined(value)) {
|
|
element.setAttribute(name, value);
|
|
} else if (element.getAttribute) {
|
|
// the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
|
|
// some elements (e.g. Document) don't have get attribute, so return undefined
|
|
var ret = element.getAttribute(name, 2);
|
|
// normalize non-existing attributes to undefined (as jQuery)
|
|
return ret === null ? undefined : ret;
|
|
}
|
|
},
|
|
|
|
prop: function(element, name, value) {
|
|
if (isDefined(value)) {
|
|
element[name] = value;
|
|
} else {
|
|
return element[name];
|
|
}
|
|
},
|
|
|
|
text: (function() {
|
|
var NODE_TYPE_TEXT_PROPERTY = [];
|
|
if (msie < 9) {
|
|
NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/
|
|
NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/
|
|
} else {
|
|
NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/
|
|
NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/
|
|
}
|
|
getText.$dv = '';
|
|
return getText;
|
|
|
|
function getText(element, value) {
|
|
var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType];
|
|
if (isUndefined(value)) {
|
|
return textProp ? element[textProp] : '';
|
|
}
|
|
element[textProp] = value;
|
|
}
|
|
})(),
|
|
|
|
val: function(element, value) {
|
|
if (isUndefined(value)) {
|
|
if (nodeName_(element) === 'SELECT' && element.multiple) {
|
|
var result = [];
|
|
forEach(element.options, function (option) {
|
|
if (option.selected) {
|
|
result.push(option.value || option.text);
|
|
}
|
|
});
|
|
return result.length === 0 ? null : result;
|
|
}
|
|
return element.value;
|
|
}
|
|
element.value = value;
|
|
},
|
|
|
|
html: function(element, value) {
|
|
if (isUndefined(value)) {
|
|
return element.innerHTML;
|
|
}
|
|
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
|
|
jqLiteDealoc(childNodes[i]);
|
|
}
|
|
element.innerHTML = value;
|
|
},
|
|
|
|
empty: jqLiteEmpty
|
|
}, function(fn, name){
|
|
/**
|
|
* Properties: writes return selection, reads return first value
|
|
*/
|
|
JQLite.prototype[name] = function(arg1, arg2) {
|
|
var i, key;
|
|
|
|
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
|
|
// in a way that survives minification.
|
|
// jqLiteEmpty takes no arguments but is a setter.
|
|
if (fn !== jqLiteEmpty &&
|
|
(((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
|
|
if (isObject(arg1)) {
|
|
|
|
// we are a write, but the object properties are the key/values
|
|
for (i = 0; i < this.length; i++) {
|
|
if (fn === jqLiteData) {
|
|
// data() takes the whole object in jQuery
|
|
fn(this[i], arg1);
|
|
} else {
|
|
for (key in arg1) {
|
|
fn(this[i], key, arg1[key]);
|
|
}
|
|
}
|
|
}
|
|
// return self for chaining
|
|
return this;
|
|
} else {
|
|
// we are a read, so read the first child.
|
|
var value = fn.$dv;
|
|
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
|
|
var jj = (value === undefined) ? Math.min(this.length, 1) : this.length;
|
|
for (var j = 0; j < jj; j++) {
|
|
var nodeValue = fn(this[j], arg1, arg2);
|
|
value = value ? value + nodeValue : nodeValue;
|
|
}
|
|
return value;
|
|
}
|
|
} else {
|
|
// we are a write, so apply to all children
|
|
for (i = 0; i < this.length; i++) {
|
|
fn(this[i], arg1, arg2);
|
|
}
|
|
// return self for chaining
|
|
return this;
|
|
}
|
|
};
|
|
});
|
|
|
|
function createEventHandler(element, events) {
|
|
var eventHandler = function (event, type) {
|
|
if (!event.preventDefault) {
|
|
event.preventDefault = function() {
|
|
event.returnValue = false; //ie
|
|
};
|
|
}
|
|
|
|
if (!event.stopPropagation) {
|
|
event.stopPropagation = function() {
|
|
event.cancelBubble = true; //ie
|
|
};
|
|
}
|
|
|
|
if (!event.target) {
|
|
event.target = event.srcElement || document;
|
|
}
|
|
|
|
if (isUndefined(event.defaultPrevented)) {
|
|
var prevent = event.preventDefault;
|
|
event.preventDefault = function() {
|
|
event.defaultPrevented = true;
|
|
prevent.call(event);
|
|
};
|
|
event.defaultPrevented = false;
|
|
}
|
|
|
|
event.isDefaultPrevented = function() {
|
|
return event.defaultPrevented || event.returnValue === false;
|
|
};
|
|
|
|
// Copy event handlers in case event handlers array is modified during execution.
|
|
var eventHandlersCopy = shallowCopy(events[type || event.type] || []);
|
|
|
|
forEach(eventHandlersCopy, function(fn) {
|
|
fn.call(element, event);
|
|
});
|
|
|
|
// Remove monkey-patched methods (IE),
|
|
// as they would cause memory leaks in IE8.
|
|
if (msie <= 8) {
|
|
// IE7/8 does not allow to delete property on native object
|
|
event.preventDefault = null;
|
|
event.stopPropagation = null;
|
|
event.isDefaultPrevented = null;
|
|
} else {
|
|
// It shouldn't affect normal browsers (native methods are defined on prototype).
|
|
delete event.preventDefault;
|
|
delete event.stopPropagation;
|
|
delete event.isDefaultPrevented;
|
|
}
|
|
};
|
|
eventHandler.elem = element;
|
|
return eventHandler;
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
// Functions iterating traversal.
|
|
// These functions chain results into a single
|
|
// selector.
|
|
//////////////////////////////////////////
|
|
forEach({
|
|
removeData: jqLiteRemoveData,
|
|
|
|
dealoc: jqLiteDealoc,
|
|
|
|
on: function onFn(element, type, fn, unsupported){
|
|
if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
|
|
|
|
var events = jqLiteExpandoStore(element, 'events'),
|
|
handle = jqLiteExpandoStore(element, 'handle');
|
|
|
|
if (!events) jqLiteExpandoStore(element, 'events', events = {});
|
|
if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
|
|
|
|
forEach(type.split(' '), function(type){
|
|
var eventFns = events[type];
|
|
|
|
if (!eventFns) {
|
|
if (type == 'mouseenter' || type == 'mouseleave') {
|
|
var contains = document.body.contains || document.body.compareDocumentPosition ?
|
|
function( a, b ) {
|
|
// jshint bitwise: false
|
|
var adown = a.nodeType === 9 ? a.documentElement : a,
|
|
bup = b && b.parentNode;
|
|
return a === bup || !!( bup && bup.nodeType === 1 && (
|
|
adown.contains ?
|
|
adown.contains( bup ) :
|
|
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
|
|
));
|
|
} :
|
|
function( a, b ) {
|
|
if ( b ) {
|
|
while ( (b = b.parentNode) ) {
|
|
if ( b === a ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
events[type] = [];
|
|
|
|
// Refer to jQuery's implementation of mouseenter & mouseleave
|
|
// Read about mouseenter and mouseleave:
|
|
// http://www.quirksmode.org/js/events_mouse.html#link8
|
|
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};
|
|
|
|
onFn(element, eventmap[type], function(event) {
|
|
var target = this, related = event.relatedTarget;
|
|
// For mousenter/leave call the handler if related is outside the target.
|
|
// NB: No relatedTarget if the mouse left/entered the browser window
|
|
if ( !related || (related !== target && !contains(target, related)) ){
|
|
handle(event, type);
|
|
}
|
|
});
|
|
|
|
} else {
|
|
addEventListenerFn(element, type, handle);
|
|
events[type] = [];
|
|
}
|
|
eventFns = events[type];
|
|
}
|
|
eventFns.push(fn);
|
|
});
|
|
},
|
|
|
|
off: jqLiteOff,
|
|
|
|
replaceWith: function(element, replaceNode) {
|
|
var index, parent = element.parentNode;
|
|
jqLiteDealoc(element);
|
|
forEach(new JQLite(replaceNode), function(node){
|
|
if (index) {
|
|
parent.insertBefore(node, index.nextSibling);
|
|
} else {
|
|
parent.replaceChild(node, element);
|
|
}
|
|
index = node;
|
|
});
|
|
},
|
|
|
|
children: function(element) {
|
|
var children = [];
|
|
forEach(element.childNodes, function(element){
|
|
if (element.nodeType === 1)
|
|
children.push(element);
|
|
});
|
|
return children;
|
|
},
|
|
|
|
contents: function(element) {
|
|
return element.childNodes || [];
|
|
},
|
|
|
|
append: function(element, node) {
|
|
forEach(new JQLite(node), function(child){
|
|
if (element.nodeType === 1 || element.nodeType === 11) {
|
|
element.appendChild(child);
|
|
}
|
|
});
|
|
},
|
|
|
|
prepend: function(element, node) {
|
|
if (element.nodeType === 1) {
|
|
var index = element.firstChild;
|
|
forEach(new JQLite(node), function(child){
|
|
element.insertBefore(child, index);
|
|
});
|
|
}
|
|
},
|
|
|
|
wrap: function(element, wrapNode) {
|
|
wrapNode = jqLite(wrapNode)[0];
|
|
var parent = element.parentNode;
|
|
if (parent) {
|
|
parent.replaceChild(wrapNode, element);
|
|
}
|
|
wrapNode.appendChild(element);
|
|
},
|
|
|
|
remove: function(element) {
|
|
jqLiteDealoc(element);
|
|
var parent = element.parentNode;
|
|
if (parent) parent.removeChild(element);
|
|
},
|
|
|
|
after: function(element, newElement) {
|
|
var index = element, parent = element.parentNode;
|
|
forEach(new JQLite(newElement), function(node){
|
|
parent.insertBefore(node, index.nextSibling);
|
|
index = node;
|
|
});
|
|
},
|
|
|
|
addClass: jqLiteAddClass,
|
|
removeClass: jqLiteRemoveClass,
|
|
|
|
toggleClass: function(element, selector, condition) {
|
|
if (isUndefined(condition)) {
|
|
condition = !jqLiteHasClass(element, selector);
|
|
}
|
|
(condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector);
|
|
},
|
|
|
|
parent: function(element) {
|
|
var parent = element.parentNode;
|
|
return parent && parent.nodeType !== 11 ? parent : null;
|
|
},
|
|
|
|
next: function(element) {
|
|
if (element.nextElementSibling) {
|
|
return element.nextElementSibling;
|
|
}
|
|
|
|
// IE8 doesn't have nextElementSibling
|
|
var elm = element.nextSibling;
|
|
while (elm != null && elm.nodeType !== 1) {
|
|
elm = elm.nextSibling;
|
|
}
|
|
return elm;
|
|
},
|
|
|
|
find: function(element, selector) {
|
|
if (element.getElementsByTagName) {
|
|
return element.getElementsByTagName(selector);
|
|
} else {
|
|
return [];
|
|
}
|
|
},
|
|
|
|
clone: jqLiteClone,
|
|
|
|
triggerHandler: function(element, eventName, eventData) {
|
|
var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName];
|
|
|
|
eventData = eventData || [];
|
|
|
|
var event = [{
|
|
preventDefault: noop,
|
|
stopPropagation: noop
|
|
}];
|
|
|
|
forEach(eventFns, function(fn) {
|
|
fn.apply(element, event.concat(eventData));
|
|
});
|
|
}
|
|
}, function(fn, name){
|
|
/**
|
|
* chaining functions
|
|
*/
|
|
JQLite.prototype[name] = function(arg1, arg2, arg3) {
|
|
var value;
|
|
for(var i=0; i < this.length; i++) {
|
|
if (isUndefined(value)) {
|
|
value = fn(this[i], arg1, arg2, arg3);
|
|
if (isDefined(value)) {
|
|
// any function which returns a value needs to be wrapped
|
|
value = jqLite(value);
|
|
}
|
|
} else {
|
|
jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
|
|
}
|
|
}
|
|
return isDefined(value) ? value : this;
|
|
};
|
|
|
|
// bind legacy bind/unbind to on/off
|
|
JQLite.prototype.bind = JQLite.prototype.on;
|
|
JQLite.prototype.unbind = JQLite.prototype.off;
|
|
});
|