'use strict';
//////////////////////////////////
//JQLite
//////////////////////////////////
/**
* @ngdoc function
* @name angular.element
* @function
*
* @description
* Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
* `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
* jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
* implementation (commonly referred to as jqLite).
*
* Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
* event fired.
*
* jqLite is a tiny, API-compatible subset of jQuery that allows
* Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
* within a very small footprint, so only a subset of the jQuery API - methods, arguments and
* invocation styles - are supported.
*
* Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
* raw DOM references.
*
* ## Angular's jQuery lite provides the following 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/)
* - [children()](http://api.jquery.com/children/)
* - [clone()](http://api.jquery.com/clone/)
* - [css()](http://api.jquery.com/css/)
* - [data()](http://api.jquery.com/data/)
* - [eq()](http://api.jquery.com/eq/)
* - [hasClass()](http://api.jquery.com/hasClass/)
* - [parent()](http://api.jquery.com/parent/)
* - [prop()](http://api.jquery.com/prop/)
* - [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/)
* - [trigger()](http://api.jquery.com/trigger/)
* - [unbind()](http://api.jquery.com/unbind/)
*
* ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
*
* - `scope()` - retrieves the current Angular scope of the element.
*
* @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
* @returns {Object} jQuery object.
*/
var jqCache = {},
jqName = '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++); }
function getStyle(element) {
var current = {}, style = element[0].style, value, name, i;
if (typeof style.length == 'number') {
for(i = 0; i < style.length; i++) {
name = style[i];
current[name] = style[name];
}
} else {
for (name in style) {
value = style[name];
if (1*name != name && name != 'cssText' && value && typeof value == 'string' && value !='false')
current[name] = value;
}
}
return current;
}
/**
* Converts dash-separated names to camelCase. Useful for dealing with css properties.
*/
function camelCase(name) {
return name.replace(/\-(\w)/g, function(all, letter, offset){
return (offset == 0 && letter == 'w') ? 'w' : letter.toUpperCase();
});
}
/////////////////////////////////////////////
// jQuery mutation patch
/////////////////////////////////////////////
function JQLitePatchJQueryRemove(name, dispatchThis) {
var originalJqFn = jQuery.fn[name];
originalJqFn = originalJqFn.$original || originalJqFn;
removePatch.$original = originalJqFn;
jQuery.fn[name] = removePatch;
function removePatch() {
var list = [this],
fireEvent = dispatchThis,
set, setIndex, setLength,
element, childIndex, childLength, children,
fns, data;
while(list.length) {
set = list.shift();
for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
element = jqLite(set[setIndex]);
if (fireEvent) {
data = element.data('events');
if ( (fns = data && data.$destroy) ) {
forEach(fns, function(fn){
fn.handler();
});
}
} 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 jqLiteWrap(element) {
if (isString(element) && element.charAt(0) != '<') {
throw new Error('selectors not implemented');
}
return new JQLite(element);
}
function JQLite(element) {
if (element instanceof JQLite) {
return element;
} else if (isString(element)) {
var div = document.createElement('div');
// Read about the NoScope elements here:
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
div.innerHTML = '
' + element; // IE insanity to make NoScope elements work!
div.removeChild(div.firstChild); // remove the superfluous div
JQLiteAddNodes(this, div.childNodes);
this.remove(); // 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 JQLiteRemoveData(element) {
var cacheId = element[jqName],
cache = jqCache[cacheId];
if (cache) {
if (cache.bind) {
forEach(cache.bind, function(fn, type){
if (type == '$destroy') {
fn({});
} else {
removeEventListenerFn(element, type, fn);
}
});
}
delete jqCache[cacheId];
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
}
}
function JQLiteData(element, key, value) {
var cacheId = element[jqName],
cache = jqCache[cacheId || -1];
if (isDefined(value)) {
if (!cache) {
element[jqName] = cacheId = jqNextId();
cache = jqCache[cacheId] = {};
}
cache[key] = value;
} else {
return cache ? cache[key] : null;
}
}
function JQLiteHasClass(element, selector, _) {
// the argument '_' is important, since it makes the function have 3 arguments, which
// is needed for delegate function to realize the this is a getter.
return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
indexOf( " " + selector + " " ) > -1);
}
function JQLiteRemoveClass(element, selector) {
if (selector) {
forEach(selector.split(' '), function(cssClass) {
element.className = trim(
(" " + element.className + " ")
.replace(/[\n\t]/g, " ")
.replace(" " + trim(cssClass) + " ", " ")
);
});
}
}
function JQLiteAddClass(element, selector) {
if (selector) {
forEach(selector.split(' '), function(cssClass) {
if (!JQLiteHasClass(element, cssClass)) {
element.className = trim(element.className + ' ' + trim(cssClass));
}
});
}
}
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]);
}
}
}
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
var JQLitePrototype = JQLite.prototype = {
ready: function(fn) {
var fired = false;
function trigger() {
if (fired) return;
fired = true;
fn();
}
this.bind('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.
jqLiteWrap(window).bind('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 SPECIAL_ATTR = makeMap("multiple,selected,checked,disabled,readonly,required");
forEach({
data: JQLiteData,
inheritedData: function(element, name, value) {
element = jqLite(element);
while (element.length) {
if (value = element.data(name)) return value;
element = element.parent();
}
},
scope: function(element) {
return jqLite(element).inheritedData($$scope);
},
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){
if (SPECIAL_ATTR[name]) {
if (isDefined(value)) {
if (!!value) {
element[name] = true;
element.setAttribute(name, name);
} else {
element[name] = false;
element.removeAttribute(name);
}
} else {
return (element[name] || element.getAttribute(name)) ? name : 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: extend((msie < 9)
? function(element, value) {
// NodeType == 3 is text node
if (element.nodeType == 3) {
if (isUndefined(value))
return element.nodeValue;
element.nodeValue = value;
} else {
if (isUndefined(value))
return element.innerText;
element.innerText = value;
}
}
: function(element, value) {
if (isUndefined(value)) {
return element.textContent;
}
element.textContent = value;
}, {$dv:''}),
val: function(element, value) {
if (isUndefined(value)) {
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;
}
}, function(fn, name){
/**
* Properties: writes return selection, reads return first value
*/
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
if ((fn.length == 2 ? 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++) {
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.
if (this.length)
return fn(this[0], arg1, arg2);
}
} 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;
}
return fn.$dv;
};
});
//////////////////////////////////////////
// Functions iterating traversal.
// These functions chain results into a single
// selector.
//////////////////////////////////////////
forEach({
removeData: JQLiteRemoveData,
dealoc: JQLiteDealoc,
bind: function(element, type, fn){
var bind = JQLiteData(element, 'bind');
if (!bind) JQLiteData(element, 'bind', bind = {});
forEach(type.split(' '), function(type){
var eventHandler = bind[type];
if (!eventHandler) {
bind[type] = eventHandler = function(event) {
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;
};
forEach(eventHandler.fns, function(fn){
fn.call(element, event);
});
};
eventHandler.fns = [];
addEventListenerFn(element, type, eventHandler);
}
eventHandler.fns.push(fn);
});
},
unbind: function(element, type, fn) {
var bind = JQLiteData(element, 'bind');
if (!bind) return; //no listeners registered
if (isUndefined(type)) {
forEach(bind, function(eventHandler, type) {
removeEventListenerFn(element, type, eventHandler);
delete bind[type];
});
} else {
if (isUndefined(fn)) {
removeEventListenerFn(element, type, bind[type]);
delete bind[type];
} else {
angularArray.remove(bind[type].fns, 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.nodeName != '#text')
children.push(element);
});
return children;
},
append: function(element, node) {
forEach(new JQLite(node), function(child){
if (element.nodeType === 1)
element.appendChild(child);
});
},
prepend: function(element, node) {
if (element.nodeType === 1) {
var index = element.firstChild;
forEach(new JQLite(node), function(child){
if (index) {
element.insertBefore(child, index);
} else {
element.appendChild(child);
index = child;
}
});
}
},
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) {
return element.nextSibling;
},
find: function(element, selector) {
return element.getElementsByTagName(selector);
},
clone: JQLiteClone
}, function(fn, name){
/**
* chaining functions
*/
JQLite.prototype[name] = function(arg1, arg2) {
var value;
for(var i=0; i < this.length; i++) {
if (value == undefined) {
value = fn(this[i], arg1, arg2);
if (value !== undefined) {
// any function which returns a value needs to be wrapped
value = jqLite(value);
}
} else {
JQLiteAddNodes(value, fn(this[i], arg1, arg2));
}
}
return value == undefined ? this : value;
};
});