You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sirona.apache.org by ol...@apache.org on 2014/10/15 12:43:56 UTC
svn commit: r1631991 [2/5] -
/incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/
Copied: incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0.js (from r1629557, incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0-beta.17.js)
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0.js?p2=incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0.js&p1=incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0-beta.17.js&r1=1629557&r2=1631991&rev=1631991&view=diff
==============================================================================
--- incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0-beta.17.js (original)
+++ incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/angular-1.3.0.js Wed Oct 15 10:43:55 2014
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.0-beta.17
+ * @license AngularJS v1.3.0
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -30,10 +30,13 @@
* should all be static strings, not variables or general expressions.
*
* @param {string} module The namespace to use for the new minErr instance.
+ * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
+ * error from returned function, for cases when a particular type of error is useful.
* @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
*/
-function minErr(module) {
+function minErr(module, ErrorConstructor) {
+ ErrorConstructor = ErrorConstructor || Error;
return function () {
var code = arguments[0],
prefix = '[' + (module ? module + ':' : '') + code + '] ',
@@ -68,14 +71,13 @@ function minErr(module) {
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.3.0-beta.17/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.0/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
encodeURIComponent(stringify(arguments[i]));
}
-
- return new Error(message);
+ return new ErrorConstructor(message);
};
}
@@ -85,11 +87,11 @@ function minErr(module) {
jqLite: true,
jQuery: true,
slice: true,
+ splice: true,
push: true,
toString: true,
ngMinErr: true,
angularModule: true,
- nodeName_: true,
uid: true,
REGEX_STRING_REGEXP: true,
VALIDITY_STATE_PROPERTY: true,
@@ -130,10 +132,8 @@ function minErr(module) {
trim: true,
isElement: true,
makeMap: true,
- map: true,
size: true,
includes: true,
- indexOf: true,
arrayRemove: true,
isLeafNode: true,
copy: true,
@@ -154,14 +154,22 @@ function minErr(module) {
encodeUriQuery: true,
angularInit: true,
bootstrap: true,
+ getTestability: true,
snake_case: true,
bindJQuery: true,
assertArg: true,
assertArgFn: true,
assertNotHasOwnProperty: true,
getter: true,
- getBlockElements: true,
+ getBlockNodes: true,
hasOwnProperty: true,
+ createMap: true,
+
+ NODE_TYPE_ELEMENT: true,
+ NODE_TYPE_TEXT: true,
+ NODE_TYPE_COMMENT: true,
+ NODE_TYPE_DOCUMENT: true,
+ NODE_TYPE_DOCUMENT_FRAGMENT: true,
*/
////////////////////////////////////
@@ -241,6 +249,7 @@ var /** holds major version number for I
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
+ splice = [].splice,
push = [].push,
toString = Object.prototype.toString,
ngMinErr = minErr('ng'),
@@ -248,17 +257,13 @@ var /** holds major version number for I
/** @name angular */
angular = window.angular || (window.angular = {}),
angularModule,
- nodeName_,
uid = 0;
/**
- * IE 11 changed the format of the UserAgent string.
- * See http://msdn.microsoft.com/en-us/library/ms537503.aspx
+ * documentMode is an IE-only property
+ * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
*/
-msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
-if (isNaN(msie)) {
- msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
-}
+msie = document.documentMode;
/**
@@ -274,7 +279,7 @@ function isArrayLike(obj) {
var length = obj.length;
- if (obj.nodeType === 1 && length) {
+ if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
}
@@ -290,9 +295,9 @@ function isArrayLike(obj) {
*
* @description
* Invokes the `iterator` function once for each item in `obj` collection, which can be either an
- * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
- * is the value of an object property or an array element and `key` is the object property key or
- * array element index. Specifying a `context` for the function is optional.
+ * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
+ * is the value of an object property or an array element, `key` is the object property key or
+ * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
*
* It is worth noting that `.forEach` does not iterate over inherited properties because it filters
* using the `hasOwnProperty` method.
@@ -320,19 +325,22 @@ function forEach(obj, iterator, context)
// Need to check if hasOwnProperty exists,
// as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
- iterator.call(context, obj[key], key);
+ iterator.call(context, obj[key], key, obj);
}
}
} else if (isArray(obj) || isArrayLike(obj)) {
+ var isPrimitive = typeof obj !== 'object';
for (key = 0, length = obj.length; key < length; key++) {
- iterator.call(context, obj[key], key);
+ if (isPrimitive || key in obj) {
+ iterator.call(context, obj[key], key, obj);
+ }
}
} else if (obj.forEach && obj.forEach !== forEach) {
- obj.forEach(iterator, context);
+ obj.forEach(iterator, context, obj);
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
- iterator.call(context, obj[key], key);
+ iterator.call(context, obj[key], key, obj);
}
}
}
@@ -404,8 +412,9 @@ function setHashKey(obj, h) {
* @kind function
*
* @description
- * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
- * to `dst`. You can specify multiple `src` objects.
+ * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
+ * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
+ * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
@@ -413,15 +422,19 @@ function setHashKey(obj, h) {
*/
function extend(dst) {
var h = dst.$$hashKey;
- forEach(arguments, function(obj) {
- if (obj !== dst) {
- forEach(obj, function(value, key) {
- dst[key] = value;
- });
+
+ for (var i = 1, ii = arguments.length; i < ii; i++) {
+ var obj = arguments[i];
+ if (obj) {
+ var keys = Object.keys(obj);
+ for (var j = 0, jj = keys.length; j < jj; j++) {
+ var key = keys[j];
+ dst[key] = obj[key];
+ }
}
- });
+ }
- setHashKey(dst,h);
+ setHashKey(dst, h);
return dst;
}
@@ -519,7 +532,10 @@ function isDefined(value){return typeof
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Object` but not `null`.
*/
-function isObject(value){return value != null && typeof value === 'object';}
+function isObject(value){
+ // http://jsperf.com/isobject4
+ return value !== null && typeof value === 'object';
+}
/**
@@ -581,14 +597,7 @@ function isDate(value) {
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Array`.
*/
-var isArray = (function() {
- if (!isFunction(Array.isArray)) {
- return function(value) {
- return toString.call(value) === '[object Array]';
- };
- }
- return Array.isArray;
-})();
+var isArray = Array.isArray;
/**
* @ngdoc function
@@ -654,19 +663,9 @@ function isPromiseLike(obj) {
}
-var trim = (function() {
- // native trim is way faster: http://jsperf.com/angular-trim-test
- // but IE doesn't have it... :-(
- // TODO: we should move this into IE/ES5 polyfill
- if (!String.prototype.trim) {
- return function(value) {
- return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value;
- };
- }
- return function(value) {
- return isString(value) ? value.trim() : value;
- };
-})();
+var trim = function(value) {
+ return isString(value) ? value.trim() : value;
+};
/**
@@ -699,27 +698,8 @@ function makeMap(str) {
}
-if (msie < 9) {
- nodeName_ = function(element) {
- element = element.nodeName ? element : element[0];
- return lowercase(
- (element.scopeName && element.scopeName != 'HTML')
- ? element.scopeName + ':' + element.nodeName : element.nodeName
- );
- };
-} else {
- nodeName_ = function(element) {
- return lowercase(element.nodeName ? element.nodeName : element[0].nodeName);
- };
-}
-
-
-function map(obj, iterator, context) {
- var results = [];
- forEach(obj, function(value, index, list) {
- results.push(iterator.call(context, value, index, list));
- });
- return results;
+function nodeName_(element) {
+ return lowercase(element.nodeName || element[0].nodeName);
}
@@ -751,20 +731,11 @@ function size(obj, ownPropsOnly) {
function includes(array, obj) {
- return indexOf(array, obj) != -1;
-}
-
-function indexOf(array, obj) {
- if (array.indexOf) return array.indexOf(obj);
-
- for (var i = 0; i < array.length; i++) {
- if (obj === array[i]) return i;
- }
- return -1;
+ return Array.prototype.indexOf.call(array, obj) != -1;
}
function arrayRemove(array, value) {
- var index = indexOf(array, value);
+ var index = array.indexOf(value);
if (index >=0)
array.splice(index, 1);
return value;
@@ -869,7 +840,7 @@ function copy(source, destination, stack
stackDest = stackDest || [];
if (isObject(source)) {
- var index = indexOf(stackSource, source);
+ var index = stackSource.indexOf(source);
if (index !== -1) return stackDest[index];
stackSource.push(source);
@@ -889,9 +860,13 @@ function copy(source, destination, stack
}
} else {
var h = destination.$$hashKey;
- forEach(destination, function(value, key) {
- delete destination[key];
- });
+ if (isArray(destination)) {
+ destination.length = 0;
+ } else {
+ forEach(destination, function(value, key) {
+ delete destination[key];
+ });
+ }
for ( var key in source) {
if(source.hasOwnProperty(key)) {
result = copy(source[key], null, stackSource, stackDest);
@@ -910,24 +885,21 @@ function copy(source, destination, stack
}
/**
- * Creates a shallow copy of an object, an array or a primitive
+ * Creates a shallow copy of an object, an array or a primitive.
+ *
+ * Assumes that there are no proto properties for objects.
*/
function shallowCopy(src, dst) {
- var i = 0;
if (isArray(src)) {
dst = dst || [];
- for (; i < src.length; i++) {
+ for (var i = 0, ii = src.length; i < ii; i++) {
dst[i] = src[i];
}
} else if (isObject(src)) {
dst = dst || {};
- var keys = Object.keys(src);
-
- for (var l = keys.length; i < l; i++) {
- var key = keys[i];
-
+ for (var key in src) {
if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
@@ -983,7 +955,8 @@ function equals(o1, o2) {
return true;
}
} else if (isDate(o1)) {
- return isDate(o2) && o1.getTime() == o2.getTime();
+ if (!isDate(o2)) return false;
+ return equals(o1.getTime(), o2.getTime());
} else if (isRegExp(o1) && isRegExp(o2)) {
return o1.toString() == o2.toString();
} else {
@@ -1143,11 +1116,9 @@ function startingTag(element) {
// are not allowed to have children. So we just ignore it.
element.empty();
} catch(e) {}
- // As Per DOM Standards
- var TEXT_NODE = 3;
var elemHtml = jqLite('<div>').append(element).html();
try {
- return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
+ return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
elemHtml.
match(/^(<[^>]+>)/)[1].
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
@@ -1255,6 +1226,7 @@ function encodeUriQuery(val, pctEncodeSp
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
+ replace(/%3B/gi, ';').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
@@ -1489,17 +1461,29 @@ function bootstrap(element, modules, con
if (element.injector()) {
var tag = (element[0] === document) ? 'document' : startingTag(element);
- throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
+ //Encode angle brackets to prevent input from being sanitized to empty string #8683
+ throw ngMinErr(
+ 'btstrpd',
+ "App Already Bootstrapped with this Element '{0}'",
+ tag.replace(/</,'<').replace(/>/,'>'));
}
modules = modules || [];
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
+
+ if (config.debugInfoEnabled) {
+ // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
+ modules.push(['$compileProvider', function($compileProvider) {
+ $compileProvider.debugInfoEnabled(true);
+ }]);
+ }
+
modules.unshift('ng');
var injector = createInjector(modules, config.strictDi);
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
- function(scope, element, compile, injector) {
+ function bootstrapApply(scope, element, compile, injector) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
@@ -1509,8 +1493,14 @@ function bootstrap(element, modules, con
return injector;
};
+ var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
+ if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
+ config.debugInfoEnabled = true;
+ window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
+ }
+
if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
return doBootstrap();
}
@@ -1524,6 +1514,33 @@ function bootstrap(element, modules, con
};
}
+/**
+ * @ngdoc function
+ * @name angular.reloadWithDebugInfo
+ * @module ng
+ * @description
+ * Use this function to reload the current application with debug information turned on.
+ * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
+ *
+ * See {@link ng.$compileProvider#debugInfoEnabled} for more.
+ */
+function reloadWithDebugInfo() {
+ window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
+ window.location.reload();
+}
+
+/**
+ * @name angular.getTestability
+ * @module ng
+ * @description
+ * Get the testability service for the instance of Angular on the given
+ * element.
+ * @param {DOMElement} element DOM element which is the root of angular application.
+ */
+function getTestability(rootElement) {
+ return angular.element(rootElement).injector().get('$$testability');
+}
+
var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator) {
separator = separator || '_';
@@ -1532,12 +1549,21 @@ function snake_case(name, separator) {
});
}
+var bindJQueryFired = false;
+var skipDestroyOnNextJQueryCleanData;
function bindJQuery() {
var originalCleanData;
+
+ if (bindJQueryFired) {
+ return;
+ }
+
// bind to jQuery if present;
jQuery = window.jQuery;
// Use jQuery if it exists with proper functionality, otherwise default to us.
- // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support.
+ // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
+ // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
+ // versions. It will not work for sure with jQuery <1.7, though.
if (jQuery && jQuery.fn.on) {
jqLite = jQuery;
extend(jQuery.fn, {
@@ -1548,25 +1574,32 @@ function bindJQuery() {
inheritedData: JQLitePrototype.inheritedData
});
- originalCleanData = jQuery.cleanData;
- // Prevent double-proxying.
- originalCleanData = originalCleanData.$$original || originalCleanData;
-
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
+ originalCleanData = jQuery.cleanData;
jQuery.cleanData = function(elems) {
- for (var i = 0, elem; (elem = elems[i]) != null; i++) {
- jQuery(elem).triggerHandler('$destroy');
+ var events;
+ if (!skipDestroyOnNextJQueryCleanData) {
+ for (var i = 0, elem; (elem = elems[i]) != null; i++) {
+ events = jQuery._data(elem, "events");
+ if (events && events.$destroy) {
+ jQuery(elem).triggerHandler('$destroy');
+ }
+ }
+ } else {
+ skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
- jQuery.cleanData.$$original = originalCleanData;
} else {
jqLite = JQLite;
}
angular.element = jqLite;
+
+ // Prevent double-proxying.
+ bindJQueryFired = true;
}
/**
@@ -1630,27 +1663,46 @@ function getter(obj, path, bindFnToScope
/**
* Return the DOM siblings between the first and last node in the given array.
* @param {Array} array like object
- * @returns {DOMElement} object containing the elements
+ * @returns {jqLite} jqLite collection containing the nodes
*/
-function getBlockElements(nodes) {
- var startNode = nodes[0],
- endNode = nodes[nodes.length - 1];
- if (startNode === endNode) {
- return jqLite(startNode);
- }
-
- var element = startNode;
- var elements = [element];
+function getBlockNodes(nodes) {
+ // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
+ // collection, otherwise update the original collection.
+ var node = nodes[0];
+ var endNode = nodes[nodes.length - 1];
+ var blockNodes = [node];
do {
- element = element.nextSibling;
- if (!element) break;
- elements.push(element);
- } while (element !== endNode);
+ node = node.nextSibling;
+ if (!node) break;
+ blockNodes.push(node);
+ } while (node !== endNode);
+
+ return jqLite(blockNodes);
+}
+
- return jqLite(elements);
+/**
+ * Creates a new object without a prototype. This object is useful for lookup without having to
+ * guard against prototypically inherited properties via hasOwnProperty.
+ *
+ * Related micro-benchmarks:
+ * - http://jsperf.com/object-create2
+ * - http://jsperf.com/proto-map-lookup/2
+ * - http://jsperf.com/for-in-vs-object-keys2
+ *
+ * @returns {Object}
+ */
+function createMap() {
+ return Object.create(null);
}
+var NODE_TYPE_ELEMENT = 1;
+var NODE_TYPE_TEXT = 3;
+var NODE_TYPE_COMMENT = 8;
+var NODE_TYPE_DOCUMENT = 9;
+var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
+
/**
* @ngdoc type
* @name angular.Module
@@ -1769,7 +1821,7 @@ function setupModuleLoader(window) {
* @ngdoc property
* @name angular.Module#requires
* @module ng
- * @returns {Array.<string>} List of module names which must be loaded before this module.
+ *
* @description
* Holds the list of modules which the injector will load before the current module is
* loaded.
@@ -1780,8 +1832,9 @@ function setupModuleLoader(window) {
* @ngdoc property
* @name angular.Module#name
* @module ng
- * @returns {string} Name of the module.
+ *
* @description
+ * Name of the module.
*/
name: name,
@@ -1872,7 +1925,7 @@ function setupModuleLoader(window) {
* })
* ```
*
- * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
* {@link ngAnimate ngAnimate module} for more information.
*/
animation: invokeLater('$animateProvider', 'register'),
@@ -1922,7 +1975,7 @@ function setupModuleLoader(window) {
* @description
* Use this method to register work which needs to be performed on module loading.
* For more about how to configure services, see
- * {@link providers#providers_provider-recipe Provider Recipe}.
+ * {@link providers#provider-recipe Provider Recipe}.
*/
config: config,
@@ -2045,6 +2098,8 @@ function setupModuleLoader(window) {
$SceDelegateProvider,
$SnifferProvider,
$TemplateCacheProvider,
+ $TemplateRequestProvider,
+ $$TestabilityProvider,
$TimeoutProvider,
$$RAFProvider,
$$AsyncCallbackProvider,
@@ -2067,11 +2122,11 @@ function setupModuleLoader(window) {
* - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.3.0-beta.17', // all of these placeholder strings will be replaced by grunt's
+ full: '1.3.0', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 3,
dot: 0,
- codeName: 'turing-autocompletion'
+ codeName: 'superluminal-nudge'
};
@@ -2084,11 +2139,11 @@ function publishExternalAPI(angular){
'element': jqLite,
'forEach': forEach,
'injector': createInjector,
- 'noop':noop,
- 'bind':bind,
+ 'noop': noop,
+ 'bind': bind,
'toJson': toJson,
'fromJson': fromJson,
- 'identity':identity,
+ 'identity': identity,
'isUndefined': isUndefined,
'isDefined': isDefined,
'isString': isString,
@@ -2102,8 +2157,10 @@ function publishExternalAPI(angular){
'lowercase': lowercase,
'uppercase': uppercase,
'callbacks': {counter: 0},
+ 'getTestability': getTestability,
'$$minErr': minErr,
- '$$csp': csp
+ '$$csp': csp,
+ 'reloadWithDebugInfo': reloadWithDebugInfo
});
angularModule = setupModuleLoader(window);
@@ -2194,6 +2251,8 @@ function publishExternalAPI(angular){
$sceDelegate: $SceDelegateProvider,
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
+ $templateRequest: $TemplateRequestProvider,
+ $$testability: $$TestabilityProvider,
$timeout: $TimeoutProvider,
$window: $WindowProvider,
$$rAF: $$RAFProvider,
@@ -2242,13 +2301,14 @@ function publishExternalAPI(angular){
* - [`addClass()`](http://api.jquery.com/addClass/)
* - [`after()`](http://api.jquery.com/after/)
* - [`append()`](http://api.jquery.com/append/)
- * - [`attr()`](http://api.jquery.com/attr/)
+ * - [`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/)
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
* - [`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
@@ -2304,17 +2364,17 @@ 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); });
+ 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 !!!
*/
-var jqData = JQLite._data = function(node) {
+JQLite._data = function(node) {
//jQuery always returns an object on cache miss
return this.cache[node[this.expando]] || {};
};
@@ -2324,6 +2384,7 @@ 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');
/**
@@ -2358,6 +2419,7 @@ 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);
}
@@ -2365,11 +2427,12 @@ function jqLiteIsTextNode(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)
- return !node.nodeType || node.nodeType === 1 || node.nodeType === 9;
+ var nodeType = node.nodeType;
+ return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
}
function jqLiteBuildFragment(html, context) {
- var elem, tmp, tag, wrap,
+ var tmp, tag, wrap,
fragment = context.createDocumentFragment(),
nodes = [], i;
@@ -2425,17 +2488,21 @@ function JQLite(element) {
if (element instanceof JQLite) {
return element;
}
+
+ var argIsString;
+
if (isString(element)) {
element = trim(element);
+ argIsString = true;
}
if (!(this instanceof JQLite)) {
- if (isString(element) && element.charAt(0) != '<') {
+ 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 (isString(element)) {
+ if (argIsString) {
jqLiteAddNodes(this, jqLiteParseHTML(element));
} else {
jqLiteAddNodes(this, element);
@@ -2449,10 +2516,8 @@ function jqLiteClone(element) {
function jqLiteDealoc(element, onlyDescendants){
if (!onlyDescendants) jqLiteRemoveData(element);
- if (element.childNodes && element.childNodes.length) {
- // we use querySelectorAll because documentFragments don't have getElementsByTagName
- var descendants = element.getElementsByTagName ? element.getElementsByTagName('*') :
- element.querySelectorAll ? element.querySelectorAll('*') : [];
+ if (element.querySelectorAll) {
+ var descendants = element.querySelectorAll('*');
for (var i = 0, l = descendants.length; i < l; i++) {
jqLiteRemoveData(descendants[i]);
}
@@ -2462,40 +2527,49 @@ function jqLiteDealoc(element, onlyDesce
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');
+ var expandoStore = jqLiteExpandoStore(element);
+ var events = expandoStore && expandoStore.events;
+ var handle = expandoStore && expandoStore.handle;
if (!handle) return; //no listeners registered
- if (isUndefined(type)) {
- forEach(events, function(eventHandler, type) {
- removeEventListenerFn(element, type, eventHandler);
+ if (!type) {
+ for (type in events) {
+ if (type !== '$destroy') {
+ removeEventListenerFn(element, type, handle);
+ }
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);
+ 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,
- expandoStore = jqCache[expandoId];
+ var expandoId = element.ng339;
+ var expandoStore = expandoId && jqCache[expandoId];
if (expandoStore) {
if (name) {
- delete jqCache[expandoId].data[name];
+ delete expandoStore.data[name];
return;
}
if (expandoStore.handle) {
- expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
+ if (expandoStore.events.$destroy) {
+ expandoStore.handle({}, '$destroy');
+ }
jqLiteOff(element);
}
delete jqCache[expandoId];
@@ -2503,44 +2577,41 @@ function jqLiteRemoveData(element, name)
}
}
-function jqLiteExpandoStore(element, key, value) {
+
+function jqLiteExpandoStore(element, createIfNecessary) {
var expandoId = element.ng339,
- expandoStore = jqCache[expandoId || -1];
+ expandoStore = expandoId && jqCache[expandoId];
- if (isDefined(value)) {
- if (!expandoStore) {
- element.ng339 = expandoId = jqNextId();
- expandoStore = jqCache[expandoId] = {};
- }
- expandoStore[key] = value;
- } else {
- return expandoStore && expandoStore[key];
+ 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 data = jqLiteExpandoStore(element, 'data'),
- isSetter = isDefined(value),
- keyDefined = !isSetter && isDefined(key),
- isSimpleGetter = keyDefined && !isObject(key);
- if (!data && !isSimpleGetter) {
- jqLiteExpandoStore(element, 'data', data = {});
- }
+ var isSimpleSetter = isDefined(value);
+ var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
+ var massGetter = !key;
+ var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
+ var data = expandoStore && expandoStore.data;
- if (isSetter) {
+ if (isSimpleSetter) { // data('key', value)
data[key] = value;
} else {
- if (keyDefined) {
- if (isSimpleGetter) {
- // don't create data in this case.
+ 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 {
+ } else { // mass-setter: data({key1: val1, key2: val2})
extend(data, key);
}
- } else {
- return data;
}
}
}
@@ -2595,11 +2666,9 @@ function jqLiteAddNodes(root, elements)
// 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);
+ for (var i = 0; i < length; i++) {
+ root[root.length++] = elements[i];
}
- push.apply(root, elements);
}
} else {
root[root.length++] = elements;
@@ -2616,7 +2685,7 @@ function jqLiteController(element, name)
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 == 9) {
+ if(element.nodeType == NODE_TYPE_DOCUMENT) {
element = element.documentElement;
}
var names = isArray(name) ? name : [name];
@@ -2629,7 +2698,7 @@ function jqLiteInheritedData(element, na
// 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 === 11 && element.host);
+ element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
}
}
@@ -2640,6 +2709,26 @@ function jqLiteEmpty(element) {
}
}
+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.
//////////////////////////////////////////
@@ -2653,7 +2742,7 @@ var JQLitePrototype = JQLite.prototype =
fn();
}
- // check if document already is loaded
+ // check if document is already loaded
if (document.readyState === 'complete'){
setTimeout(trigger);
} else {
@@ -2662,6 +2751,7 @@ var JQLitePrototype = JQLite.prototype =
// jshint -W064
JQLite(window).on('load', trigger); // fallback to window.onload for others
// jshint +W064
+ this.on('DOMContentLoaded', trigger);
}
},
toString: function() {
@@ -2696,6 +2786,8 @@ forEach('input,select,option,textarea,bu
var ALIASED_ATTR = {
'ngMinlength' : 'minlength',
'ngMaxlength' : 'maxlength',
+ 'ngMin' : 'min',
+ 'ngMax' : 'max',
'ngPattern' : 'pattern'
};
@@ -2739,7 +2831,7 @@ forEach({
return jqLiteInheritedData(element, '$injector');
},
- removeAttr: function(element,name) {
+ removeAttr: function(element, name) {
element.removeAttribute(name);
},
@@ -2751,22 +2843,7 @@ forEach({
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;
+ return element.style[name];
}
},
@@ -2813,7 +2890,7 @@ forEach({
function getText(element, value) {
if (isUndefined(value)) {
var nodeType = element.nodeType;
- return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
+ return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
}
element.textContent = value;
}
@@ -2897,56 +2974,49 @@ forEach({
function createEventHandler(element, events) {
var eventHandler = function (event, type) {
- if (!event.preventDefault) {
- event.preventDefault = function() {
- event.returnValue = false; //ie
- };
- }
+ // jQuery specific api
+ event.isDefaultPrevented = function() {
+ return event.defaultPrevented;
+ };
- if (!event.stopPropagation) {
- event.stopPropagation = function() {
- event.cancelBubble = true; //ie
- };
- }
+ var eventFns = events[type || event.type];
+ var eventFnsLength = eventFns ? eventFns.length : 0;
- if (!event.target) {
- event.target = event.srcElement || document;
- }
+ if (!eventFnsLength) return;
+
+ if (isUndefined(event.immediatePropagationStopped)) {
+ var originalStopImmediatePropagation = event.stopImmediatePropagation;
+ event.stopImmediatePropagation = function() {
+ event.immediatePropagationStopped = true;
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ }
- if (isUndefined(event.defaultPrevented)) {
- var prevent = event.preventDefault;
- event.preventDefault = function() {
- event.defaultPrevented = true;
- prevent.call(event);
+ if (originalStopImmediatePropagation) {
+ originalStopImmediatePropagation.call(event);
+ }
};
- event.defaultPrevented = false;
}
- event.isDefaultPrevented = function() {
- return event.defaultPrevented || event.returnValue === false;
+ event.isImmediatePropagationStopped = function() {
+ return event.immediatePropagationStopped === true;
};
// 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);
- });
+ if ((eventFnsLength > 1)) {
+ eventFns = shallowCopy(eventFns);
+ }
- // 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;
+ 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;
}
@@ -2959,7 +3029,7 @@ function createEventHandler(element, eve
forEach({
removeData: jqLiteRemoveData,
- on: function onFn(element, type, fn, unsupported){
+ 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.
@@ -2967,63 +3037,48 @@ forEach({
return;
}
- var events = jqLiteExpandoStore(element, 'events'),
- handle = jqLiteExpandoStore(element, 'handle');
+ var expandoStore = jqLiteExpandoStore(element, true);
+ var events = expandoStore.events;
+ var handle = expandoStore.handle;
+
+ if (!handle) {
+ handle = expandoStore.handle = createEventHandler(element, events);
+ }
- if (!events) jqLiteExpandoStore(element, 'events', events = {});
- if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
+ // http://jsperf.com/string-indexof-vs-split
+ var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
+ var i = types.length;
- forEach(type.split(' '), function(type){
+ while (i--) {
+ type = types[i];
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] = [];
+ 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
- var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};
- onFn(element, eventmap[type], function(event) {
+ 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 && !contains(target, related)) ){
+ if ( !related || (related !== target && !target.contains(related)) ){
handle(event, type);
}
});
} else {
- addEventListenerFn(element, type, handle);
- events[type] = [];
+ if (type !== '$destroy') {
+ addEventListenerFn(element, type, handle);
+ }
}
eventFns = events[type];
}
eventFns.push(fn);
- });
+ }
},
off: jqLiteOff,
@@ -3057,7 +3112,7 @@ forEach({
children: function(element) {
var children = [];
forEach(element.childNodes, function(element){
- if (element.nodeType === 1)
+ if (element.nodeType === NODE_TYPE_ELEMENT)
children.push(element);
});
return children;
@@ -3068,15 +3123,19 @@ forEach({
},
append: function(element, node) {
- forEach(new JQLite(node), function(child){
- if (element.nodeType === 1 || element.nodeType === 11) {
- element.appendChild(child);
- }
- });
+ 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 === 1) {
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
var index = element.firstChild;
forEach(new JQLite(node), function(child){
element.insertBefore(child, index);
@@ -3085,7 +3144,7 @@ forEach({
},
wrap: function(element, wrapNode) {
- wrapNode = jqLite(wrapNode)[0];
+ wrapNode = jqLite(wrapNode).eq(0).clone()[0];
var parent = element.parentNode;
if (parent) {
parent.replaceChild(wrapNode, element);
@@ -3093,18 +3152,21 @@ forEach({
wrapNode.appendChild(element);
},
- remove: function(element) {
- jqLiteDealoc(element);
- var parent = element.parentNode;
- if (parent) parent.removeChild(element);
+ remove: jqLiteRemove,
+
+ detach: function(element) {
+ jqLiteRemove(element, true);
},
after: function(element, newElement) {
var index = element, parent = element.parentNode;
- forEach(new JQLite(newElement), function(node){
+ 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,
@@ -3124,20 +3186,11 @@ forEach({
parent: function(element) {
var parent = element.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
+ return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? 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;
+ return element.nextElementSibling;
},
find: function(element, selector) {
@@ -3150,26 +3203,41 @@ forEach({
clone: jqLiteClone,
- triggerHandler: function(element, eventName, eventData) {
- // Copy event handlers in case event handlers array is modified during execution.
- var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName],
- eventFnsCopy = shallowCopy(eventFns || []);
+ triggerHandler: function(element, event, extraParameters) {
- eventData = eventData || [];
+ 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
+ };
- var event = [{
- preventDefault: function() {
- this.defaultPrevented = true;
- },
- isDefaultPrevented: function() {
- return this.defaultPrevented === true;
- },
- stopPropagation: noop
- }];
+ // If a custom event was provided then extend our dummy event with it
+ if (event.type) {
+ dummyEvent = extend(dummyEvent, event);
+ }
- forEach(eventFnsCopy, function(fn) {
- fn.apply(element, event.concat(eventData));
- });
+ // 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){
/**
@@ -3177,7 +3245,8 @@ forEach({
*/
JQLite.prototype[name] = function(arg1, arg2, arg3) {
var value;
- for(var i=0; i < this.length; i++) {
+
+ for(var i = 0, ii = this.length; i < ii; i++) {
if (isUndefined(value)) {
value = fn(this[i], arg1, arg2, arg3);
if (isDefined(value)) {
@@ -3209,21 +3278,23 @@ forEach({
* The resulting string key is in 'type:hashKey' format.
*/
function hashKey(obj, nextUidFn) {
- var objType = typeof obj,
- key;
+ var key = obj && obj.$$hashKey;
- if (objType == 'function' || (objType == 'object' && obj !== null)) {
- if (typeof (key = obj.$$hashKey) == 'function') {
- // must invoke on object to keep the right this
+ if (key) {
+ if (typeof key === 'function') {
key = obj.$$hashKey();
- } else if (key === undefined) {
- key = obj.$$hashKey = (nextUidFn || nextUid)();
}
+ return key;
+ }
+
+ var objType = typeof obj;
+ if (objType == 'function' || (objType == 'object' && obj !== null)) {
+ key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
} else {
- key = obj;
+ key = objType + ':' + obj;
}
- return objType + ':' + key;
+ return key;
}
/**
@@ -3274,13 +3345,13 @@ HashMap.prototype = {
* @kind function
*
* @description
- * Creates an injector function that can be used for retrieving services as well as for
+ * Creates an injector object that can be used for retrieving services as well as for
* dependency injection (see {@link guide/di dependency injection}).
*
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
* {@link angular.module}. The `ng` module must be explicitly added.
- * @returns {function()} Injector function. See {@link auto.$injector $injector}.
+ * @returns {injector} Injector object. See {@link auto.$injector $injector}.
*
* @example
* Typical usage
@@ -3387,7 +3458,6 @@ function annotate(fn, strictDi, name) {
/**
* @ngdoc service
* @name $injector
- * @kind function
*
* @description
*
@@ -3402,7 +3472,7 @@ function annotate(fn, strictDi, name) {
* expect($injector.get('$injector')).toBe($injector);
* expect($injector.invoke(function($injector) {
* return $injector;
- * }).toBe($injector);
+ * })).toBe($injector);
* ```
*
* # Injection Function Annotation
@@ -3469,8 +3539,8 @@ function annotate(fn, strictDi, name) {
* @description
* Allows the user to query if the particular service exists.
*
- * @param {string} Name of the service to query.
- * @returns {boolean} returns true if injector has given service.
+ * @param {string} name Name of the service to query.
+ * @returns {boolean} `true` if injector has given service.
*/
/**
@@ -3892,13 +3962,13 @@ function createInjector(modulesToLoad, s
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() {
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
- }, strictDi)),
+ })),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
- }, strictDi));
+ }));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
@@ -3930,7 +4000,21 @@ function createInjector(modulesToLoad, s
return providerCache[name + providerSuffix] = provider_;
}
- function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
+ function enforceReturnValue(name, factory) {
+ return function enforcedReturnValue() {
+ var result = instanceInjector.invoke(factory, this, undefined, name);
+ if (isUndefined(result)) {
+ throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
+ }
+ return result;
+ };
+ }
+
+ function factory(name, factoryFn, enforce) {
+ return provider(name, {
+ $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
+ });
+ }
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
@@ -3938,7 +4022,7 @@ function createInjector(modulesToLoad, s
}]);
}
- function value(name, val) { return factory(name, valueFn(val)); }
+ function value(name, val) { return factory(name, valueFn(val), false); }
function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
@@ -3960,7 +4044,7 @@ function createInjector(modulesToLoad, s
// Module Loading
////////////////////////////////////
function loadModules(modulesToLoad){
- var runBlocks = [], moduleFn, invokeQueue;
+ var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
@@ -4096,101 +4180,265 @@ function createInjector(modulesToLoad, s
createInjector.$$annotate = annotate;
/**
- * @ngdoc service
- * @name $anchorScroll
- * @kind function
- * @requires $window
- * @requires $location
- * @requires $rootScope
+ * @ngdoc provider
+ * @name $anchorScrollProvider
*
* @description
- * When called, it checks current value of `$location.hash()` and scrolls to the related element,
- * according to rules specified in
- * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
- *
- * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
- * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
- *
- * @example
- <example module="anchorScrollExample">
- <file name="index.html">
- <div id="scrollArea" ng-controller="ScrollController">
- <a ng-click="gotoBottom()">Go to bottom</a>
- <a id="bottom"></a> You're at the bottom!
- </div>
- </file>
- <file name="script.js">
- angular.module('anchorScrollExample', [])
- .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
- function ($scope, $location, $anchorScroll) {
- $scope.gotoBottom = function() {
- // set the location.hash to the id of
- // the element you wish to scroll to.
- $location.hash('bottom');
-
- // call $anchorScroll()
- $anchorScroll();
- };
- }]);
- </file>
- <file name="style.css">
- #scrollArea {
- height: 350px;
- overflow: auto;
- }
-
- #bottom {
- display: block;
- margin-top: 2000px;
- }
- </file>
- </example>
+ * Use `$anchorScrollProvider` to disable automatic scrolling whenever
+ * {@link ng.$location#hash $location.hash()} changes.
*/
function $AnchorScrollProvider() {
var autoScrollingEnabled = true;
+ /**
+ * @ngdoc method
+ * @name $anchorScrollProvider#disableAutoScrolling
+ *
+ * @description
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
+ * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
+ * Use this method to disable automatic scrolling.
+ *
+ * If automatic scrolling is disabled, one must explicitly call
+ * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
+ * current hash.
+ */
this.disableAutoScrolling = function() {
autoScrollingEnabled = false;
};
+ /**
+ * @ngdoc service
+ * @name $anchorScroll
+ * @kind function
+ * @requires $window
+ * @requires $location
+ * @requires $rootScope
+ *
+ * @description
+ * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and
+ * scrolls to the related element, according to the rules specified in the
+ * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
+ *
+ * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
+ * match any anchor whenever it changes. This can be disabled by calling
+ * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
+ *
+ * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
+ * vertical scroll-offset (either fixed or dynamic).
+ *
+ * @property {(number|function|jqLite)} yOffset
+ * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
+ * positioned elements at the top of the page, such as navbars, headers etc.
+ *
+ * `yOffset` can be specified in various ways:
+ * - **number**: A fixed number of pixels to be used as offset.<br /><br />
+ * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
+ * a number representing the offset (in pixels).<br /><br />
+ * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
+ * the top of the page to the element's bottom will be used as offset.<br />
+ * **Note**: The element will be taken into account only as long as its `position` is set to
+ * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
+ * their height and/or positioning according to the viewport's size.
+ *
+ * <br />
+ * <div class="alert alert-warning">
+ * In order for `yOffset` to work properly, scrolling should take place on the document's root and
+ * not some child element.
+ * </div>
+ *
+ * @example
+ <example module="anchorScrollExample">
+ <file name="index.html">
+ <div id="scrollArea" ng-controller="ScrollController">
+ <a ng-click="gotoBottom()">Go to bottom</a>
+ <a id="bottom"></a> You're at the bottom!
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('anchorScrollExample', [])
+ .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
+ function ($scope, $location, $anchorScroll) {
+ $scope.gotoBottom = function() {
+ // set the location.hash to the id of
+ // the element you wish to scroll to.
+ $location.hash('bottom');
+
+ // call $anchorScroll()
+ $anchorScroll();
+ };
+ }]);
+ </file>
+ <file name="style.css">
+ #scrollArea {
+ height: 280px;
+ overflow: auto;
+ }
+
+ #bottom {
+ display: block;
+ margin-top: 2000px;
+ }
+ </file>
+ </example>
+ *
+ * <hr />
+ * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
+ * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
+ *
+ * @example
+ <example module="anchorScrollOffsetExample">
+ <file name="index.html">
+ <div class="fixed-header" ng-controller="headerCtrl">
+ <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
+ Go to anchor {{x}}
+ </a>
+ </div>
+ <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
+ Anchor {{x}} of 5
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('anchorScrollOffsetExample', [])
+ .run(['$anchorScroll', function($anchorScroll) {
+ $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
+ }])
+ .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
+ function ($anchorScroll, $location, $scope) {
+ $scope.gotoAnchor = function(x) {
+ var newHash = 'anchor' + x;
+ if ($location.hash() !== newHash) {
+ // set the $location.hash to `newHash` and
+ // $anchorScroll will automatically scroll to it
+ $location.hash('anchor' + x);
+ } else {
+ // call $anchorScroll() explicitly,
+ // since $location.hash hasn't changed
+ $anchorScroll();
+ }
+ };
+ }
+ ]);
+ </file>
+ <file name="style.css">
+ body {
+ padding-top: 50px;
+ }
+
+ .anchor {
+ border: 2px dashed DarkOrchid;
+ padding: 10px 10px 200px 10px;
+ }
+
+ .fixed-header {
+ background-color: rgba(0, 0, 0, 0.2);
+ height: 50px;
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ }
+
+ .fixed-header > a {
+ display: inline-block;
+ margin: 5px 15px;
+ }
+ </file>
+ </example>
+ */
this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
var document = $window.document;
+ var scrollScheduled = false;
- // helper function to get first anchor from a NodeList
- // can't use filter.filter, as it accepts only instances of Array
- // and IE can't convert NodeList to an array using [].slice
- // TODO(vojta): use filter if we change it to accept lists as well
+ // Helper function to get first anchor from a NodeList
+ // (using `Array#some()` instead of `angular#forEach()` since it's more performant
+ // and working in all supported browsers.)
function getFirstAnchor(list) {
var result = null;
- forEach(list, function(element) {
- if (!result && nodeName_(element) === 'a') result = element;
+ Array.prototype.some.call(list, function(element) {
+ if (nodeName_(element) === 'a') {
+ result = element;
+ return true;
+ }
});
return result;
}
+ function getYOffset() {
+
+ var offset = scroll.yOffset;
+
+ if (isFunction(offset)) {
+ offset = offset();
+ } else if (isElement(offset)) {
+ var elem = offset[0];
+ var style = $window.getComputedStyle(elem);
+ if (style.position !== 'fixed') {
+ offset = 0;
+ } else {
+ offset = elem.getBoundingClientRect().bottom;
+ }
+ } else if (!isNumber(offset)) {
+ offset = 0;
+ }
+
+ return offset;
+ }
+
+ function scrollTo(elem) {
+ if (elem) {
+ elem.scrollIntoView();
+
+ var offset = getYOffset();
+
+ if (offset) {
+ // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
+ // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
+ // top of the viewport.
+ //
+ // IF the number of pixels from the top of `elem` to the end of the page's content is less
+ // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
+ // way down the page.
+ //
+ // This is often the case for elements near the bottom of the page.
+ //
+ // In such cases we do not need to scroll the whole `offset` up, just the difference between
+ // the top of the element and the offset, which is enough to align the top of `elem` at the
+ // desired position.
+ var elemTop = elem.getBoundingClientRect().top;
+ $window.scrollBy(0, elemTop - offset);
+ }
+ } else {
+ $window.scrollTo(0, 0);
+ }
+ }
+
function scroll() {
var hash = $location.hash(), elm;
// empty hash, scroll to the top of the page
- if (!hash) $window.scrollTo(0, 0);
+ if (!hash) scrollTo(null);
// element with given id
- else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
+ else if ((elm = document.getElementById(hash))) scrollTo(elm);
// first anchor with given name :-D
- else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
// no element and hash == 'top', scroll to the top of the page
- else if (hash === 'top') $window.scrollTo(0, 0);
+ else if (hash === 'top') scrollTo(null);
}
// does not scroll when user clicks on anchor link that is currently on
// (no url change, no $location.hash() change), browser native does scroll
if (autoScrollingEnabled) {
$rootScope.$watch(function autoScrollWatch() {return $location.hash();},
- function autoScrollWatchAction() {
- $rootScope.$evalAsync(scroll);
+ function autoScrollWatchAction(newVal, oldVal) {
+ // skip the initial scroll if $location.hash is empty
+ if (newVal === oldVal && newVal === '') return;
+
+ jqLiteDocumentLoaded(function() {
+ $rootScope.$evalAsync(scroll);
+ });
});
}
@@ -4279,10 +4527,75 @@ var $AnimateProvider = ['$provide', func
return this.$$classNameFilter;
};
- this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) {
+ this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
+
+ var currentDefer;
+
+ function runAnimationPostDigest(fn) {
+ var cancelFn, defer = $$q.defer();
+ defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
+ cancelFn && cancelFn();
+ };
+
+ $rootScope.$$postDigest(function ngAnimatePostDigest() {
+ cancelFn = fn(function ngAnimateNotifyComplete() {
+ defer.resolve();
+ });
+ });
+
+ return defer.promise;
+ }
+
+ function resolveElementClasses(element, classes) {
+ var toAdd = [], toRemove = [];
+
+ var hasClasses = createMap();
+ forEach((element.attr('class') || '').split(/\s+/), function(className) {
+ hasClasses[className] = true;
+ });
+
+ forEach(classes, function(status, className) {
+ var hasClass = hasClasses[className];
+
+ // If the most recent class manipulation (via $animate) was to remove the class, and the
+ // element currently has the class, the class is scheduled for removal. Otherwise, if
+ // the most recent class manipulation (via $animate) was to add the class, and the
+ // element does not currently have the class, the class is scheduled to be added.
+ if (status === false && hasClass) {
+ toRemove.push(className);
+ } else if (status === true && !hasClass) {
+ toAdd.push(className);
+ }
+ });
+
+ return (toAdd.length + toRemove.length) > 0 &&
+ [toAdd.length ? toAdd : null, toRemove.length ? toRemove : null];
+ }
+
+ function cachedClassManipulation(cache, classes, op) {
+ for (var i=0, ii = classes.length; i < ii; ++i) {
+ var className = classes[i];
+ cache[className] = op;
+ }
+ }
+
+ function asyncPromise() {
+ // only serve one instance of a promise in order to save CPU cycles
+ if (!currentDefer) {
+ currentDefer = $$q.defer();
+ $$asyncCallback(function() {
+ currentDefer.resolve();
+ currentDefer = null;
+ });
+ }
+ return currentDefer.promise;
+ }
- function async(fn) {
- fn && $$asyncCallback(fn);
+ function applyStyles(element, options) {
+ if (angular.isObject(options)) {
+ var styles = extend(options.from || {}, options.to || {});
+ element.css(styles);
+ }
}
/**
@@ -4303,6 +4616,10 @@ var $AnimateProvider = ['$provide', func
* page}.
*/
return {
+ animate : function(element, from, to) {
+ applyStyles(element, { from: from, to: to });
+ return asyncPromise();
+ },
/**
*
@@ -4310,22 +4627,21 @@ var $AnimateProvider = ['$provide', func
* @name $animate#enter
* @kind function
* @description Inserts the element into the DOM either after the `after` element or
- * as the first child within the `parent` element. Once complete, the done() callback
- * will be fired (if provided).
+ * as the first child within the `parent` element. When the function is called a promise
+ * is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be inserted into the DOM
* @param {DOMElement} parent the parent element which will append the element as
* a child (if the after element is not present)
* @param {DOMElement} after the sibling element which will append the element
* after itself
- * @param {Function=} done callback function that will be called after the element has been
- * inserted into the DOM
+ * @param {object=} options an optional collection of styles that will be applied to the element.
+ * @return {Promise} the animation callback promise
*/
- enter : function(element, parent, after, done) {
- after
- ? after.after(element)
- : parent.prepend(element);
- async(done);
- return noop;
+ enter : function(element, parent, after, options) {
+ applyStyles(element, options);
+ after ? after.after(element)
+ : parent.prepend(element);
+ return asyncPromise();
},
/**
@@ -4333,16 +4649,15 @@ var $AnimateProvider = ['$provide', func
* @ngdoc method
* @name $animate#leave
* @kind function
- * @description Removes the element from the DOM. Once complete, the done() callback will be
- * fired (if provided).
+ * @description Removes the element from the DOM. When the function is called a promise
+ * is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be removed from the DOM
- * @param {Function=} done callback function that will be called after the element has been
- * removed from the DOM
+ * @param {object=} options an optional collection of options that will be applied to the element.
+ * @return {Promise} the animation callback promise
*/
- leave : function(element, done) {
+ leave : function(element, options) {
element.remove();
- async(done);
- return noop;
+ return asyncPromise();
},
/**
@@ -4351,8 +4666,8 @@ var $AnimateProvider = ['$provide', func
* @name $animate#move
* @kind function
* @description Moves the position of the provided element within the DOM to be placed
- * either after the `after` element or inside of the `parent` element. Once complete, the
- * done() callback will be fired (if provided).
+ * either after the `after` element or inside of the `parent` element. When the function
+ * is called a promise is returned that will be resolved at a later time.
*
* @param {DOMElement} element the element which will be moved around within the
* DOM
@@ -4360,13 +4675,13 @@ var $AnimateProvider = ['$provide', func
* inserted into (if the after element is not present)
* @param {DOMElement} after the sibling element where the element will be
* positioned next to
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * element has been moved to its new position
+ * @param {object=} options an optional collection of options that will be applied to the element.
+ * @return {Promise} the animation callback promise
*/
- move : function(element, parent, after, done) {
+ move : function(element, parent, after, options) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
- return this.enter(element, parent, after, done);
+ return this.enter(element, parent, after, options);
},
/**
@@ -4374,23 +4689,28 @@ var $AnimateProvider = ['$provide', func
* @ngdoc method
* @name $animate#addClass
* @kind function
- * @description Adds the provided className CSS class value to the provided element. Once
- * complete, the done() callback will be fired (if provided).
+ * @description Adds the provided className CSS class value to the provided element.
+ * When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* added to it
* @param {string} className the CSS class which will be added to the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * className value has been added to the element
+ * @param {object=} options an optional collection of options that will be applied to the element.
+ * @return {Promise} the animation callback promise
*/
- addClass : function(element, className, done) {
+ addClass : function(element, className, options) {
+ return this.setClass(element, className, [], options);
+ },
+
+ $$addClassImmediately : function(element, className, options) {
+ element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function (element) {
jqLiteAddClass(element, className);
});
- async(done);
- return noop;
+ applyStyles(element, options);
+ return asyncPromise();
},
/**
@@ -4399,22 +4719,27 @@ var $AnimateProvider = ['$provide', func
* @name $animate#removeClass
* @kind function
* @description Removes the provided className CSS class value from the provided element.
- * Once complete, the done() callback will be fired (if provided).
+ * When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* removed from it
* @param {string} className the CSS class which will be removed from the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * className value has been removed from the element
+ * @param {object=} options an optional collection of options that will be applied to the element.
+ * @return {Promise} the animation callback promise
*/
- removeClass : function(element, className, done) {
- className = isString(className) ?
- className :
- isArray(className) ? className.join(' ') : '';
+ removeClass : function(element, className, options) {
+ return this.setClass(element, [], className, options);
+ },
+
+ $$removeClassImmediately : function(element, className, options) {
+ element = jqLite(element);
+ className = !isString(className)
+ ? (isArray(className) ? className.join(' ') : '')
+ : className;
forEach(element, function (element) {
jqLiteRemoveClass(element, className);
});
- async(done);
- return noop;
+ applyStyles(element, options);
+ return asyncPromise();
},
/**
@@ -4423,24 +4748,70 @@ var $AnimateProvider = ['$provide', func
* @name $animate#setClass
* @kind function
* @description Adds and/or removes the given CSS classes to and from the element.
- * Once complete, the done() callback will be fired (if provided).
+ * When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
- * @param {Function=} done the callback function (if provided) that will be fired after the
- * CSS classes have been set on the element
+ * @param {object=} options an optional collection of options that will be applied to the element.
+ * @return {Promise} the animation callback promise
*/
- setClass : function(element, add, remove, done) {
- forEach(element, function (element) {
- jqLiteAddClass(element, add);
- jqLiteRemoveClass(element, remove);
- });
- async(done);
- return noop;
+ setClass : function(element, add, remove, options) {
+ var self = this;
+ var STORAGE_KEY = '$$animateClasses';
+ var createdCache = false;
+ element = jqLite(element);
+
+ var cache = element.data(STORAGE_KEY);
+ if (!cache) {
+ cache = {
+ classes: {},
+ options : options
+ };
+ createdCache = true;
+ } else if (options && cache.options) {
+ cache.options = angular.extend(cache.options || {}, options);
+ }
+
+ var classes = cache.classes;
+
+ add = isArray(add) ? add : add.split(' ');
+ remove = isArray(remove) ? remove : remove.split(' ');
+ cachedClassManipulation(classes, add, true);
+ cachedClassManipulation(classes, remove, false);
+
+ if (createdCache) {
+ cache.promise = runAnimationPostDigest(function(done) {
+ var cache = element.data(STORAGE_KEY);
+ element.removeData(STORAGE_KEY);
+
+ // in the event that the element is removed before postDigest
+ // is run then the cache will be undefined and there will be
+ // no need anymore to add or remove and of the element classes
+ if (cache) {
+ var classes = resolveElementClasses(element, cache.classes);
+ if (classes) {
+ self.$$setClassImmediately(element, classes[0], classes[1], cache.options);
+ }
+ }
+
+ done();
+ });
+ element.data(STORAGE_KEY, cache);
+ }
+
+ return cache.promise;
+ },
+
+ $$setClassImmediately : function(element, add, remove, options) {
+ add && this.$$addClassImmediately(element, add);
+ remove && this.$$removeClassImmediately(element, remove);
+ applyStyles(element, options);
+ return asyncPromise();
},
- enabled : noop
+ enabled : noop,
+ cancel : noop
};
}];
}];
@@ -4455,6 +4826,8 @@ function $$AsyncCallbackProvider(){
}];
}
+/* global stripHash: true */
+
/**
* ! This is a private undocumented service !
*
@@ -4577,9 +4950,13 @@ function Browser(window, document, $log,
// URL API
//////////////////////////////////////////////////////////////
- var lastBrowserUrl = location.href,
+ var cachedState, lastHistoryState,
+ lastBrowserUrl = location.href,
baseElement = document.find('base'),
- newLocation = null;
+ reloadLocation = null;
+
+ cacheState();
+ lastHistoryState = cachedState;
/**
* @name $browser#url
@@ -4598,26 +4975,47 @@ function Browser(window, document, $log,
* {@link ng.$location $location service} to change url.
*
* @param {string} url New url (when used as setter)
- * @param {boolean=} replace Should new url replace current history record ?
+ * @param {boolean=} replace Should new url replace current history record?
+ * @param {object=} state object to use with pushState/replaceState
*/
- self.url = function(url, replace) {
+ self.url = function(url, replace, state) {
+ // In modern browsers `history.state` is `null` by default; treating it separately
+ // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
+ // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
+ if (isUndefined(state)) {
+ state = null;
+ }
+
// Android Browser BFCache causes location, history reference to become stale.
if (location !== window.location) location = window.location;
if (history !== window.history) history = window.history;
// setter
if (url) {
- if (lastBrowserUrl == url) return;
+ var sameState = lastHistoryState === state;
+
+ // Don't change anything if previous and current URLs and states match. This also prevents
+ // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
+ // See https://github.com/angular/angular.js/commit/ffb2701
+ if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
+ return;
+ }
+ var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url;
- if ($sniffer.history) {
- if (replace) history.replaceState(null, '', url);
- else {
- history.pushState(null, '', url);
- // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
- baseElement.attr('href', baseElement.attr('href'));
- }
+ lastHistoryState = state;
+ // Don't use history API if only the hash changed
+ // due to a bug in IE10/IE11 which leads
+ // to not firing a `hashchange` nor `popstate` event
+ // in some cases (see #9143).
+ if ($sniffer.history && (!sameBase || !sameState)) {
+ history[replace ? 'replaceState' : 'pushState'](state, '', url);
+ cacheState();
+ // Do the assignment again so that those two variables are referentially identical.
+ lastHistoryState = cachedState;
} else {
- newLocation = url;
+ if (!sameBase) {
+ reloadLocation = url;
+ }
if (replace) {
location.replace(url);
} else {
@@ -4627,23 +5025,58 @@ function Browser(window, document, $log,
return self;
// getter
} else {
- // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
- // methods not updating location.href synchronously.
+ // - reloadLocation is needed as browsers don't allow to read out
+ // the new location.href if a reload happened.
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
- return newLocation || location.href.replace(/%27/g,"'");
+ return reloadLocation || location.href.replace(/%27/g,"'");
}
};
+ /**
+ * @name $browser#state
+ *
+ * @description
+ * This method is a getter.
+ *
+ * Return history.state or null if history.state is undefined.
+ *
+ * @returns {object} state
+ */
+ self.state = function() {
+ return cachedState;
+ };
+
var urlChangeListeners = [],
urlChangeInit = false;
+ function cacheStateAndFireUrlChange() {
+ cacheState();
+ fireUrlChange();
+ }
+
+ // This variable should be used *only* inside the cacheState function.
+ var lastCachedState = null;
+ function cacheState() {
+ // This should be the only place in $browser where `history.state` is read.
+ cachedState = window.history.state;
+ cachedState = isUndefined(cachedState) ? null : cachedState;
+
+ // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
+ if (equals(cachedState, lastCachedState)) {
+ cachedState = lastCachedState;
+ }
+ lastCachedState = cachedState;
+ }
+
function fireUrlChange() {
- newLocation = null;
- if (lastBrowserUrl == self.url()) return;
+ if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
+ return;
+ }
lastBrowserUrl = self.url();
+ lastHistoryState = cachedState;
forEach(urlChangeListeners, function(listener) {
- listener(self.url());
+ listener(self.url(), cachedState);
});
}
@@ -4676,11 +5109,9 @@ function Browser(window, document, $log,
// changed by push/replaceState
// html5 history api - popstate event
- if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
+ if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
// hashchange event
- if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
- // polling
- else self.addPollFn(fireUrlChange);
+ jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
urlChangeInit = true;
}
@@ -4689,6 +5120,13 @@ function Browser(window, document, $log,
return callback;
};
+ /**
+ * Checks whether the url has changed outside of Angular.
+ * Needs to be exported to be able to check for changes that have been done in sync,
+ * as hashchange/popstate events fire in async.
+ */
+ self.$$checkUrlChange = fireUrlChange;
+
//////////////////////////////////////////////////////////////
// Misc API
//////////////////////////////////////////////////////////////
@@ -4714,6 +5152,14 @@ function Browser(window, document, $log,
var lastCookieString = '';
var cookiePath = self.baseHref();
+ function safeDecodeURIComponent(str) {
+ try {
+ return decodeURIComponent(str);
+ } catch (e) {
+ return str;
+ }
+ }
+
/**
* @name $browser#cookies
*
@@ -4767,12 +5213,12 @@ function Browser(window, document, $log,
cookie = cookieArray[i];
index = cookie.indexOf('=');
if (index > 0) { //ignore nameless cookies
- name = decodeURIComponent(cookie.substring(0, index));
+ name = safeDecodeURIComponent(cookie.substring(0, index));
// the first value that is seen for a cookie is the most
// specific one. values for the same cookie name that
// follow are for less specific paths.
if (lastCookies[name] === undefined) {
- lastCookies[name] = decodeURIComponent(cookie.substring(index + 1));
+ lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
}
}
}
@@ -4904,8 +5350,10 @@ function $BrowserProvider(){
$scope.keys = [];
$scope.cache = $cacheFactory('cacheId');
$scope.put = function(key, value) {
- $scope.cache.put(key, value);
- $scope.keys.push(key);
+ if ($scope.cache.get(key) === undefined) {
+ $scope.keys.push(key);
+ }
+ $scope.cache.put(key, value === undefined ? null : value);
};
}]);
</file>
@@ -5295,6 +5743,7 @@ function $TemplateCacheProvider() {
* // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
* transclude: false,
* restrict: 'A',
+ * templateNamespace: 'html',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
* controllerAs: 'stringAlias',
@@ -5345,12 +5794,12 @@ function $TemplateCacheProvider() {
* The directive definition object provides instructions to the {@link ng.$compile
* compiler}. The attributes are:
*
- * ### `multiElement`
+ * #### `multiElement`
* When this property is set to true, the HTML compiler will collect DOM nodes between
* nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
* together as the directive elements. It is recomended that this feature be used on directives
- * which are not strictly behavioural (such as {@link api/ng.directive:ngClick ngClick}), and which
- * do not manipulate or replace child nodes (such as {@link api/ng.directive:ngInclude ngInclude}).
+ * which are not strictly behavioural (such as {@link ngClick}), and which
+ * do not manipulate or replace child nodes (such as {@link ngInclude}).
*
* #### `priority`
* When there are multiple directives defined on a single DOM element, sometimes it
@@ -5363,7 +5812,8 @@ function $TemplateCacheProvider() {
* #### `terminal`
* If set to true then the current `priority` will be the last set of directives
* which will execute (any directives at the current priority will still execute
- * as the order of execution on same `priority` is undefined).
+ * as the order of execution on same `priority` is undefined). Note that expressions
+ * and other directives used in the directive's template will also be excluded from execution.
*
* #### `scope`
* **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
@@ -5409,6 +5859,10 @@ function $TemplateCacheProvider() {
* by calling the `localFn` as `localFn({amount: 22})`.
*
*
+ * #### `bindToController`
+ * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will
+ * allow a component to have its properties bound to the controller, rather than to scope. When the controller
+ * is instantiated, the initial values of the isolate scope bindings are already available.
*
* #### `controller`
* Controller constructor function. The controller is instantiated before the
@@ -5419,9 +5873,18 @@ function $TemplateCacheProvider() {
* * `$scope` - Current scope associated with the element
* * `$element` - Current element
* * `$attrs` - Current attributes object for the element
[... 9030 lines stripped ...]