From 36edce38f6b17f73322fa38404d6e01818a44fd2 Mon Sep 17 00:00:00 2001
From: rsanchez <rsanchez@curisit.net>
Date: Thu, 11 Dec 2014 19:08:16 +0000
Subject: [PATCH] #2140 fix - Added renew period to pack, change on DB schema, fixed pack selection in listing, upgrade angular to 1.3.6 and other minor issues
---
securis/src/main/webapp/js/angular/angular.js | 2676 ++++++++++++++++++++++++++++++++++-------------------------
1 files changed, 1,546 insertions(+), 1,130 deletions(-)
diff --git a/securis/src/main/webapp/js/angular/angular.js b/securis/src/main/webapp/js/angular/angular.js
index b804d64..12423c1 100644
--- a/securis/src/main/webapp/js/angular/angular.js
+++ b/securis/src/main/webapp/js/angular/angular.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.0
+ * @license AngularJS v1.3.6
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -37,45 +37,28 @@
function minErr(module, ErrorConstructor) {
ErrorConstructor = ErrorConstructor || Error;
- return function () {
+ return function() {
var code = arguments[0],
prefix = '[' + (module ? module + ':' : '') + code + '] ',
template = arguments[1],
templateArgs = arguments,
- stringify = function (obj) {
- if (typeof obj === 'function') {
- return obj.toString().replace(/ \{[\s\S]*$/, '');
- } else if (typeof obj === 'undefined') {
- return 'undefined';
- } else if (typeof obj !== 'string') {
- return JSON.stringify(obj);
- }
- return obj;
- },
+
message, i;
- message = prefix + template.replace(/\{\d+\}/g, function (match) {
+ message = prefix + template.replace(/\{\d+\}/g, function(match) {
var index = +match.slice(1, -1), arg;
if (index + 2 < templateArgs.length) {
- arg = templateArgs[index + 2];
- if (typeof arg === 'function') {
- return arg.toString().replace(/ ?\{[\s\S]*$/, '');
- } else if (typeof arg === 'undefined') {
- return 'undefined';
- } else if (typeof arg !== 'string') {
- return toJson(arg);
- }
- return arg;
+ return toDebugString(templateArgs[index + 2]);
}
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.3.0/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.6/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
- message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
- encodeURIComponent(stringify(arguments[i]));
+ message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
+ encodeURIComponent(toDebugString(arguments[i]));
}
return new ErrorConstructor(message);
};
@@ -130,12 +113,11 @@
isBoolean: true,
isPromiseLike: true,
trim: true,
+ escapeForRegexp: true,
isElement: true,
makeMap: true,
- size: true,
includes: true,
arrayRemove: true,
- isLeafNode: true,
copy: true,
shallowCopy: true,
equals: true,
@@ -205,7 +187,7 @@
* @param {string} string String to be converted to lowercase.
* @returns {string} Lowercased string.
*/
-var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
+var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
@@ -218,7 +200,7 @@
* @param {string} string String to be converted to uppercase.
* @returns {string} Uppercased string.
*/
-var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
+var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
var manualLowercase = function(s) {
@@ -244,8 +226,8 @@
}
-var /** holds major version number for IE or NaN for real browsers */
- msie,
+var
+ msie, // holds major version number for IE, or NaN if UA is not IE.
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
@@ -302,6 +284,11 @@
* It is worth noting that `.forEach` does not iterate over inherited properties because it filters
* using the `hasOwnProperty` method.
*
+ * Unlike ES262's
+ * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
+ * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
+ * return the value provided.
+ *
```js
var values = {name: 'misko', gender: 'male'};
var log = [];
@@ -349,18 +336,12 @@
}
function sortedKeys(obj) {
- var keys = [];
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- keys.push(key);
- }
- }
- return keys.sort();
+ return Object.keys(obj).sort();
}
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
- for ( var i = 0; i < keys.length; i++) {
+ for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
return keys;
@@ -415,6 +396,7 @@
* 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)`.
+ * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
@@ -444,7 +426,7 @@
function inherit(parent, extra) {
- return extend(new (extend(function() {}, {prototype:parent}))(), extra);
+ return extend(Object.create(parent), extra);
}
/**
@@ -501,7 +483,7 @@
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is undefined.
*/
-function isUndefined(value){return typeof value === 'undefined';}
+function isUndefined(value) {return typeof value === 'undefined';}
/**
@@ -516,7 +498,7 @@
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is defined.
*/
-function isDefined(value){return typeof value !== 'undefined';}
+function isDefined(value) {return typeof value !== 'undefined';}
/**
@@ -532,7 +514,7 @@
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Object` but not `null`.
*/
-function isObject(value){
+function isObject(value) {
// http://jsperf.com/isobject4
return value !== null && typeof value === 'object';
}
@@ -550,7 +532,7 @@
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `String`.
*/
-function isString(value){return typeof value === 'string';}
+function isString(value) {return typeof value === 'string';}
/**
@@ -565,7 +547,7 @@
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
-function isNumber(value){return typeof value === 'number';}
+function isNumber(value) {return typeof value === 'number';}
/**
@@ -611,7 +593,7 @@
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Function`.
*/
-function isFunction(value){return typeof value === 'function';}
+function isFunction(value) {return typeof value === 'function';}
/**
@@ -667,6 +649,14 @@
return isString(value) ? value.trim() : value;
};
+// Copied from:
+// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
+// Prereq: s is a string.
+var escapeForRegexp = function(s) {
+ return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
+ replace(/\x08/g, '\\x08');
+};
+
/**
* @ngdoc function
@@ -692,43 +682,15 @@
*/
function makeMap(str) {
var obj = {}, items = str.split(","), i;
- for ( i = 0; i < items.length; i++ )
+ for (i = 0; i < items.length; i++)
obj[ items[i] ] = true;
return obj;
}
function nodeName_(element) {
- return lowercase(element.nodeName || element[0].nodeName);
+ return lowercase(element.nodeName || (element[0] && element[0].nodeName));
}
-
-
-/**
- * @description
- * Determines the number of elements in an array, the number of properties an object has, or
- * the length of a string.
- *
- * Note: This function is used to augment the Object type in Angular expressions. See
- * {@link angular.Object} for more information about Angular arrays.
- *
- * @param {Object|Array|string} obj Object, array, or string to inspect.
- * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
- * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
- */
-function size(obj, ownPropsOnly) {
- var count = 0, key;
-
- if (isArray(obj) || isString(obj)) {
- return obj.length;
- } else if (isObject(obj)) {
- for (key in obj)
- if (!ownPropsOnly || obj.hasOwnProperty(key))
- count++;
- }
-
- return count;
-}
-
function includes(array, obj) {
return Array.prototype.indexOf.call(array, obj) != -1;
@@ -736,21 +698,9 @@
function arrayRemove(array, value) {
var index = array.indexOf(value);
- if (index >=0)
+ if (index >= 0)
array.splice(index, 1);
return value;
-}
-
-function isLeafNode (node) {
- if (node) {
- switch (nodeName_(node)) {
- case "option":
- case "pre":
- case "title":
- return true;
- }
- }
- return false;
}
/**
@@ -850,7 +800,7 @@
var result;
if (isArray(source)) {
destination.length = 0;
- for ( var i = 0; i < source.length; i++) {
+ for (var i = 0; i < source.length; i++) {
result = copy(source[i], null, stackSource, stackDest);
if (isObject(source[i])) {
stackSource.push(source[i]);
@@ -867,8 +817,8 @@
delete destination[key];
});
}
- for ( var key in source) {
- if(source.hasOwnProperty(key)) {
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
result = copy(source[key], null, stackSource, stackDest);
if (isObject(source[key])) {
stackSource.push(source[key]);
@@ -949,7 +899,7 @@
if (isArray(o1)) {
if (!isArray(o2)) return false;
if ((length = o1.length) == o2.length) {
- for(key=0; key<length; key++) {
+ for (key = 0; key < length; key++) {
if (!equals(o1[key], o2[key])) return false;
}
return true;
@@ -962,12 +912,12 @@
} else {
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
keySet = {};
- for(key in o1) {
+ for (key in o1) {
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
if (!equals(o1[key], o2[key])) return false;
keySet[key] = true;
}
- for(key in o2) {
+ for (key in o2) {
if (!keySet.hasOwnProperty(key) &&
key.charAt(0) !== '$' &&
o2[key] !== undefined &&
@@ -1035,7 +985,7 @@
return curryArgs.length
? function() {
return arguments.length
- ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
+ ? fn.apply(self, concat(curryArgs, arguments, 0))
: fn.apply(self, curryArgs);
}
: function() {
@@ -1078,12 +1028,16 @@
* stripped since angular uses this notation internally.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
- * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
+ * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
+ * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
if (typeof obj === 'undefined') return undefined;
- return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
+ if (!isNumber(pretty)) {
+ pretty = pretty ? 2 : null;
+ }
+ return JSON.stringify(obj, toJsonReplacer, pretty);
}
@@ -1115,14 +1069,14 @@
// turns out IE does not let you set .html() on elements which
// are not allowed to have children. So we just ignore it.
element.empty();
- } catch(e) {}
+ } catch (e) {}
var elemHtml = jqLite('<div>').append(element).html();
try {
return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
elemHtml.
match(/^(<[^>]+>)/)[1].
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
- } catch(e) {
+ } catch (e) {
return lowercase(elemHtml);
}
@@ -1142,7 +1096,7 @@
function tryDecodeURIComponent(value) {
try {
return decodeURIComponent(value);
- } catch(e) {
+ } catch (e) {
// Ignore any invalid uri component
}
}
@@ -1155,14 +1109,14 @@
function parseKeyValue(/**string*/keyValue) {
var obj = {}, key_value, key;
forEach((keyValue || "").split('&'), function(keyValue) {
- if ( keyValue ) {
+ if (keyValue) {
key_value = keyValue.replace(/\+/g,'%20').split('=');
key = tryDecodeURIComponent(key_value[0]);
- if ( isDefined(key) ) {
+ if (isDefined(key)) {
var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
if (!hasOwnProperty.call(obj, key)) {
obj[key] = val;
- } else if(isArray(obj[key])) {
+ } else if (isArray(obj[key])) {
obj[key].push(val);
} else {
obj[key] = [obj[key],val];
@@ -1235,7 +1189,7 @@
function getNgAttribute(element, ngAttr) {
var attr, i, ii = ngAttrPrefixes.length;
element = jqLite(element);
- for (i=0; i<ii; ++i) {
+ for (i = 0; i < ii; ++i) {
attr = ngAttrPrefixes[i] + ngAttr;
if (isString(attr = element.attr(attr))) {
return attr;
@@ -1445,8 +1399,8 @@
* @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported:
*
- * - `strictDi`: disable automatic function annotation for the application. This is meant to
- * assist in finding bugs which break minified code.
+ * * `strictDi` - disable automatic function annotation for the application. This is meant to
+ * assist in finding bugs which break minified code. Defaults to `false`.
*
* @returns {auto.$injector} Returns the newly created injector for this app.
*/
@@ -1999,7 +1953,7 @@
config(configFn);
}
- return moduleInstance;
+ return moduleInstance;
/**
* @param {string} provider
@@ -2018,6 +1972,34 @@
};
});
+}
+
+/* global: toDebugString: true */
+
+function serializeObject(obj) {
+ var seen = [];
+
+ return JSON.stringify(obj, function(key, val) {
+ val = toJsonReplacer(key, val);
+ if (isObject(val)) {
+
+ if (seen.indexOf(val) >= 0) return '<<already seen>>';
+
+ seen.push(val);
+ }
+ return val;
+ });
+}
+
+function toDebugString(obj) {
+ if (typeof obj === 'function') {
+ return obj.toString().replace(/ \{[\s\S]*$/, '');
+ } else if (typeof obj === 'undefined') {
+ return 'undefined';
+ } else if (typeof obj !== 'string') {
+ return serializeObject(obj);
+ }
+ return obj;
}
/* global angularModule: true,
@@ -2103,7 +2085,8 @@
$TimeoutProvider,
$$RAFProvider,
$$AsyncCallbackProvider,
- $WindowProvider
+ $WindowProvider,
+ $$jqLiteProvider
*/
@@ -2122,15 +2105,15 @@
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.3.0', // all of these placeholder strings will be replaced by grunt's
+ full: '1.3.6', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 3,
- dot: 0,
- codeName: 'superluminal-nudge'
+ dot: 6,
+ codeName: 'robofunky-danceblaster'
};
-function publishExternalAPI(angular){
+function publishExternalAPI(angular) {
extend(angular, {
'bootstrap': bootstrap,
'copy': copy,
@@ -2256,7 +2239,8 @@
$timeout: $TimeoutProvider,
$window: $WindowProvider,
$$rAF: $$RAFProvider,
- $$asyncCallback : $$AsyncCallbackProvider
+ $$asyncCallback: $$AsyncCallbackProvider,
+ $$jqLite: $$jqLiteProvider
});
}
]);
@@ -2306,7 +2290,7 @@
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
* - [`data()`](http://api.jquery.com/data/)
* - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
@@ -2349,10 +2333,12 @@
* `'ngModel'`).
* - `injector()` - retrieves the injector of the current element or its parent.
* - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
- * element or its parent.
+ * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
+ * be enabled.
* - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
* current element. This getter should be used only on elements that contain a directive which starts a new isolate
* scope. Calling `scope()` on this element always returns the original non-isolate scope.
+ * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
@@ -2384,7 +2370,7 @@
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
-var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"};
+var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
var jqLiteMinErr = minErr('jqLite');
/**
@@ -2513,7 +2499,7 @@
return element.cloneNode(true);
}
-function jqLiteDealoc(element, onlyDescendants){
+function jqLiteDealoc(element, onlyDescendants) {
if (!onlyDescendants) jqLiteRemoveData(element);
if (element.querySelectorAll) {
@@ -2620,7 +2606,7 @@
function jqLiteHasClass(element, selector) {
if (!element.getAttribute) return false;
return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
- indexOf( " " + selector + " " ) > -1);
+ indexOf(" " + selector + " ") > -1);
}
function jqLiteRemoveClass(element, cssClasses) {
@@ -2679,13 +2665,13 @@
function jqLiteController(element, name) {
- return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
+ return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
}
function jqLiteInheritedData(element, name, value) {
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
- if(element.nodeType == NODE_TYPE_DOCUMENT) {
+ if (element.nodeType == NODE_TYPE_DOCUMENT) {
element = element.documentElement;
}
var names = isArray(name) ? name : [name];
@@ -2743,7 +2729,7 @@
}
// check if document is already loaded
- if (document.readyState === 'complete'){
+ if (document.readyState === 'complete') {
setTimeout(trigger);
} else {
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
@@ -2751,12 +2737,11 @@
// jshint -W064
JQLite(window).on('load', trigger); // fallback to window.onload for others
// jshint +W064
- this.on('DOMContentLoaded', trigger);
}
},
toString: function() {
var value = [];
- forEach(this, function(e){ value.push('' + e);});
+ forEach(this, function(e) { value.push('' + e);});
return '[' + value.join(', ') + ']';
},
@@ -2784,11 +2769,11 @@
BOOLEAN_ELEMENTS[value] = true;
});
var ALIASED_ATTR = {
- 'ngMinlength' : 'minlength',
- 'ngMaxlength' : 'maxlength',
- 'ngMin' : 'min',
- 'ngMax' : 'max',
- 'ngPattern' : 'pattern'
+ 'ngMinlength': 'minlength',
+ 'ngMaxlength': 'maxlength',
+ 'ngMin': 'min',
+ 'ngMax': 'max',
+ 'ngPattern': 'pattern'
};
function getBooleanAttrName(element, name) {
@@ -2847,7 +2832,7 @@
}
},
- attr: function(element, name, value){
+ attr: function(element, name, value) {
var lowercasedName = lowercase(name);
if (BOOLEAN_ATTR[lowercasedName]) {
if (isDefined(value)) {
@@ -2860,7 +2845,7 @@
}
} else {
return (element[name] ||
- (element.attributes.getNamedItem(name)|| noop).specified)
+ (element.attributes.getNamedItem(name) || noop).specified)
? lowercasedName
: undefined;
}
@@ -2900,7 +2885,7 @@
if (isUndefined(value)) {
if (element.multiple && nodeName_(element) === 'select') {
var result = [];
- forEach(element.options, function (option) {
+ forEach(element.options, function(option) {
if (option.selected) {
result.push(option.value || option.text);
}
@@ -2921,7 +2906,7 @@
},
empty: jqLiteEmpty
-}, function(fn, name){
+}, function(fn, name) {
/**
* Properties: writes return selection, reads return first value
*/
@@ -2973,7 +2958,7 @@
});
function createEventHandler(element, events) {
- var eventHandler = function (event, type) {
+ var eventHandler = function(event, type) {
// jQuery specific api
event.isDefaultPrevented = function() {
return event.defaultPrevented;
@@ -3029,7 +3014,7 @@
forEach({
removeData: jqLiteRemoveData,
- on: function jqLiteOn(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.
@@ -3065,7 +3050,7 @@
var target = this, related = event.relatedTarget;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
- if ( !related || (related !== target && !target.contains(related)) ){
+ if (!related || (related !== target && !target.contains(related))) {
handle(event, type);
}
});
@@ -3099,7 +3084,7 @@
replaceWith: function(element, replaceNode) {
var index, parent = element.parentNode;
jqLiteDealoc(element);
- forEach(new JQLite(replaceNode), function(node){
+ forEach(new JQLite(replaceNode), function(node) {
if (index) {
parent.insertBefore(node, index.nextSibling);
} else {
@@ -3111,7 +3096,7 @@
children: function(element) {
var children = [];
- forEach(element.childNodes, function(element){
+ forEach(element.childNodes, function(element) {
if (element.nodeType === NODE_TYPE_ELEMENT)
children.push(element);
});
@@ -3137,7 +3122,7 @@
prepend: function(element, node) {
if (element.nodeType === NODE_TYPE_ELEMENT) {
var index = element.firstChild;
- forEach(new JQLite(node), function(child){
+ forEach(new JQLite(node), function(child) {
element.insertBefore(child, index);
});
}
@@ -3174,7 +3159,7 @@
toggleClass: function(element, selector, condition) {
if (selector) {
- forEach(selector.split(' '), function(className){
+ forEach(selector.split(' '), function(className) {
var classCondition = condition;
if (isUndefined(classCondition)) {
classCondition = !jqLiteHasClass(element, className);
@@ -3239,14 +3224,14 @@
});
}
}
-}, function(fn, name){
+}, function(fn, name) {
/**
* chaining functions
*/
JQLite.prototype[name] = function(arg1, arg2, arg3) {
var value;
- for(var i = 0, ii = this.length; i < ii; i++) {
+ for (var i = 0, ii = this.length; i < ii; i++) {
if (isUndefined(value)) {
value = fn(this[i], arg1, arg2, arg3);
if (isDefined(value)) {
@@ -3264,6 +3249,27 @@
JQLite.prototype.bind = JQLite.prototype.on;
JQLite.prototype.unbind = JQLite.prototype.off;
});
+
+
+// Provider for private $$jqLite service
+function $$jqLiteProvider() {
+ this.$get = function $$jqLite() {
+ return extend(JQLite, {
+ hasClass: function(node, classes) {
+ if (node.attr) node = node[0];
+ return jqLiteHasClass(node, classes);
+ },
+ addClass: function(node, classes) {
+ if (node.attr) node = node[0];
+ return jqLiteAddClass(node, classes);
+ },
+ removeClass: function(node, classes) {
+ if (node.attr) node = node[0];
+ return jqLiteRemoveClass(node, classes);
+ }
+ });
+ };
+}
/**
* Computes a hash of an 'obj'.
@@ -3348,9 +3354,10 @@
* 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.
+ * {@link angular.module}. The `ng` module must be explicitly added.
+ * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
+ * disallows argument name annotation inference.
* @returns {injector} Injector object. See {@link auto.$injector $injector}.
*
* @example
@@ -3496,8 +3503,10 @@
* ## Inference
*
* In JavaScript calling `toString()` on a function returns the function definition. The definition
- * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
- * minification, and obfuscation tools since these tools change the argument names.
+ * can then be parsed and the function arguments can be extracted. This method of discovering
+ * annotations is disallowed when the injector is in strict mode.
+ * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
+ * argument names.
*
* ## `$inject` Annotation
* By adding an `$inject` property onto a function the injection parameters can be specified.
@@ -3514,6 +3523,7 @@
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
+ * @param {string} caller An optional string to provide the origin of the function call for error messages.
* @return {*} The instance.
*/
@@ -3582,6 +3592,8 @@
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
* ```
*
+ * You can disallow this method by using strict injection mode.
+ *
* This method does not work with code minification / obfuscation. For this reason the following
* annotation strategies are supported.
*
@@ -3633,6 +3645,8 @@
*
* @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
* be retrieved as described above.
+ *
+ * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
*
* @returns {Array.<string>} The names of the services which the function requires.
*/
@@ -3960,14 +3974,17 @@
}
},
providerInjector = (providerCache.$injector =
- createInternalInjector(providerCache, function() {
+ createInternalInjector(providerCache, function(serviceName, caller) {
+ if (angular.isString(caller)) {
+ path.push(caller);
+ }
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
- createInternalInjector(instanceCache, function(servicename) {
- var provider = providerInjector.get(servicename + providerSuffix);
- return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
+ createInternalInjector(instanceCache, function(serviceName, caller) {
+ var provider = providerInjector.get(serviceName + providerSuffix, caller);
+ return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
@@ -4002,7 +4019,7 @@
function enforceReturnValue(name, factory) {
return function enforcedReturnValue() {
- var result = instanceInjector.invoke(factory, this, undefined, name);
+ var result = instanceInjector.invoke(factory, this);
if (isUndefined(result)) {
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
}
@@ -4043,7 +4060,7 @@
////////////////////////////////////
// Module Loading
////////////////////////////////////
- function loadModules(modulesToLoad){
+ function loadModules(modulesToLoad) {
var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
@@ -4051,7 +4068,7 @@
function runInvokeQueue(queue) {
var i, ii;
- for(i = 0, ii = queue.length; i < ii; i++) {
+ for (i = 0, ii = queue.length; i < ii; i++) {
var invokeArgs = queue[i],
provider = providerInjector.get(invokeArgs[0]);
@@ -4097,7 +4114,7 @@
function createInternalInjector(cache, factory) {
- function getService(serviceName) {
+ function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
@@ -4108,7 +4125,7 @@
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
- return cache[serviceName] = factory(serviceName);
+ return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
@@ -4131,7 +4148,7 @@
length, i,
key;
- for(i = 0, length = $inject.length; i < length; i++) {
+ for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
@@ -4140,7 +4157,7 @@
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
- : getService(key)
+ : getService(key, serviceName)
);
}
if (isArray(fn)) {
@@ -4153,14 +4170,11 @@
}
function instantiate(Type, locals, serviceName) {
- var Constructor = function() {},
- instance, returnedValue;
-
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
- Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
- instance = new Constructor();
- returnedValue = invoke(Type, instance, locals, serviceName);
+ // Object creation: http://jsperf.com/create-constructor/2
+ var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
+ var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
@@ -4196,7 +4210,7 @@
* @name $anchorScrollProvider#disableAutoScrolling
*
* @description
- * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
+ * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically 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.
*
@@ -4347,7 +4361,6 @@
*/
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
// (using `Array#some()` instead of `angular#forEach()` since it's more performant
@@ -4521,7 +4534,7 @@
* @return {RegExp} The current CSS className expression value. If null then there is no expression value
*/
this.classNameFilter = function(expression) {
- if(arguments.length === 1) {
+ if (arguments.length === 1) {
this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
}
return this.$$classNameFilter;
@@ -4616,7 +4629,7 @@
* page}.
*/
return {
- animate : function(element, from, to) {
+ animate: function(element, from, to) {
applyStyles(element, { from: from, to: to });
return asyncPromise();
},
@@ -4637,7 +4650,7 @@
* @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, options) {
+ enter: function(element, parent, after, options) {
applyStyles(element, options);
after ? after.after(element)
: parent.prepend(element);
@@ -4655,7 +4668,7 @@
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
- leave : function(element, options) {
+ leave: function(element, options) {
element.remove();
return asyncPromise();
},
@@ -4678,7 +4691,7 @@
* @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, options) {
+ 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, options);
@@ -4697,16 +4710,16 @@
* @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, options) {
+ addClass: function(element, className, options) {
return this.setClass(element, className, [], options);
},
- $$addClassImmediately : function(element, className, options) {
+ $$addClassImmediately: function(element, className, options) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
- forEach(element, function (element) {
+ forEach(element, function(element) {
jqLiteAddClass(element, className);
});
applyStyles(element, options);
@@ -4726,16 +4739,16 @@
* @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, options) {
+ removeClass: function(element, className, options) {
return this.setClass(element, [], className, options);
},
- $$removeClassImmediately : function(element, className, options) {
+ $$removeClassImmediately: function(element, className, options) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
- forEach(element, function (element) {
+ forEach(element, function(element) {
jqLiteRemoveClass(element, className);
});
applyStyles(element, options);
@@ -4756,7 +4769,7 @@
* @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, options) {
+ setClass: function(element, add, remove, options) {
var self = this;
var STORAGE_KEY = '$$animateClasses';
var createdCache = false;
@@ -4766,7 +4779,7 @@
if (!cache) {
cache = {
classes: {},
- options : options
+ options: options
};
createdCache = true;
} else if (options && cache.options) {
@@ -4803,20 +4816,20 @@
return cache.promise;
},
- $$setClassImmediately : function(element, add, remove, options) {
+ $$setClassImmediately: function(element, add, remove, options) {
add && this.$$addClassImmediately(element, add);
remove && this.$$removeClassImmediately(element, remove);
applyStyles(element, options);
return asyncPromise();
},
- enabled : noop,
- cancel : noop
+ enabled: noop,
+ cancel: noop
};
}];
}];
-function $$AsyncCallbackProvider(){
+function $$AsyncCallbackProvider() {
this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) {
return $$rAF.supported
? function(fn) { return $$rAF(fn); }
@@ -4846,8 +4859,7 @@
/**
* @param {object} window The global window object.
* @param {object} document jQuery wrapped document.
- * @param {function()} XHR XMLHttpRequest constructor.
- * @param {object} $log console.log or an object with the same interface.
+ * @param {object} $log window.console or an object with the same interface.
* @param {object} $sniffer $sniffer service
*/
function Browser(window, document, $log, $sniffer) {
@@ -4878,7 +4890,7 @@
} finally {
outstandingRequestCount--;
if (outstandingRequestCount === 0) {
- while(outstandingRequestCallbacks.length) {
+ while (outstandingRequestCallbacks.length) {
try {
outstandingRequestCallbacks.pop()();
} catch (e) {
@@ -4887,6 +4899,11 @@
}
}
}
+ }
+
+ function getHash(url) {
+ var index = url.indexOf('#');
+ return index === -1 ? '' : url.substr(index + 1);
}
/**
@@ -4899,7 +4916,7 @@
// force browser to execute all pollFns - this is needed so that cookies and other pollers fire
// at some deterministic time in respect to the test runner's actions. Leaving things up to the
// regular poller would result in flaky tests.
- forEach(pollFns, function(pollFn){ pollFn(); });
+ forEach(pollFns, function(pollFn) { pollFn(); });
if (outstandingRequestCount === 0) {
callback();
@@ -4941,7 +4958,7 @@
*/
function startPoller(interval, setTimeout) {
(function check() {
- forEach(pollFns, function(pollFn){ pollFn(); });
+ forEach(pollFns, function(pollFn) { pollFn(); });
pollTimeout = setTimeout(check, interval);
})();
}
@@ -4998,7 +5015,7 @@
// 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;
+ return self;
}
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url;
@@ -5018,8 +5035,10 @@
}
if (replace) {
location.replace(url);
- } else {
+ } else if (!sameBase) {
location.href = url;
+ } else {
+ location.hash = getHash(url);
}
}
return self;
@@ -5197,8 +5216,8 @@
// - 20 cookies per unique domain
// - 4096 bytes per cookie
if (cookieLength > 4096) {
- $log.warn("Cookie '"+ name +
- "' possibly not set or overflowed because it was too large ("+
+ $log.warn("Cookie '" + name +
+ "' possibly not set or overflowed because it was too large (" +
cookieLength + " > 4096 bytes)!");
}
}
@@ -5276,9 +5295,9 @@
}
-function $BrowserProvider(){
+function $BrowserProvider() {
this.$get = ['$window', '$log', '$sniffer', '$document',
- function( $window, $log, $sniffer, $document){
+ function($window, $log, $sniffer, $document) {
return new Browser($window, $document, $log, $sniffer);
}];
}
@@ -5652,7 +5671,8 @@
* ```
*
* **Note:** the `script` tag containing the template does not need to be included in the `head` of
- * the document, but it must be below the `ng-app` definition.
+ * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
+ * element with ng-app attribute), otherwise the template will be ignored.
*
* Adding via the $templateCache service:
*
@@ -5797,7 +5817,7 @@
* #### `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
+ * together as the directive elements. It is recommended that this feature be used on directives
* which are not strictly behavioural (such as {@link ngClick}), and which
* do not manipulate or replace child nodes (such as {@link ngInclude}).
*
@@ -5846,7 +5866,9 @@
* value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
* in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
* scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
- * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
+ * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
+ * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
+ * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
*
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
* If no `attr` name is specified then the attribute name is assumed to be the same as the
@@ -5860,7 +5882,7 @@
*
*
* #### `bindToController`
- * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will
+ * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` 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.
*
@@ -6302,10 +6324,17 @@
*
*
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
- * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
+ * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
+ *
+ * <div class="alert alert-error">
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
+ * e.g. will not use the right outer scope. Please pass the transclude function as a
+ * `parentBoundTranscludeFn` to the link function instead.
+ * </div>
+ *
* @param {number} maxPriority only apply directives lower than given priority (Only effects the
* root element(s), not their children)
- * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
+ * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
@@ -6316,6 +6345,19 @@
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
+ *
+ * * `options` - An optional object hash with linking options. If `options` is provided, then the following
+ * keys may be used to control linking behavior:
+ *
+ * * `parentBoundTranscludeFn` - the transclude function made available to
+ * directives; if given, it will be passed through to the link functions of
+ * directives found in `element` during compilation.
+ * * `transcludeControllers` - an object hash with keys that map controller names
+ * to controller instances; if given, it will make the controllers
+ * available to directives.
+ * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
+ * the cloned elements; only needed for transcludes that are allowed to contain non html
+ * elements (e.g. SVG elements). See also the directive.controller property.
*
* Calling the linking function returns the element of the template. It is either the original
* element passed in, or the clone of the element if the `cloneAttachFn` is provided.
@@ -6362,8 +6404,8 @@
function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
- COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
- CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
+ COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
+ CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
@@ -6373,7 +6415,7 @@
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
function parseIsolateBindings(scope, directiveName) {
- var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
+ var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
@@ -6388,9 +6430,10 @@
}
bindings[scopeName] = {
- attrName: match[3] || scopeName,
- mode: match[1],
- optional: match[2] === '?'
+ mode: match[1][0],
+ collection: match[2] === '*',
+ optional: match[3] === '?',
+ attrName: match[4] || scopeName
};
});
@@ -6462,7 +6505,7 @@
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during a[href] sanitization.
*
- * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ * The sanitization is a security measure aimed at preventing XSS attacks via html links.
*
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
@@ -6529,14 +6572,14 @@
* * `ng-binding` CSS class
* * `$binding` data property containing an array of the binding expressions
*
- * You may want to use this in production for a significant performance boost. See
+ * You may want to disable this in production for a significant performance boost. See
* {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
*
* The default value is true.
*/
var debugInfoEnabled = true;
this.debugInfoEnabled = function(enabled) {
- if(isDefined(enabled)) {
+ if (isDefined(enabled)) {
debugInfoEnabled = enabled;
return this;
}
@@ -6566,6 +6609,21 @@
};
Attributes.prototype = {
+ /**
+ * @ngdoc method
+ * @name $compile.directive.Attributes#$normalize
+ * @kind function
+ *
+ * @description
+ * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
+ * `data-`) to its normalized, camelCase form.
+ *
+ * Also there is special case for Moz prefix starting with upper case letter.
+ *
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
+ *
+ * @param {string} name Name to normalize
+ */
$normalize: directiveNormalize,
@@ -6580,8 +6638,8 @@
*
* @param {string} classVal The className value that will be added to the element
*/
- $addClass : function(classVal) {
- if(classVal && classVal.length > 0) {
+ $addClass: function(classVal) {
+ if (classVal && classVal.length > 0) {
$animate.addClass(this.$$element, classVal);
}
},
@@ -6597,8 +6655,8 @@
*
* @param {string} classVal The className value that will be removed from the element
*/
- $removeClass : function(classVal) {
- if(classVal && classVal.length > 0) {
+ $removeClass: function(classVal) {
+ if (classVal && classVal.length > 0) {
$animate.removeClass(this.$$element, classVal);
}
},
@@ -6615,7 +6673,7 @@
* @param {string} newClasses The current CSS className value
* @param {string} oldClasses The former CSS className value
*/
- $updateClass : function(newClasses, oldClasses) {
+ $updateClass: function(newClasses, oldClasses) {
var toAdd = tokenDifference(newClasses, oldClasses);
if (toAdd && toAdd.length) {
$animate.addClass(this.$$element, toAdd);
@@ -6645,13 +6703,12 @@
booleanKey = getBooleanAttrName(node, key),
aliasedKey = getAliasedAttrName(node, key),
observer = key,
- normalizedVal,
nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
- } else if(aliasedKey) {
+ } else if (aliasedKey) {
this[aliasedKey] = value;
observer = aliasedKey;
}
@@ -6689,22 +6746,22 @@
// for each tuples
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
- for (var i=0; i<nbrUrisWith2parts; i++) {
- var innerIdx = i*2;
+ for (var i = 0; i < nbrUrisWith2parts; i++) {
+ var innerIdx = i * 2;
// sanitize the uri
- result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
+ result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
// add the descriptor
- result += ( " " + trim(rawUris[innerIdx+1]));
+ result += (" " + trim(rawUris[innerIdx + 1]));
}
// split the last item into uri and descriptor
- var lastTuple = trim(rawUris[i*2]).split(/\s/);
+ var lastTuple = trim(rawUris[i * 2]).split(/\s/);
// sanitize the last uri
result += $$sanitizeUri(trim(lastTuple[0]), true);
// and add the last descriptor if any
- if( lastTuple.length === 2) {
+ if (lastTuple.length === 2) {
result += (" " + trim(lastTuple[1]));
}
this[key] = value = result;
@@ -6755,7 +6812,7 @@
listeners.push(fn);
$rootScope.$evalAsync(function() {
- if (!listeners.$$inter) {
+ if (!listeners.$$inter && attrs.hasOwnProperty(key)) {
// no one registered attribute interpolation function, so lets call it manually
fn(attrs[key]);
}
@@ -6771,7 +6828,7 @@
function safeAddClass($element, className) {
try {
$element.addClass(className);
- } catch(e) {
+ } catch (e) {
// ignore, since it means that we are trying to set class on
// SVG element, where class name is read-only.
}
@@ -6825,7 +6882,7 @@
}
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in <span>
- forEach($compileNodes, function(node, index){
+ forEach($compileNodes, function(node, index) {
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
}
@@ -6835,8 +6892,22 @@
maxPriority, ignoreDirective, previousCompileContext);
compile.$$addScopeClass($compileNodes);
var namespace = null;
- return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
+ return function publicLinkFn(scope, cloneConnectFn, options) {
assertArg(scope, 'scope');
+
+ options = options || {};
+ var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
+ transcludeControllers = options.transcludeControllers,
+ futureParentElement = options.futureParentElement;
+
+ // When `parentBoundTranscludeFn` is passed, it is a
+ // `controllersBoundTransclude` function (it was previously passed
+ // as `transclude` to directive.link) so we must unwrap it to get
+ // its `boundTranscludeFn`
+ if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
+ parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
+ }
+
if (!namespace) {
namespace = detectNamespaceForChildElements(futureParentElement);
}
@@ -6878,7 +6949,7 @@
if (!node) {
return 'html';
} else {
- return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html';
+ return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
}
}
@@ -6960,7 +7031,7 @@
stableNodeList = nodeList;
}
- for(i = 0, ii = linkFns.length; i < ii;) {
+ for (i = 0, ii = linkFns.length; i < ii;) {
node = stableNodeList[linkFns[i++]];
nodeLinkFn = linkFns[i++];
childLinkFn = linkFns[i++];
@@ -6973,7 +7044,7 @@
childScope = scope;
}
- if ( nodeLinkFn.transcludeOnThisElement ) {
+ if (nodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(
scope, nodeLinkFn.transclude, parentBoundTranscludeFn,
nodeLinkFn.elementTranscludeOnThisElement);
@@ -7006,7 +7077,11 @@
transcludedScope.$$transcluded = true;
}
- return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
+ return transcludeFn(transcludedScope, cloneFn, {
+ parentBoundTranscludeFn: previousBoundTranscludeFn,
+ transcludeControllers: controllers,
+ futureParentElement: futureParentElement
+ });
};
return boundTranscludeFn;
@@ -7028,7 +7103,7 @@
match,
className;
- switch(nodeType) {
+ switch (nodeType) {
case NODE_TYPE_ELEMENT: /* Element */
// use the node name: <directive>
addDirective(directives,
@@ -7120,7 +7195,6 @@
var nodes = [];
var depth = 0;
if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
- var startNode = node;
do {
if (!node) {
throw $compileMinErr('uterdir',
@@ -7204,7 +7278,7 @@
directiveValue;
// executes all directives on the current element
- for(var i = 0, ii = directives.length; i < ii; i++) {
+ for (var i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
var attrStart = directive.$$start;
var attrEnd = directive.$$end;
@@ -7448,7 +7522,7 @@
"Controller '{0}', required by directive '{1}', can't be found!",
require, directiveName);
}
- return value;
+ return value || null;
} else if (isArray(require)) {
value = [];
forEach(require, function(require) {
@@ -7475,7 +7549,13 @@
isolateScope = scope.$new(true);
}
- transcludeFn = boundTranscludeFn && controllersBoundTransclude;
+ if (boundTranscludeFn) {
+ // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
+ // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
+ transcludeFn = controllersBoundTransclude;
+ transcludeFn.$$boundTransclude = boundTranscludeFn;
+ }
+
if (controllerDirectives) {
// TODO: merge `controllers` and `elementControllers` into single object.
controllers = {};
@@ -7510,8 +7590,6 @@
}
if (newIsolateScopeDirective) {
- var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
-
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
templateDirective === newIsolateScopeDirective.$$originalDirective)));
compile.$$addScopeClass($element, true);
@@ -7537,7 +7615,7 @@
isolateBindingContext[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = scope;
- if( attrs[attrName] ) {
+ if (attrs[attrName]) {
// If the attribute has been provided then we trigger an interpolation to ensure
// the value is there for use in the link fn
isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
@@ -7552,7 +7630,7 @@
if (parentGet.literal) {
compare = equals;
} else {
- compare = function(a,b) { return a === b || (a !== a && b !== b); };
+ compare = function(a, b) { return a === b || (a !== a && b !== b); };
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
@@ -7576,7 +7654,12 @@
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
- var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
+ var unwatch;
+ if (definition.collection) {
+ unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
+ } else {
+ unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
+ }
isolateScope.$on('$destroy', unwatch);
break;
@@ -7597,7 +7680,7 @@
}
// PRELINKING
- for(i = 0, ii = preLinkFns.length; i < ii; i++) {
+ for (i = 0, ii = preLinkFns.length; i < ii; i++) {
linkFn = preLinkFns[i];
invokeLinkFn(linkFn,
linkFn.isolateScope ? isolateScope : scope,
@@ -7618,7 +7701,7 @@
childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
// POSTLINKING
- for(i = postLinkFns.length - 1; i >= 0; i--) {
+ for (i = postLinkFns.length - 1; i >= 0; i--) {
linkFn = postLinkFns[i];
invokeLinkFn(linkFn,
linkFn.isolateScope ? isolateScope : scope,
@@ -7678,11 +7761,11 @@
if (name === ignoreDirective) return null;
var match = null;
if (hasDirectives.hasOwnProperty(name)) {
- for(var directive, directives = $injector.get(name + Suffix),
- i = 0, ii = directives.length; i<ii; i++) {
+ for (var directive, directives = $injector.get(name + Suffix),
+ i = 0, ii = directives.length; i < ii; i++) {
try {
directive = directives[i];
- if ( (maxPriority === undefined || maxPriority > directive.priority) &&
+ if ((maxPriority === undefined || maxPriority > directive.priority) &&
directive.restrict.indexOf(location) != -1) {
if (startAttrName) {
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
@@ -7690,7 +7773,7 @@
tDirectives.push(directive);
match = directive;
}
- } catch(e) { $exceptionHandler(e); }
+ } catch (e) { $exceptionHandler(e); }
}
}
return match;
@@ -7707,8 +7790,8 @@
*/
function directiveIsMultiElement(name) {
if (hasDirectives.hasOwnProperty(name)) {
- for(var directive, directives = $injector.get(name + Suffix),
- i = 0, ii = directives.length; i<ii; i++) {
+ for (var directive, directives = $injector.get(name + Suffix),
+ i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
if (directive.multiElement) {
return true;
@@ -7824,7 +7907,7 @@
});
afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
- while(linkQueue.length) {
+ while (linkQueue.length) {
var scope = linkQueue.shift(),
beforeTemplateLinkNode = linkQueue.shift(),
linkRootElement = linkQueue.shift(),
@@ -7861,10 +7944,10 @@
var childBoundTranscludeFn = boundTranscludeFn;
if (scope.$$destroyed) return;
if (linkQueue) {
- linkQueue.push(scope);
- linkQueue.push(node);
- linkQueue.push(rootElement);
- linkQueue.push(childBoundTranscludeFn);
+ linkQueue.push(scope,
+ node,
+ rootElement,
+ childBoundTranscludeFn);
} else {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
@@ -7923,11 +8006,11 @@
function wrapTemplate(type, template) {
type = lowercase(type || 'html');
- switch(type) {
+ switch (type) {
case 'svg':
case 'math':
var wrapper = document.createElement('div');
- wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
+ wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
return wrapper.childNodes[0].childNodes;
default:
return template;
@@ -8004,7 +8087,7 @@
//skip animations when the first digest occurs (when
//both the new and the old values are the same) since
//the CSS classes are the non-interpolated values
- if(name === 'class' && newValue != oldValue) {
+ if (name === 'class' && newValue != oldValue) {
attr.$updateClass(newValue, oldValue);
} else {
attr.$set(name, newValue);
@@ -8034,7 +8117,7 @@
i, ii;
if ($rootElement) {
- for(i = 0, ii = $rootElement.length; i < ii; i++) {
+ for (i = 0, ii = $rootElement.length; i < ii; i++) {
if ($rootElement[i] == firstElementToRemove) {
$rootElement[i++] = newNode;
for (var j = i, j2 = j + removeCount - 1,
@@ -8109,23 +8192,16 @@
function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
try {
linkFn(scope, $element, attrs, controllers, transcludeFn);
- } catch(e) {
+ } catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
}];
}
-var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
+var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
/**
* Converts all accepted directives format into proper directive name.
- * All of these will become 'myDirective':
- * my:Directive
- * my-directive
- * x-my-directive
- * data-my:directive
- *
- * Also there is special case for Moz prefix starting with upper case letter.
* @param name Name to normalize
*/
function directiveNormalize(name) {
@@ -8182,7 +8258,7 @@
/* NodeList */ nodeList,
/* Element */ rootElement,
/* function(Function) */ boundTranscludeFn
-){}
+) {}
function directiveLinkingFn(
/* nodesetLinkingFn */ nodesetLinkingFn,
@@ -8190,7 +8266,7 @@
/* Node */ node,
/* Element */ rootElement,
/* function(Function) */ boundTranscludeFn
-){}
+) {}
function tokenDifference(str1, str2) {
var values = '',
@@ -8198,10 +8274,10 @@
tokens2 = str2.split(/\s+/);
outer:
- for(var i = 0; i < tokens1.length; i++) {
+ for (var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
- for(var j = 0; j < tokens2.length; j++) {
- if(token == tokens2[j]) continue outer;
+ for (var j = 0; j < tokens2.length; j++) {
+ if (token == tokens2[j]) continue outer;
}
values += (values.length > 0 ? ' ' : '') + token;
}
@@ -8284,6 +8360,10 @@
* * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
* `window` object (not recommended)
*
+ * The string can use the `controller as property` syntax, where the controller instance is published
+ * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
+ * to work correctly.
+ *
* @param {Object} locals Injection locals for Controller.
* @return {Object} Instance of given controller.
*
@@ -8307,7 +8387,7 @@
identifier = ident;
}
- if(isString(expression)) {
+ if (isString(expression)) {
match = expression.match(CNTRL_REG),
constructor = match[1],
identifier = identifier || match[3];
@@ -8329,10 +8409,10 @@
//
// This feature is not intended for use by applications, and is thus not documented
// publicly.
- var Constructor = function() {};
- Constructor.prototype = (isArray(expression) ?
+ // Object creation: http://jsperf.com/create-constructor/2
+ var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype;
- instance = new Constructor();
+ instance = Object.create(controllerPrototype);
if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name);
@@ -8393,8 +8473,8 @@
</file>
</example>
*/
-function $DocumentProvider(){
- this.$get = ['$window', function(window){
+function $DocumentProvider() {
+ this.$get = ['$window', function(window) {
return jqLite(window.document);
}];
}
@@ -8415,8 +8495,8 @@
* ## Example:
*
* ```js
- * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
- * return function (exception, cause) {
+ * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
+ * return function(exception, cause) {
* exception.message += ' (caused by "' + cause + '")';
* throw exception;
* };
@@ -8447,6 +8527,25 @@
}];
}
+var APPLICATION_JSON = 'application/json';
+var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
+var JSON_START = /^\s*(\[|\{[^\{])/;
+var JSON_END = /[\}\]]\s*$/;
+var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
+
+function defaultHttpResponseTransform(data, headers) {
+ if (isString(data)) {
+ // strip json vulnerability protection prefix
+ data = data.replace(JSON_PROTECTION_PREFIX, '');
+ var contentType = headers('Content-Type');
+ if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0 && data.trim()) ||
+ (JSON_START.test(data) && JSON_END.test(data))) {
+ data = fromJson(data);
+ }
+ }
+ return data;
+}
+
/**
* Parse headers into key value object
*
@@ -8454,7 +8553,7 @@
* @returns {Object} Parsed headers as key value object
*/
function parseHeaders(headers) {
- var parsed = {}, key, val, i;
+ var parsed = createMap(), key, val, i;
if (!headers) return parsed;
@@ -8491,7 +8590,11 @@
if (!headersObj) headersObj = parseHeaders(headers);
if (name) {
- return headersObj[lowercase(name)] || null;
+ var value = headersObj[lowercase(name)];
+ if (value === void 0) {
+ value = null;
+ }
+ return value;
}
return headersObj;
@@ -8533,18 +8636,17 @@
* Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
* */
function $HttpProvider() {
- var JSON_START = /^\s*(\[|\{[^\{])/,
- JSON_END = /[\}\]]\s*$/,
- PROTECTION_PREFIX = /^\)\]\}',?\n/,
- APPLICATION_JSON = 'application/json',
- CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
-
/**
* @ngdoc property
* @name $httpProvider#defaults
* @description
*
* Object containing default values for all {@link ng.$http $http} requests.
+ *
+ * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
+ * that will provide the cache for all requests who set their `cache` property to `true`.
+ * If you set the `default.cache = false` then only requests that specify their own custom
+ * cache object will be cached. See {@link $http#caching $http Caching} for more information.
*
* - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
* Defaults value is `'XSRF-TOKEN'`.
@@ -8559,21 +8661,11 @@
* - **`defaults.headers.post`**
* - **`defaults.headers.put`**
* - **`defaults.headers.patch`**
+ *
**/
var defaults = this.defaults = {
// transform incoming response data
- transformResponse: [function defaultHttpResponseTransform(data, headers) {
- if (isString(data)) {
- // strip json vulnerability protection prefix
- data = data.replace(PROTECTION_PREFIX, '');
- var contentType = headers('Content-Type');
- if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
- (JSON_START.test(data) && JSON_END.test(data))) {
- data = fromJson(data);
- }
- }
- return data;
- }],
+ transformResponse: [defaultHttpResponseTransform],
// transform outgoing request data
transformRequest: [function(d) {
@@ -8623,9 +8715,18 @@
};
/**
- * Are ordered by request, i.e. they are applied in the same order as the
+ * @ngdoc property
+ * @name $httpProvider#interceptors
+ * @description
+ *
+ * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
+ * pre-processing of request or postprocessing of responses.
+ *
+ * These service factories are ordered by request, i.e. they are applied in the same order as the
* array, on request, but reverse order, on response.
- */
+ *
+ * {@link ng.$http#interceptors Interceptors detailed info}
+ **/
var interceptorFactories = this.interceptors = [];
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
@@ -8775,6 +8876,21 @@
* In addition, you can supply a `headers` property in the config object passed when
* calling `$http(config)`, which overrides the defaults without changing them globally.
*
+ * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
+ * Use the `headers` property, setting the desired header to `undefined`. For example:
+ *
+ * ```js
+ * var req = {
+ * method: 'POST',
+ * url: 'http://example.com',
+ * headers: {
+ * 'Content-Type': undefined
+ * },
+ * data: { test: 'test' },
+ * }
+ *
+ * $http(req).success(function(){...}).error(function(){...});
+ * ```
*
* ## Transforming Requests and Responses
*
@@ -9021,12 +9137,14 @@
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
- * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
+ * Overriding the Default Transformations}
* - **transformResponse** –
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
- * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
+ * See {@link ng.$http#overriding-the-default-transformations-per-request
+ * Overriding the Default Transformations}
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
@@ -9154,6 +9272,10 @@
};
var headers = mergeHeaders(requestConfig);
+ if (!angular.isObject(requestConfig)) {
+ throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
+ }
+
extend(config, requestConfig);
config.headers = headers;
config.method = uppercase(config.method);
@@ -9192,7 +9314,7 @@
}
});
- while(chain.length) {
+ while (chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();
@@ -9432,8 +9554,7 @@
if (isDefined(cachedResp)) {
if (isPromiseLike(cachedResp)) {
// cached request has already been sent, but there is no response yet
- cachedResp.then(removePendingReq, removePendingReq);
- return cachedResp;
+ cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
} else {
// serving from cache
if (isArray(cachedResp)) {
@@ -9507,10 +9628,13 @@
status: status,
headers: headersGetter(headers),
config: config,
- statusText : statusText
+ statusText: statusText
});
}
+ function resolvePromiseWithResult(result) {
+ resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
+ }
function removePendingReq() {
var idx = $http.pendingRequests.indexOf(config);
@@ -9528,7 +9652,7 @@
forEach(value, function(v) {
if (isObject(v)) {
- if (isDate(v)){
+ if (isDate(v)) {
v = v.toISOString();
} else {
v = toJson(v);
@@ -9538,7 +9662,7 @@
encodeUriQuery(v));
});
});
- if(parts.length > 0) {
+ if (parts.length > 0) {
url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
}
return url;
@@ -9625,7 +9749,7 @@
statusText);
};
- var requestError = function () {
+ var requestError = function() {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest(callback, -1, null, null, '');
@@ -9672,7 +9796,9 @@
function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
- timeoutId && $browserDefer.cancel(timeoutId);
+ if (timeoutId !== undefined) {
+ $browserDefer.cancel(timeoutId);
+ }
jsonpDone = xhr = null;
callback(status, response, headersString, statusText);
@@ -9767,7 +9893,7 @@
* @param {string=} value new value to set the starting symbol to.
* @returns {string|self} Returns the symbol when used as getter and self if used as setter.
*/
- this.startSymbol = function(value){
+ this.startSymbol = function(value) {
if (value) {
startSymbol = value;
return this;
@@ -9785,7 +9911,7 @@
* @param {string=} value new value to set the ending symbol to.
* @returns {string|self} Returns the symbol when used as getter and self if used as setter.
*/
- this.endSymbol = function(value){
+ this.endSymbol = function(value) {
if (value) {
endSymbol = value;
return this;
@@ -9911,9 +10037,9 @@
concat = [],
expressionPositions = [];
- while(index < textLength) {
- if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
- ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
+ while (index < textLength) {
+ if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
if (index !== startIndex) {
concat.push(unescapeText(text.substring(index, startIndex)));
}
@@ -9947,20 +10073,20 @@
if (!mustHaveExpression || expressions.length) {
var compute = function(values) {
- for(var i = 0, ii = expressions.length; i < ii; i++) {
+ for (var i = 0, ii = expressions.length; i < ii; i++) {
if (allOrNothing && isUndefined(values[i])) return;
concat[expressionPositions[i]] = values[i];
}
return concat.join('');
};
- var getValue = function (value) {
+ var getValue = function(value) {
return trustedContext ?
$sce.getTrusted(trustedContext, value) :
$sce.valueOf(value);
};
- var stringify = function (value) {
+ var stringify = function(value) {
if (value == null) { // null || undefined
return '';
}
@@ -9988,7 +10114,7 @@
}
return compute(values);
- } catch(err) {
+ } catch (err) {
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
err.toString());
$exceptionHandler(newErr);
@@ -9998,7 +10124,7 @@
// all of these properties are undocumented for now
exp: text, //just for compatibility with regular watchers created via $watch
expressions: expressions,
- $$watchDelegate: function (scope, listener, objectEquality) {
+ $$watchDelegate: function(scope, listener, objectEquality) {
var lastValue;
return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
var currValue = compute(values);
@@ -10018,8 +10144,9 @@
function parseStringifyInterceptor(value) {
try {
- return stringify(getValue(value));
- } catch(err) {
+ value = getValue(value);
+ return allOrNothing && !isDefined(value) ? value : stringify(value);
+ } catch (err) {
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
err.toString());
$exceptionHandler(newErr);
@@ -10119,33 +10246,33 @@
* // Don't start a new fight if we are already fighting
* if ( angular.isDefined(stop) ) return;
*
- * stop = $interval(function() {
- * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
- * $scope.blood_1 = $scope.blood_1 - 3;
- * $scope.blood_2 = $scope.blood_2 - 4;
- * } else {
- * $scope.stopFight();
+ * stop = $interval(function() {
+ * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
+ * $scope.blood_1 = $scope.blood_1 - 3;
+ * $scope.blood_2 = $scope.blood_2 - 4;
+ * } else {
+ * $scope.stopFight();
+ * }
+ * }, 100);
+ * };
+ *
+ * $scope.stopFight = function() {
+ * if (angular.isDefined(stop)) {
+ * $interval.cancel(stop);
+ * stop = undefined;
* }
- * }, 100);
- * };
+ * };
*
- * $scope.stopFight = function() {
- * if (angular.isDefined(stop)) {
- * $interval.cancel(stop);
- * stop = undefined;
- * }
- * };
+ * $scope.resetFight = function() {
+ * $scope.blood_1 = 100;
+ * $scope.blood_2 = 120;
+ * };
*
- * $scope.resetFight = function() {
- * $scope.blood_1 = 100;
- * $scope.blood_2 = 120;
- * };
- *
- * $scope.$on('$destroy', function() {
- * // Make sure that the interval is destroyed too
- * $scope.stopFight();
- * });
- * }])
+ * $scope.$on('$destroy', function() {
+ * // Make sure that the interval is destroyed too
+ * $scope.stopFight();
+ * });
+ * }])
* // Register the 'myCurrentTime' directive factory method.
* // We inject $interval and dateFilter service since the factory method is DI.
* .directive('myCurrentTime', ['$interval', 'dateFilter',
@@ -10258,7 +10385,7 @@
*
* * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
*/
-function $LocaleProvider(){
+function $LocaleProvider() {
this.$get = function() {
return {
id: 'en-us',
@@ -10301,7 +10428,7 @@
SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
AMPMS: ['AM','PM'],
medium: 'MMM d, y h:mm:ss a',
- short: 'M/d/yy h:mm a',
+ 'short': 'M/d/yy h:mm a',
fullDate: 'EEEE, MMMM d, y',
longDate: 'MMMM d, y',
mediumDate: 'MMM d, y',
@@ -10342,8 +10469,8 @@
return segments.join('/');
}
-function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
- var parsedUrl = urlResolve(absoluteUrl, appBase);
+function parseAbsoluteUrl(absoluteUrl, locationObj) {
+ var parsedUrl = urlResolve(absoluteUrl);
locationObj.$$protocol = parsedUrl.protocol;
locationObj.$$host = parsedUrl.hostname;
@@ -10351,12 +10478,12 @@
}
-function parseAppUrl(relativeUrl, locationObj, appBase) {
+function parseAppUrl(relativeUrl, locationObj) {
var prefixed = (relativeUrl.charAt(0) !== '/');
if (prefixed) {
relativeUrl = '/' + relativeUrl;
}
- var match = urlResolve(relativeUrl, appBase);
+ var match = urlResolve(relativeUrl);
locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
match.pathname.substring(1) : match.pathname);
locationObj.$$search = parseKeyValue(match.search);
@@ -10388,6 +10515,10 @@
return index == -1 ? url : url.substr(0, index);
}
+function trimEmptyHash(url) {
+ return url.replace(/(#.+)|#$/, '$1');
+}
+
function stripFile(url) {
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
@@ -10411,12 +10542,12 @@
this.$$html5 = true;
basePrefix = basePrefix || '';
var appBaseNoFile = stripFile(appBase);
- parseAbsoluteUrl(appBase, this, appBase);
+ parseAbsoluteUrl(appBase, this);
/**
* Parse given html5 (regular) url string into properties
- * @param {string} newAbsoluteUrl HTML5 url
+ * @param {string} url HTML5 url
* @private
*/
this.$$parse = function(url) {
@@ -10426,7 +10557,7 @@
appBaseNoFile);
}
- parseAppUrl(pathUrl, this, appBase);
+ parseAppUrl(pathUrl, this);
if (!this.$$path) {
this.$$path = '/';
@@ -10457,14 +10588,14 @@
var appUrl, prevAppUrl;
var rewrittenUrl;
- if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
+ if ((appUrl = beginsWith(appBase, url)) !== undefined) {
prevAppUrl = appUrl;
- if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
+ if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) {
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
} else {
rewrittenUrl = appBase + prevAppUrl;
}
- } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
+ } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) {
rewrittenUrl = appBaseNoFile + appUrl;
} else if (appBaseNoFile == url + '/') {
rewrittenUrl = appBaseNoFile;
@@ -10489,7 +10620,7 @@
function LocationHashbangUrl(appBase, hashPrefix) {
var appBaseNoFile = stripFile(appBase);
- parseAbsoluteUrl(appBase, this, appBase);
+ parseAbsoluteUrl(appBase, this);
/**
@@ -10499,17 +10630,26 @@
*/
this.$$parse = function(url) {
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
- var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
- ? beginsWith(hashPrefix, withoutBaseUrl)
- : (this.$$html5)
- ? withoutBaseUrl
- : '';
+ var withoutHashUrl;
- if (!isString(withoutHashUrl)) {
- throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
- hashPrefix);
+ if (withoutBaseUrl.charAt(0) === '#') {
+
+ // The rest of the url starts with a hash so we have
+ // got either a hashbang path or a plain hash fragment
+ withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
+ if (isUndefined(withoutHashUrl)) {
+ // There was no hashbang prefix so we just have a hash fragment
+ withoutHashUrl = withoutBaseUrl;
+ }
+
+ } else {
+ // There was no hashbang path nor hash fragment:
+ // If we are in HTML5 mode we use what is left as the path;
+ // Otherwise we ignore what is left
+ withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
}
- parseAppUrl(withoutHashUrl, this, appBase);
+
+ parseAppUrl(withoutHashUrl, this);
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
@@ -10526,7 +10666,7 @@
* Inside of Angular, we're always using pathnames that
* do not include drive names for routing.
*/
- function removeWindowsDriveName (path, url, base) {
+ function removeWindowsDriveName(path, url, base) {
/*
Matches paths for file protocol on windows,
such as /C:/foo/bar, and captures only /foo/bar.
@@ -10563,7 +10703,7 @@
};
this.$$parseLinkUrl = function(url, relHref) {
- if(stripHash(appBase) == stripHash(url)) {
+ if (stripHash(appBase) == stripHash(url)) {
this.$$parse(url);
return true;
}
@@ -10598,11 +10738,11 @@
var rewrittenUrl;
var appUrl;
- if ( appBase == stripHash(url) ) {
+ if (appBase == stripHash(url)) {
rewrittenUrl = url;
- } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
+ } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
rewrittenUrl = appBase + hashPrefix + appUrl;
- } else if ( appBaseNoFile === url + '/') {
+ } else if (appBaseNoFile === url + '/') {
rewrittenUrl = appBaseNoFile;
}
if (rewrittenUrl) {
@@ -10647,6 +10787,13 @@
* Return full url representation with all segments encoded according to rules specified in
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var absUrl = $location.absUrl();
+ * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
+ * ```
+ *
* @return {string} full url
*/
absUrl: locationGetter('$$absUrl'),
@@ -10662,6 +10809,13 @@
*
* Change path, search and hash, when called with parameter and return `$location`.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var url = $location.url();
+ * // => "/some/path?foo=bar&baz=xoxo"
+ * ```
+ *
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
* @return {string} url
*/
@@ -10670,8 +10824,8 @@
return this.$$url;
var match = PATH_MATCH.exec(url);
- if (match[1]) this.path(decodeURIComponent(match[1]));
- if (match[2] || match[1]) this.search(match[3] || '');
+ if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
+ if (match[2] || match[1] || url === '') this.search(match[3] || '');
this.hash(match[5] || '');
return this;
@@ -10686,6 +10840,13 @@
*
* Return protocol of current url.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var protocol = $location.protocol();
+ * // => "http"
+ * ```
+ *
* @return {string} protocol of current url
*/
protocol: locationGetter('$$protocol'),
@@ -10699,6 +10860,13 @@
*
* Return host of current url.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var host = $location.host();
+ * // => "example.com"
+ * ```
+ *
* @return {string} host of current url.
*/
host: locationGetter('$$host'),
@@ -10711,6 +10879,13 @@
* This method is getter only.
*
* Return port of current url.
+ *
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var port = $location.port();
+ * // => 80
+ * ```
*
* @return {Number} port
*/
@@ -10729,6 +10904,13 @@
*
* Note: Path should always begin with forward slash (/), this method will add the forward slash
* if it is missing.
+ *
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var path = $location.path();
+ * // => "/some/path"
+ * ```
*
* @param {(string|number)=} path New path
* @return {string} path
@@ -10755,10 +10937,9 @@
* var searchObject = $location.search();
* // => {foo: 'bar', baz: 'xoxo'}
*
- *
* // set foo to 'yipee'
* $location.search('foo', 'yipee');
- * // => $location
+ * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
* ```
*
* @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
@@ -10828,6 +11009,13 @@
*
* Change hash fragment when called with parameter and return `$location`.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
+ * var hash = $location.hash();
+ * // => "hashValue"
+ * ```
+ *
* @param {(string|number)=} hash New hash fragment
* @return {string} hash
*/
@@ -10849,7 +11037,7 @@
}
};
-forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) {
+forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
Location.prototype = Object.create(locationPrototype);
/**
@@ -10941,7 +11129,7 @@
* @description
* Use the `$locationProvider` to configure how the application deep linking paths are stored.
*/
-function $LocationProvider(){
+function $LocationProvider() {
var hashPrefix = '',
html5Mode = {
enabled: false,
@@ -11048,7 +11236,7 @@
*/
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
- function( $rootScope, $browser, $sniffer, $rootElement) {
+ function($rootScope, $browser, $sniffer, $rootElement) {
var $location,
LocationMode,
baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
@@ -11149,11 +11337,19 @@
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
var oldState = $location.$$state;
+ var defaultPrevented;
$location.$$parse(newUrl);
$location.$$state = newState;
- if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
- newState, oldState).defaultPrevented) {
+
+ defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
+ newState, oldState).defaultPrevented;
+
+ // if the location was changed by a `$locationChangeStart` handler then stop
+ // processing this location change
+ if ($location.absUrl() !== newUrl) return;
+
+ if (defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
setBrowserUrlWithFallback(oldUrl, false, oldState);
@@ -11167,23 +11363,31 @@
// update browser
$rootScope.$watch(function $locationWatch() {
- var oldUrl = $browser.url();
+ var oldUrl = trimEmptyHash($browser.url());
+ var newUrl = trimEmptyHash($location.absUrl());
var oldState = $browser.state();
var currentReplace = $location.$$replace;
- var urlOrStateChanged = oldUrl !== $location.absUrl() ||
+ var urlOrStateChanged = oldUrl !== newUrl ||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
if (initializing || urlOrStateChanged) {
initializing = false;
$rootScope.$evalAsync(function() {
- if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
- $location.$$state, oldState).defaultPrevented) {
+ var newUrl = $location.absUrl();
+ var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
+ $location.$$state, oldState).defaultPrevented;
+
+ // if the location was changed by a `$locationChangeStart` handler then stop
+ // processing this location change
+ if ($location.absUrl() !== newUrl) return;
+
+ if (defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
} else {
if (urlOrStateChanged) {
- setBrowserUrlWithFallback($location.absUrl(), currentReplace,
+ setBrowserUrlWithFallback(newUrl, currentReplace,
oldState === $location.$$state ? null : $location.$$state);
}
afterLocationChange(oldUrl, oldState);
@@ -11249,7 +11453,7 @@
* @description
* Use the `$logProvider` to configure how the application logs messages
*/
-function $LogProvider(){
+function $LogProvider() {
var debug = true,
self = this;
@@ -11269,7 +11473,7 @@
}
};
- this.$get = ['$window', function($window){
+ this.$get = ['$window', function($window) {
return {
/**
* @ngdoc method
@@ -11314,7 +11518,7 @@
* @description
* Write a debug message
*/
- debug: (function () {
+ debug: (function() {
var fn = consoleLog('debug');
return function() {
@@ -11373,7 +11577,7 @@
// Sandboxing Angular Expressions
// ------------------------------
// Angular expressions are generally considered safe because these expressions only have direct
-// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
+// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
// obtaining a reference to native JS functions such as the Function constructor.
//
// As an example, consider the following Angular expression:
@@ -11382,7 +11586,7 @@
//
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
// against the expression language, but not to prevent exploits that were enabled by exposing
-// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good
+// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
// practice and therefore we are not even trying to protect against interaction with an object
// explicitly exposed in this way.
//
@@ -11390,6 +11594,8 @@
// window or some DOM object that has a reference to window is published onto a Scope.
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
// native objects.
+//
+// See https://docs.angularjs.org/guide/security
function ensureSafeMemberName(name, fullExpression) {
@@ -11398,7 +11604,7 @@
|| name === "__proto__") {
throw $parseMinErr('isecfld',
'Attempting to access a disallowed field in Angular expressions! '
- +'Expression: {0}', fullExpression);
+ + 'Expression: {0}', fullExpression);
}
return name;
}
@@ -11467,7 +11673,7 @@
//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
var OPERATORS = extend(createMap(), {
- '+':function(self, locals, a,b){
+ '+':function(self, locals, a, b) {
a=a(self, locals); b=b(self, locals);
if (isDefined(a)) {
if (isDefined(b)) {
@@ -11475,25 +11681,25 @@
}
return a;
}
- return isDefined(b)?b:undefined;},
- '-':function(self, locals, a,b){
+ return isDefined(b) ? b : undefined;},
+ '-':function(self, locals, a, b) {
a=a(self, locals); b=b(self, locals);
- return (isDefined(a)?a:0)-(isDefined(b)?b:0);
+ return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
},
- '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
- '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
- '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
- '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
- '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
- '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
- '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
- '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
- '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
- '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
- '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
- '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
- '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
- '!':function(self, locals, a){return !a(self, locals);},
+ '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);},
+ '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);},
+ '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);},
+ '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);},
+ '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);},
+ '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);},
+ '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);},
+ '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);},
+ '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);},
+ '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);},
+ '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);},
+ '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);},
+ '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);},
+ '!':function(self, locals, a) {return !a(self, locals);},
//Tokenized as operators but parsed as assignment/filters
'=':true,
@@ -11508,54 +11714,41 @@
/**
* @constructor
*/
-var Lexer = function (options) {
+var Lexer = function(options) {
this.options = options;
};
Lexer.prototype = {
constructor: Lexer,
- lex: function (text) {
+ lex: function(text) {
this.text = text;
this.index = 0;
- this.ch = undefined;
this.tokens = [];
while (this.index < this.text.length) {
- this.ch = this.text.charAt(this.index);
- if (this.is('"\'')) {
- this.readString(this.ch);
- } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) {
+ var ch = this.text.charAt(this.index);
+ if (ch === '"' || ch === "'") {
+ this.readString(ch);
+ } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
this.readNumber();
- } else if (this.isIdent(this.ch)) {
+ } else if (this.isIdent(ch)) {
this.readIdent();
- } else if (this.is('(){}[].,;:?')) {
- this.tokens.push({
- index: this.index,
- text: this.ch
- });
+ } else if (this.is(ch, '(){}[].,;:?')) {
+ this.tokens.push({index: this.index, text: ch});
this.index++;
- } else if (this.isWhitespace(this.ch)) {
+ } else if (this.isWhitespace(ch)) {
this.index++;
} else {
- var ch2 = this.ch + this.peek();
+ var ch2 = ch + this.peek();
var ch3 = ch2 + this.peek(2);
- var fn = OPERATORS[this.ch];
- var fn2 = OPERATORS[ch2];
- var fn3 = OPERATORS[ch3];
- if (fn3) {
- this.tokens.push({index: this.index, text: ch3, fn: fn3});
- this.index += 3;
- } else if (fn2) {
- this.tokens.push({index: this.index, text: ch2, fn: fn2});
- this.index += 2;
- } else if (fn) {
- this.tokens.push({
- index: this.index,
- text: this.ch,
- fn: fn
- });
- this.index += 1;
+ var op1 = OPERATORS[ch];
+ var op2 = OPERATORS[ch2];
+ var op3 = OPERATORS[ch3];
+ if (op1 || op2 || op3) {
+ var token = op3 ? ch3 : (op2 ? ch2 : ch);
+ this.tokens.push({index: this.index, text: token, operator: true});
+ this.index += token.length;
} else {
this.throwError('Unexpected next character ', this.index, this.index + 1);
}
@@ -11564,8 +11757,8 @@
return this.tokens;
},
- is: function(chars) {
- return chars.indexOf(this.ch) !== -1;
+ is: function(ch, chars) {
+ return chars.indexOf(ch) !== -1;
},
peek: function(i) {
@@ -11574,7 +11767,7 @@
},
isNumber: function(ch) {
- return ('0' <= ch && ch <= '9');
+ return ('0' <= ch && ch <= '9') && typeof ch === "string";
},
isWhitespace: function(ch) {
@@ -11627,79 +11820,28 @@
}
this.index++;
}
- number = 1 * number;
this.tokens.push({
index: start,
text: number,
constant: true,
- fn: function() { return number; }
+ value: Number(number)
});
},
readIdent: function() {
- var expression = this.text;
-
- var ident = '';
var start = this.index;
-
- var lastDot, peekIndex, methodName, ch;
-
while (this.index < this.text.length) {
- ch = this.text.charAt(this.index);
- if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) {
- if (ch === '.') lastDot = this.index;
- ident += ch;
- } else {
+ var ch = this.text.charAt(this.index);
+ if (!(this.isIdent(ch) || this.isNumber(ch))) {
break;
}
this.index++;
}
-
- //check if the identifier ends with . and if so move back one char
- if (lastDot && ident[ident.length - 1] === '.') {
- this.index--;
- ident = ident.slice(0, -1);
- lastDot = ident.lastIndexOf('.');
- if (lastDot === -1) {
- lastDot = undefined;
- }
- }
-
- //check if this is not a method invocation and if it is back out to last dot
- if (lastDot) {
- peekIndex = this.index;
- while (peekIndex < this.text.length) {
- ch = this.text.charAt(peekIndex);
- if (ch === '(') {
- methodName = ident.substr(lastDot - start + 1);
- ident = ident.substr(0, lastDot - start);
- this.index = peekIndex;
- break;
- }
- if (this.isWhitespace(ch)) {
- peekIndex++;
- } else {
- break;
- }
- }
- }
-
this.tokens.push({
index: start,
- text: ident,
- fn: CONSTANTS[ident] || getterFn(ident, this.options, expression)
+ text: this.text.slice(start, this.index),
+ identifier: true
});
-
- if (methodName) {
- this.tokens.push({
- index: lastDot,
- text: '.'
- });
- this.tokens.push({
- index: lastDot + 1,
- text: methodName
- });
- }
},
readString: function(quote) {
@@ -11730,9 +11872,8 @@
this.tokens.push({
index: start,
text: rawString,
- string: string,
constant: true,
- fn: function() { return string; }
+ value: string
});
return;
} else {
@@ -11752,13 +11893,13 @@
/**
* @constructor
*/
-var Parser = function (lexer, $filter, options) {
+var Parser = function(lexer, $filter, options) {
this.lexer = lexer;
this.$filter = $filter;
this.options = options;
};
-Parser.ZERO = extend(function () {
+Parser.ZERO = extend(function() {
return 0;
}, {
sharedGetter: true,
@@ -11768,7 +11909,7 @@
Parser.prototype = {
constructor: Parser,
- parse: function (text) {
+ parse: function(text) {
this.text = text;
this.tokens = this.lexer.lex(text);
@@ -11784,7 +11925,7 @@
return value;
},
- primary: function () {
+ primary: function() {
var primary;
if (this.expect('(')) {
primary = this.filterChain();
@@ -11793,16 +11934,12 @@
primary = this.arrayDeclaration();
} else if (this.expect('{')) {
primary = this.object();
+ } else if (this.peek().identifier) {
+ primary = this.identifier();
+ } else if (this.peek().constant) {
+ primary = this.constant();
} else {
- var token = this.expect();
- primary = token.fn;
- if (!primary) {
- this.throwError('not a primary expression', token);
- }
- if (token.constant) {
- primary.constant = true;
- primary.literal = true;
- }
+ this.throwError('not a primary expression', this.peek());
}
var next, context;
@@ -11836,8 +11973,11 @@
},
peek: function(e1, e2, e3, e4) {
- if (this.tokens.length > 0) {
- var token = this.tokens[0];
+ return this.peekAhead(0, e1, e2, e3, e4);
+ },
+ peekAhead: function(i, e1, e2, e3, e4) {
+ if (this.tokens.length > i) {
+ var token = this.tokens[i];
var t = token.text;
if (t === e1 || t === e2 || t === e3 || t === e4 ||
(!e1 && !e2 && !e3 && !e4)) {
@@ -11847,7 +11987,7 @@
return false;
},
- expect: function(e1, e2, e3, e4){
+ expect: function(e1, e2, e3, e4) {
var token = this.peek(e1, e2, e3, e4);
if (token) {
this.tokens.shift();
@@ -11856,13 +11996,20 @@
return false;
},
- consume: function(e1){
- if (!this.expect(e1)) {
+ consume: function(e1) {
+ if (this.tokens.length === 0) {
+ throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
+ }
+
+ var token = this.expect(e1);
+ if (!token) {
this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
}
+ return token;
},
- unaryFn: function(fn, right) {
+ unaryFn: function(op, right) {
+ var fn = OPERATORS[op];
return extend(function $parseUnaryFn(self, locals) {
return fn(self, locals, right);
}, {
@@ -11871,12 +12018,35 @@
});
},
- binaryFn: function(left, fn, right, isBranching) {
+ binaryFn: function(left, op, right, isBranching) {
+ var fn = OPERATORS[op];
return extend(function $parseBinaryFn(self, locals) {
return fn(self, locals, left, right);
}, {
constant: left.constant && right.constant,
inputs: !isBranching && [left, right]
+ });
+ },
+
+ identifier: function() {
+ var id = this.consume().text;
+
+ //Continue reading each `.identifier` unless it is a method invocation
+ while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) {
+ id += this.consume().text + this.consume().text;
+ }
+
+ return CONSTANTS[id] || getterFn(id, this.options, this.text);
+ },
+
+ constant: function() {
+ var value = this.consume().value;
+
+ return extend(function $parseConstant() {
+ return value;
+ }, {
+ constant: true,
+ literal: true
});
},
@@ -11911,8 +12081,7 @@
},
filter: function(inputFn) {
- var token = this.expect();
- var fn = this.$filter(token.text);
+ var fn = this.$filter(this.consume().text);
var argsFn;
var args;
@@ -11975,17 +12144,14 @@
var token;
if ((token = this.expect('?'))) {
middle = this.assignment();
- if ((token = this.expect(':'))) {
+ if (this.consume(':')) {
var right = this.assignment();
- return extend(function $parseTernary(self, locals){
+ return extend(function $parseTernary(self, locals) {
return left(self, locals) ? middle(self, locals) : right(self, locals);
}, {
constant: left.constant && middle.constant && right.constant
});
-
- } else {
- this.throwError('expected :', token);
}
}
@@ -11996,7 +12162,7 @@
var left = this.logicalAND();
var token;
while ((token = this.expect('||'))) {
- left = this.binaryFn(left, token.fn, this.logicalAND(), true);
+ left = this.binaryFn(left, token.text, this.logicalAND(), true);
}
return left;
},
@@ -12004,8 +12170,8 @@
logicalAND: function() {
var left = this.equality();
var token;
- if ((token = this.expect('&&'))) {
- left = this.binaryFn(left, token.fn, this.logicalAND(), true);
+ while ((token = this.expect('&&'))) {
+ left = this.binaryFn(left, token.text, this.equality(), true);
}
return left;
},
@@ -12013,8 +12179,8 @@
equality: function() {
var left = this.relational();
var token;
- if ((token = this.expect('==','!=','===','!=='))) {
- left = this.binaryFn(left, token.fn, this.equality());
+ while ((token = this.expect('==','!=','===','!=='))) {
+ left = this.binaryFn(left, token.text, this.relational());
}
return left;
},
@@ -12022,8 +12188,8 @@
relational: function() {
var left = this.additive();
var token;
- if ((token = this.expect('<', '>', '<=', '>='))) {
- left = this.binaryFn(left, token.fn, this.relational());
+ while ((token = this.expect('<', '>', '<=', '>='))) {
+ left = this.binaryFn(left, token.text, this.additive());
}
return left;
},
@@ -12032,7 +12198,7 @@
var left = this.multiplicative();
var token;
while ((token = this.expect('+','-'))) {
- left = this.binaryFn(left, token.fn, this.multiplicative());
+ left = this.binaryFn(left, token.text, this.multiplicative());
}
return left;
},
@@ -12041,7 +12207,7 @@
var left = this.unary();
var token;
while ((token = this.expect('*','/','%'))) {
- left = this.binaryFn(left, token.fn, this.unary());
+ left = this.binaryFn(left, token.text, this.unary());
}
return left;
},
@@ -12051,9 +12217,9 @@
if (this.expect('+')) {
return this.primary();
} else if ((token = this.expect('-'))) {
- return this.binaryFn(Parser.ZERO, token.fn, this.unary());
+ return this.binaryFn(Parser.ZERO, token.text, this.unary());
} else if ((token = this.expect('!'))) {
- return this.unaryFn(token.fn, this.unary());
+ return this.unaryFn(token.text, this.unary());
} else {
return this.primary();
}
@@ -12061,7 +12227,7 @@
fieldAccess: function(object) {
var expression = this.text;
- var field = this.expect().text;
+ var field = this.consume().text;
var getter = getterFn(field, this.options, expression);
return extend(function $parseFieldAccess(scope, locals, self) {
@@ -12115,7 +12281,7 @@
var args = argsFn.length ? [] : null;
return function $parseFunctionCall(scope, locals) {
- var context = contextGetter ? contextGetter(scope, locals) : scope;
+ var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
var fn = fnGetter(scope, locals, context) || noop;
if (args) {
@@ -12128,17 +12294,17 @@
ensureSafeObject(context, expressionText);
ensureSafeFunction(fn, expressionText);
- // IE stupidity! (IE doesn't have apply for some native functions)
+ // IE doesn't have apply for some native functions
var v = fn.apply
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
return ensureSafeObject(v, expressionText);
- };
+ };
},
// This is used with json array declaration
- arrayDeclaration: function () {
+ arrayDeclaration: function() {
var elementFns = [];
if (this.peekToken().text !== ']') {
do {
@@ -12146,8 +12312,7 @@
// Support trailing commas per ES5.1.
break;
}
- var elementFn = this.expression();
- elementFns.push(elementFn);
+ elementFns.push(this.expression());
} while (this.expect(','));
}
this.consume(']');
@@ -12165,7 +12330,7 @@
});
},
- object: function () {
+ object: function() {
var keys = [], valueFns = [];
if (this.peekToken().text !== '}') {
do {
@@ -12173,11 +12338,16 @@
// Support trailing commas per ES5.1.
break;
}
- var token = this.expect();
- keys.push(token.string || token.text);
+ var token = this.consume();
+ if (token.constant) {
+ keys.push(token.value);
+ } else if (token.identifier) {
+ keys.push(token.text);
+ } else {
+ this.throwError("invalid key", token);
+ }
this.consume(':');
- var value = this.expression();
- valueFns.push(value);
+ valueFns.push(this.expression());
} while (this.expect(','));
}
this.consume('}');
@@ -12220,50 +12390,71 @@
return setValue;
}
-var getterFnCache = createMap();
+var getterFnCacheDefault = createMap();
+var getterFnCacheExpensive = createMap();
+
+function isPossiblyDangerousMemberName(name) {
+ return name == 'constructor';
+}
/**
* Implementation of the "Black Hole" variant from:
* - http://jsperf.com/angularjs-parse-getter/4
* - http://jsperf.com/path-evaluation-simplified/7
*/
-function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) {
+function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) {
ensureSafeMemberName(key0, fullExp);
ensureSafeMemberName(key1, fullExp);
ensureSafeMemberName(key2, fullExp);
ensureSafeMemberName(key3, fullExp);
ensureSafeMemberName(key4, fullExp);
+ var eso = function(o) {
+ return ensureSafeObject(o, fullExp);
+ };
+ var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity;
+ var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity;
+ var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity;
+ var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity;
+ var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity;
return function cspSafeGetter(scope, locals) {
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
if (pathVal == null) return pathVal;
- pathVal = pathVal[key0];
+ pathVal = eso0(pathVal[key0]);
if (!key1) return pathVal;
if (pathVal == null) return undefined;
- pathVal = pathVal[key1];
+ pathVal = eso1(pathVal[key1]);
if (!key2) return pathVal;
if (pathVal == null) return undefined;
- pathVal = pathVal[key2];
+ pathVal = eso2(pathVal[key2]);
if (!key3) return pathVal;
if (pathVal == null) return undefined;
- pathVal = pathVal[key3];
+ pathVal = eso3(pathVal[key3]);
if (!key4) return pathVal;
if (pathVal == null) return undefined;
- pathVal = pathVal[key4];
+ pathVal = eso4(pathVal[key4]);
return pathVal;
};
}
-function getterFn(path, options, fullExp) {
- var fn = getterFnCache[path];
+function getterFnWithEnsureSafeObject(fn, fullExpression) {
+ return function(s, l) {
+ return fn(s, l, ensureSafeObject, fullExpression);
+ };
+}
+function getterFn(path, options, fullExp) {
+ var expensiveChecks = options.expensiveChecks;
+ var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
+ var fn = getterFnCache[path];
if (fn) return fn;
+
var pathKeys = path.split('.'),
pathKeysLength = pathKeys.length;
@@ -12271,13 +12462,13 @@
// http://jsperf.com/angularjs-parse-getter/6
if (options.csp) {
if (pathKeysLength < 6) {
- fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp);
+ fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks);
} else {
fn = function cspSafeGetter(scope, locals) {
var i = 0, val;
do {
val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++],
- pathKeys[i++], fullExp)(scope, locals);
+ pathKeys[i++], fullExp, expensiveChecks)(scope, locals);
locals = undefined; // clear after first iteration
scope = val;
@@ -12287,22 +12478,33 @@
}
} else {
var code = '';
+ if (expensiveChecks) {
+ code += 's = eso(s, fe);\nl = eso(l, fe);\n';
+ }
+ var needsEnsureSafeObject = expensiveChecks;
forEach(pathKeys, function(key, index) {
ensureSafeMemberName(key, fullExp);
- code += 'if(s == null) return undefined;\n' +
- 's='+ (index
+ var lookupJs = (index
// we simply dereference 's' on any .dot notation
? 's'
// but if we are first then we check locals first, and if so read it first
- : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n';
+ : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
+ if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
+ lookupJs = 'eso(' + lookupJs + ', fe)';
+ needsEnsureSafeObject = true;
+ }
+ code += 'if(s == null) return undefined;\n' +
+ 's=' + lookupJs + ';\n';
});
code += 'return s;';
/* jshint -W054 */
- var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals
+ var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
/* jshint +W054 */
evaledFnGetter.toString = valueFn(code);
-
+ if (needsEnsureSafeObject) {
+ evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp);
+ }
fn = evaledFnGetter;
}
@@ -12312,6 +12514,12 @@
};
getterFnCache[path] = fn;
return fn;
+}
+
+var objectValueOf = Object.prototype.valueOf;
+
+function getValueOf(value) {
+ return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
}
///////////////////////////////////
@@ -12366,15 +12574,20 @@
* service.
*/
function $ParseProvider() {
- var cache = createMap();
+ var cacheDefault = createMap();
+ var cacheExpensive = createMap();
- var $parseOptions = {
- csp: false
- };
this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
- $parseOptions.csp = $sniffer.csp;
+ var $parseOptions = {
+ csp: $sniffer.csp,
+ expensiveChecks: false
+ },
+ $parseOptionsExpensive = {
+ csp: $sniffer.csp,
+ expensiveChecks: true
+ };
function wrapSharedExpression(exp) {
var wrapped = exp;
@@ -12391,13 +12604,14 @@
return wrapped;
}
- return function $parse(exp, interceptorFn) {
+ return function $parse(exp, interceptorFn, expensiveChecks) {
var parsedExpression, oneTime, cacheKey;
switch (typeof exp) {
case 'string':
cacheKey = exp = exp.trim();
+ var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
parsedExpression = cache[cacheKey];
if (!parsedExpression) {
@@ -12406,8 +12620,9 @@
exp = exp.substring(2);
}
- var lexer = new Lexer($parseOptions);
- var parser = new Parser(lexer, $filter, $parseOptions);
+ var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
+ var lexer = new Lexer(parseOptions);
+ var parser = new Parser(lexer, $filter, parseOptions);
parsedExpression = parser.parse(exp);
if (parsedExpression.constant) {
@@ -12460,7 +12675,7 @@
// attempt to convert the value to a primitive type
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
// be cheaply dirty-checked
- newValue = newValue.valueOf();
+ newValue = getValueOf(newValue);
if (typeof newValue === 'object') {
// objects/arrays are not supported - deep-watching them would be too expensive
@@ -12487,7 +12702,7 @@
var newInputValue = inputExpressions(scope);
if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) {
lastResult = parsedExpression(scope);
- oldInputValue = newInputValue && newInputValue.valueOf();
+ oldInputValue = newInputValue && getValueOf(newInputValue);
}
return lastResult;
}, listener, objectEquality);
@@ -12504,7 +12719,7 @@
for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
var newInputValue = inputExpressions[i](scope);
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
- oldInputValueOfValues[i] = newInputValue && newInputValue.valueOf();
+ oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
}
}
@@ -12526,7 +12741,7 @@
listener.apply(this, arguments);
}
if (isDefined(value)) {
- scope.$$postDigest(function () {
+ scope.$$postDigest(function() {
if (isDefined(lastValue)) {
unwatch();
}
@@ -12545,15 +12760,15 @@
listener.call(this, value, old, scope);
}
if (isAllDefined(value)) {
- scope.$$postDigest(function () {
- if(isAllDefined(lastValue)) unwatch();
+ scope.$$postDigest(function() {
+ if (isAllDefined(lastValue)) unwatch();
});
}
}, objectEquality);
function isAllDefined(value) {
var allDefined = true;
- forEach(value, function (val) {
+ forEach(value, function(val) {
if (!isDefined(val)) allDefined = false;
});
return allDefined;
@@ -12574,8 +12789,16 @@
function addInterceptor(parsedExpression, interceptorFn) {
if (!interceptorFn) return parsedExpression;
+ var watchDelegate = parsedExpression.$$watchDelegate;
- var fn = function interceptedExpression(scope, locals) {
+ var regularWatch =
+ watchDelegate !== oneTimeLiteralWatchDelegate &&
+ watchDelegate !== oneTimeWatchDelegate;
+
+ var fn = regularWatch ? function regularInterceptedExpression(scope, locals) {
+ var value = parsedExpression(scope, locals);
+ return interceptorFn(value, scope, locals);
+ } : function oneTimeInterceptedExpression(scope, locals) {
var value = parsedExpression(scope, locals);
var result = interceptorFn(value, scope, locals);
// we only return the interceptor's result if the
@@ -12605,7 +12828,11 @@
* @requires $rootScope
*
* @description
- * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
+ * A service that helps you run functions asynchronously, and use their return values (or exceptions)
+ * when they are done processing.
+ *
+ * This is an implementation of promises/deferred objects inspired by
+ * [Kris Kowal's Q](https://github.com/kriskowal/q).
*
* $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
* implementations, and the other which resembles ES6 promises to some degree.
@@ -12741,15 +12968,11 @@
*
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
*
- * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise,
+ * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
* but to do so without modifying the final value. This is useful to release resources or do some
* clean-up that needs to be done whether the promise was rejected or resolved. See the [full
* specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
* more information.
- *
- * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as
- * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to
- * make your code IE8 and Android 2.x compatible.
*
* # Chaining promises
*
@@ -12917,7 +13140,7 @@
} else {
promise.reject(state.value);
}
- } catch(e) {
+ } catch (e) {
promise.reject(e);
exceptionHandler(e);
}
@@ -12967,7 +13190,7 @@
this.promise.$$state.status = 1;
scheduleProcessQueue(this.promise.$$state);
}
- } catch(e) {
+ } catch (e) {
fns[1](e);
exceptionHandler(e);
}
@@ -12995,7 +13218,7 @@
callback = callbacks[i][3];
try {
result.notify(isFunction(callback) ? callback(progress) : progress);
- } catch(e) {
+ } catch (e) {
exceptionHandler(e);
}
}
@@ -13060,7 +13283,7 @@
var callbackOutput = null;
try {
if (isFunction(callback)) callbackOutput = callback();
- } catch(e) {
+ } catch (e) {
return makePromise(e, false);
}
if (isPromiseLike(callbackOutput)) {
@@ -13168,7 +13391,7 @@
return $Q;
}
-function $$RAFProvider(){ //rAF
+function $$RAFProvider() { //rAF
this.$get = ['$window', '$timeout', function($window, $timeout) {
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
@@ -13267,7 +13490,7 @@
* They also provide an event emission/broadcast and subscription facility. See the
* {@link guide/scope developer guide on scopes}.
*/
-function $RootScopeProvider(){
+function $RootScopeProvider() {
var TTL = 10;
var $rootScopeMinErr = minErr('$rootScope');
var lastDirtyWatch = null;
@@ -13281,7 +13504,7 @@
};
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
- function( $injector, $exceptionHandler, $parse, $browser) {
+ function($injector, $exceptionHandler, $parse, $browser) {
/**
* @ngdoc type
@@ -13312,6 +13535,10 @@
expect(child.salutation).toEqual('Welcome');
expect(parent.salutation).toEqual('Hello');
* ```
+ *
+ * When interacting with `Scope` in tests, additional helper methods are available on the
+ * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
+ * details.
*
*
* @param {Object.<string, function()>=} providers Map of service factory which need to be
@@ -13624,7 +13851,7 @@
if (!watchExpressions.length) {
// No expressions means we call the listener ASAP
var shouldCall = true;
- self.$evalAsync(function () {
+ self.$evalAsync(function() {
if (shouldCall) listener(newValues, newValues, self);
});
return function deregisterWatchGroup() {
@@ -13641,7 +13868,7 @@
});
}
- forEach(watchExpressions, function (expr, i) {
+ forEach(watchExpressions, function(expr, i) {
var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
newValues[i] = value;
oldValues[i] = oldValue;
@@ -13751,6 +13978,9 @@
newValue = _value;
var newLength, key, bothNaN, newItem, oldItem;
+ // If the new value is undefined, then return undefined as the watch may be a one-time watch
+ if (isUndefined(newValue)) return;
+
if (!isObject(newValue)) { // if primitive
if (oldValue !== newValue) {
oldValue = newValue;
@@ -13813,7 +14043,7 @@
if (oldLength > newLength) {
// we used to have more keys, need to find them and destroy them.
changeDetected++;
- for(key in oldValue) {
+ for (key in oldValue) {
if (!newValue.hasOwnProperty(key)) {
oldLength--;
delete oldValue[key];
@@ -13933,7 +14163,7 @@
dirty = false;
current = target;
- while(asyncQueue.length) {
+ while (asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
@@ -13966,11 +14196,11 @@
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
- logMsg = (isFunction(watch.exp))
- ? 'fn: ' + (watch.exp.name || watch.exp.toString())
- : watch.exp;
- logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
- watchLog[logIdx].push(logMsg);
+ watchLog[logIdx].push({
+ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
+ newVal: value,
+ oldVal: last
+ });
}
} else if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
@@ -13990,7 +14220,7 @@
// this piece should be kept in sync with the traversal in $broadcast
if (!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
- while(current !== target && !(next = current.$$nextSibling)) {
+ while (current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
@@ -13998,19 +14228,19 @@
// `break traverseScopesLoop;` takes us to here
- if((dirty || asyncQueue.length) && !(ttl--)) {
+ if ((dirty || asyncQueue.length) && !(ttl--)) {
clearPhase();
throw $rootScopeMinErr('infdig',
'{0} $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: {1}',
- TTL, toJson(watchLog));
+ TTL, watchLog);
}
} while (dirty || asyncQueue.length);
clearPhase();
- while(postDigestQueue.length) {
+ while (postDigestQueue.length) {
try {
postDigestQueue.shift()();
} catch (e) {
@@ -14166,7 +14396,7 @@
asyncQueue.push({scope: this, expression: expr});
},
- $$postDigest : function(fn) {
+ $$postDigest: function(fn) {
postDigestQueue.push(fn);
},
@@ -14303,8 +14533,11 @@
var self = this;
return function() {
- namedListeners[namedListeners.indexOf(listener)] = null;
- decrementListenerCount(self, 1, name);
+ var indexOfListener = namedListeners.indexOf(listener);
+ if (indexOfListener !== -1) {
+ namedListeners[indexOfListener] = null;
+ decrementListenerCount(self, 1, name);
+ }
};
},
@@ -14351,7 +14584,7 @@
do {
namedListeners = scope.$$listeners[name] || empty;
event.currentScope = scope;
- for (i=0, length=namedListeners.length; i<length; i++) {
+ for (i = 0, length = namedListeners.length; i < length; i++) {
// if listeners were deregistered, defragment the array
if (!namedListeners[i]) {
@@ -14425,7 +14658,7 @@
while ((current = next)) {
event.currentScope = current;
listeners = current.$$listeners[name] || [];
- for (i=0, length = listeners.length; i<length; i++) {
+ for (i = 0, length = listeners.length; i < length; i++) {
// if listeners were deregistered, defragment the array
if (!listeners[i]) {
listeners.splice(i, 1);
@@ -14436,7 +14669,7 @@
try {
listeners[i].apply(null, listenerArgs);
- } catch(e) {
+ } catch (e) {
$exceptionHandler(e);
}
}
@@ -14447,7 +14680,7 @@
// (though it differs due to having the extra check for $$listenerCount)
if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
- while(current !== target && !(next = current.$$nextSibling)) {
+ while (current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
@@ -14501,7 +14734,7 @@
while (applyAsyncQueue.length) {
try {
applyAsyncQueue.shift()();
- } catch(e) {
+ } catch (e) {
$exceptionHandler(e);
}
}
@@ -14581,7 +14814,7 @@
var normalizedVal;
normalizedVal = urlResolve(uri).href;
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
- return 'unsafe:'+normalizedVal;
+ return 'unsafe:' + normalizedVal;
}
return uri;
};
@@ -14601,15 +14834,6 @@
};
// Helper functions follow.
-
-// Copied from:
-// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962
-// Prereq: s is a string.
-function escapeForRegexp(s) {
- return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
- replace(/\x08/g, '\\x08');
-}
-
function adjustMatcher(matcher) {
if (matcher === 'self') {
@@ -14746,7 +14970,7 @@
* @description
* Sets/Gets the whitelist of trusted resource URLs.
*/
- this.resourceUrlWhitelist = function (value) {
+ this.resourceUrlWhitelist = function(value) {
if (arguments.length) {
resourceUrlWhitelist = adjustMatchers(value);
}
@@ -14780,7 +15004,7 @@
* Sets/Gets the blacklist of trusted resource URLs.
*/
- this.resourceUrlBlacklist = function (value) {
+ this.resourceUrlBlacklist = function(value) {
if (arguments.length) {
resourceUrlBlacklist = adjustMatchers(value);
}
@@ -15261,7 +15485,7 @@
* @description
* Enables/disables SCE and returns the current value.
*/
- this.enabled = function (value) {
+ this.enabled = function(value) {
if (arguments.length) {
enabled = !!value;
}
@@ -15315,11 +15539,11 @@
* sce.js and sceSpecs.js would need to be aware of this detail.
*/
- this.$get = ['$document', '$parse', '$sceDelegate', function(
- $document, $parse, $sceDelegate) {
+ this.$get = ['$parse', '$sceDelegate', function(
+ $parse, $sceDelegate) {
// Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
// the "expression(javascript expression)" syntax which is insecure.
- if (enabled && $document[0].documentMode < 8) {
+ if (enabled && msie < 8) {
throw $sceMinErr('iequirks',
'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
@@ -15339,7 +15563,7 @@
* @description
* Returns a boolean indicating if SCE is enabled.
*/
- sce.isEnabled = function () {
+ sce.isEnabled = function() {
return enabled;
};
sce.trustAs = $sceDelegate.trustAs;
@@ -15375,7 +15599,7 @@
if (parsed.literal && parsed.constant) {
return parsed;
} else {
- return $parse(expr, function (value) {
+ return $parse(expr, function(value) {
return sce.getTrusted(type, value);
});
}
@@ -15628,15 +15852,15 @@
getTrusted = sce.getTrusted,
trustAs = sce.trustAs;
- forEach(SCE_CONTEXTS, function (enumValue, name) {
+ forEach(SCE_CONTEXTS, function(enumValue, name) {
var lName = lowercase(name);
- sce[camelCase("parse_as_" + lName)] = function (expr) {
+ sce[camelCase("parse_as_" + lName)] = function(expr) {
return parse(enumValue, expr);
};
- sce[camelCase("get_trusted_" + lName)] = function (value) {
+ sce[camelCase("get_trusted_" + lName)] = function(value) {
return getTrusted(enumValue, value);
};
- sce[camelCase("trust_as_" + lName)] = function (value) {
+ sce[camelCase("trust_as_" + lName)] = function(value) {
return trustAs(enumValue, value);
};
});
@@ -15667,29 +15891,29 @@
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
document = $document[0] || {},
vendorPrefix,
- vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
+ vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
bodyStyle = document.body && document.body.style,
transitions = false,
animations = false,
match;
if (bodyStyle) {
- for(var prop in bodyStyle) {
- if(match = vendorRegex.exec(prop)) {
+ for (var prop in bodyStyle) {
+ if (match = vendorRegex.exec(prop)) {
vendorPrefix = match[0];
vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
break;
}
}
- if(!vendorPrefix) {
+ if (!vendorPrefix) {
vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
}
transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
- if (android && (!transitions||!animations)) {
+ if (android && (!transitions || !animations)) {
transitions = isString(document.body.style.webkitTransition);
animations = isString(document.body.style.webkitAnimation);
}
@@ -15712,7 +15936,9 @@
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
// when cut operation is performed.
- if (event == 'input' && msie == 9) return false;
+ // IE10+ implements 'input' event but it erroneously fires under various situations,
+ // e.g. when placeholder changes, or a form is focused.
+ if (event === 'input' && msie <= 11) return false;
if (isUndefined(eventSupport[event])) {
var divElm = document.createElement('div');
@@ -15723,8 +15949,8 @@
},
csp: csp(),
vendorPrefix: vendorPrefix,
- transitions : transitions,
- animations : animations,
+ transitions: transitions,
+ animations: animations,
android: android
};
}];
@@ -15755,24 +15981,33 @@
var self = handleRequestFn;
self.totalPendingRequests++;
- return $http.get(tpl, { cache : $templateCache })
- .then(function(response) {
- var html = response.data;
- if(!html || html.length === 0) {
- return handleError();
- }
+ var transformResponse = $http.defaults && $http.defaults.transformResponse;
+ if (isArray(transformResponse)) {
+ transformResponse = transformResponse.filter(function(transformer) {
+ return transformer !== defaultHttpResponseTransform;
+ });
+ } else if (transformResponse === defaultHttpResponseTransform) {
+ transformResponse = null;
+ }
+
+ var httpOptions = {
+ cache: $templateCache,
+ transformResponse: transformResponse
+ };
+
+ return $http.get(tpl, httpOptions)
+ .then(function(response) {
self.totalPendingRequests--;
- $templateCache.put(tpl, html);
- return html;
+ return response.data;
}, handleError);
- function handleError() {
+ function handleError(resp) {
self.totalPendingRequests--;
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
}
- return $q.reject();
+ return $q.reject(resp);
}
}
@@ -15815,7 +16050,7 @@
if (dataBinding) {
forEach(dataBinding, function(bindingName) {
if (opt_exactMatch) {
- var matcher = new RegExp('(^|\\s)' + expression + '(\\s|\\||$)');
+ var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
if (matcher.test(bindingName)) {
matches.push(binding);
}
@@ -15937,7 +16172,7 @@
timeoutId = $browser.defer(function() {
try {
deferred.resolve(fn());
- } catch(e) {
+ } catch (e) {
deferred.reject(e);
$exceptionHandler(e);
}
@@ -15988,7 +16223,7 @@
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = document.createElement("a");
-var originUrl = urlResolve(window.location.href, true);
+var originUrl = urlResolve(window.location.href);
/**
@@ -16043,7 +16278,7 @@
* | pathname | The pathname, beginning with "/"
*
*/
-function urlResolve(url, base) {
+function urlResolve(url) {
var href = url;
if (msie) {
@@ -16103,7 +16338,7 @@
<file name="index.html">
<script>
angular.module('windowExample', [])
- .controller('ExampleController', ['$scope', '$window', function ($scope, $window) {
+ .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
$scope.greeting = 'Hello, World!';
$scope.doGreeting = function(greeting) {
$window.alert(greeting);
@@ -16124,7 +16359,7 @@
</file>
</example>
*/
-function $WindowProvider(){
+function $WindowProvider() {
this.$get = valueFn(window);
}
@@ -16233,7 +16468,7 @@
* of the registered filter instances.
*/
function register(name, factory) {
- if(isObject(name)) {
+ if (isObject(name)) {
var filters = {};
forEach(name, function(filter, key) {
filters[key] = register(key, filter);
@@ -16395,106 +16630,103 @@
return function(array, expression, comparator) {
if (!isArray(array)) return array;
- var comparatorType = typeof(comparator),
- predicates = [];
+ var predicateFn;
+ var matchAgainstAnyProp;
- predicates.check = function(value, index) {
- for (var j = 0; j < predicates.length; j++) {
- if(!predicates[j](value, index)) {
- return false;
- }
- }
- return true;
- };
-
- if (comparatorType !== 'function') {
- if (comparatorType === 'boolean' && comparator) {
- comparator = function(obj, text) {
- return angular.equals(obj, text);
- };
- } else {
- comparator = function(obj, text) {
- if (obj && text && typeof obj === 'object' && typeof text === 'object') {
- for (var objKey in obj) {
- if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
- comparator(obj[objKey], text[objKey])) {
- return true;
- }
- }
- return false;
- }
- text = (''+text).toLowerCase();
- return (''+obj).toLowerCase().indexOf(text) > -1;
- };
- }
- }
-
- var search = function(obj, text){
- if (typeof text === 'string' && text.charAt(0) === '!') {
- return !search(obj, text.substr(1));
- }
- switch (typeof obj) {
- case 'boolean':
- case 'number':
- case 'string':
- return comparator(obj, text);
- case 'object':
- switch (typeof text) {
- case 'object':
- return comparator(obj, text);
- default:
- for ( var objKey in obj) {
- if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
- return true;
- }
- }
- break;
- }
- return false;
- case 'array':
- for ( var i = 0; i < obj.length; i++) {
- if (search(obj[i], text)) {
- return true;
- }
- }
- return false;
- default:
- return false;
- }
- };
switch (typeof expression) {
+ case 'function':
+ predicateFn = expression;
+ break;
case 'boolean':
case 'number':
case 'string':
- // Set up expression object and fall through
- expression = {$:expression};
- // jshint -W086
+ matchAgainstAnyProp = true;
+ //jshint -W086
case 'object':
- // jshint +W086
- for (var key in expression) {
- (function(path) {
- if (typeof expression[path] === 'undefined') return;
- predicates.push(function(value) {
- return search(path == '$' ? value : (value && value[path]), expression[path]);
- });
- })(key);
- }
- break;
- case 'function':
- predicates.push(expression);
+ //jshint +W086
+ predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
- var filtered = [];
- for ( var j = 0; j < array.length; j++) {
- var value = array[j];
- if (predicates.check(value, j)) {
- filtered.push(value);
- }
- }
- return filtered;
+
+ return array.filter(predicateFn);
};
+}
+
+// Helper functions for `filterFilter`
+function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
+ var predicateFn;
+
+ if (comparator === true) {
+ comparator = equals;
+ } else if (!isFunction(comparator)) {
+ comparator = function(actual, expected) {
+ if (isObject(actual) || isObject(expected)) {
+ // Prevent an object to be considered equal to a string like `'[object'`
+ return false;
+ }
+
+ actual = lowercase('' + actual);
+ expected = lowercase('' + expected);
+ return actual.indexOf(expected) !== -1;
+ };
+ }
+
+ predicateFn = function(item) {
+ return deepCompare(item, expression, comparator, matchAgainstAnyProp);
+ };
+
+ return predicateFn;
+}
+
+function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
+ var actualType = typeof actual;
+ var expectedType = typeof expected;
+
+ if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
+ return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
+ } else if (actualType === 'array') {
+ // In case `actual` is an array, consider it a match
+ // if ANY of it's items matches `expected`
+ return actual.some(function(item) {
+ return deepCompare(item, expected, comparator, matchAgainstAnyProp);
+ });
+ }
+
+ switch (actualType) {
+ case 'object':
+ var key;
+ if (matchAgainstAnyProp) {
+ for (key in actual) {
+ if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
+ return true;
+ }
+ }
+ return false;
+ } else if (expectedType === 'object') {
+ for (key in expected) {
+ var expectedVal = expected[key];
+ if (isFunction(expectedVal)) {
+ continue;
+ }
+
+ var keyIsDollar = key === '$';
+ var actualVal = keyIsDollar ? actual : actual[key];
+ if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return comparator(actual, expected);
+ }
+ break;
+ case 'function':
+ return false;
+ default:
+ return comparator(actual, expected);
+ }
}
/**
@@ -16508,7 +16740,7 @@
*
* @param {number} amount Input to filter.
* @param {string=} symbol Currency symbol or identifier to be displayed.
- * @param {number=} fractionSize Number of decimal places to round the amount to.
+ * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
* @returns {string} Formatted number.
*
*
@@ -16552,14 +16784,13 @@
currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
- return function(amount, currencySymbol, fractionSize){
+ return function(amount, currencySymbol, fractionSize) {
if (isUndefined(currencySymbol)) {
currencySymbol = formats.CURRENCY_SYM;
}
if (isUndefined(fractionSize)) {
- // TODO: read the default value from the locale file
- fractionSize = 2;
+ fractionSize = formats.PATTERNS[1].maxFrac;
}
// if null or undefined pass it through
@@ -16648,7 +16879,6 @@
if (numStr.indexOf('e') !== -1) {
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
- numStr = '0';
number = 0;
} else {
formatedText = numStr;
@@ -16669,10 +16899,6 @@
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
- if (number === 0) {
- isNegative = false;
- }
-
var fraction = ('' + number).split(DECIMAL_SEP);
var whole = fraction[0];
fraction = fraction[1] || '';
@@ -16684,7 +16910,7 @@
if (whole.length >= (lgroup + group)) {
pos = whole.length - lgroup;
for (i = 0; i < pos; i++) {
- if ((pos - i)%group === 0 && i !== 0) {
+ if ((pos - i) % group === 0 && i !== 0) {
formatedText += groupSep;
}
formatedText += whole.charAt(i);
@@ -16692,28 +16918,32 @@
}
for (i = pos; i < whole.length; i++) {
- if ((whole.length - i)%lgroup === 0 && i !== 0) {
+ if ((whole.length - i) % lgroup === 0 && i !== 0) {
formatedText += groupSep;
}
formatedText += whole.charAt(i);
}
// format fraction part.
- while(fraction.length < fractionSize) {
+ while (fraction.length < fractionSize) {
fraction += '0';
}
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
} else {
-
- if (fractionSize > 0 && number > -1 && number < 1) {
+ if (fractionSize > 0 && number < 1) {
formatedText = number.toFixed(fractionSize);
+ number = parseFloat(formatedText);
}
}
- parts.push(isNegative ? pattern.negPre : pattern.posPre);
- parts.push(formatedText);
- parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
+ if (number === 0) {
+ isNegative = false;
+ }
+
+ parts.push(isNegative ? pattern.negPre : pattern.posPre,
+ formatedText,
+ isNegative ? pattern.negSuf : pattern.posSuf);
return parts.join('');
}
@@ -16724,7 +16954,7 @@
num = -num;
}
num = '' + num;
- while(num.length < digits) num = '0' + num;
+ while (num.length < digits) num = '0' + num;
if (trim)
num = num.substr(num.length - digits);
return neg + num;
@@ -16737,7 +16967,7 @@
var value = date['get' + name]();
if (offset > 0 || value > -offset)
value += offset;
- if (value === 0 && offset == -12 ) value = 12;
+ if (value === 0 && offset == -12) value = 12;
return padNumber(value, size, trim);
};
}
@@ -16932,10 +17162,10 @@
tzMin = int(match[9] + match[11]);
}
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
- var h = int(match[4]||0) - tzHour;
- var m = int(match[5]||0) - tzMin;
- var s = int(match[6]||0);
- var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000);
+ var h = int(match[4] || 0) - tzHour;
+ var m = int(match[5] || 0) - tzMin;
+ var s = int(match[6] || 0);
+ var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
}
@@ -16962,7 +17192,7 @@
return date;
}
- while(format) {
+ while (format) {
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = concat(parts, match, 1);
@@ -16977,7 +17207,7 @@
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
}
- forEach(parts, function(value){
+ forEach(parts, function(value) {
fn = DATE_FORMATS[value];
text += fn ? fn(date, $locale.DATETIME_FORMATS)
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
@@ -17000,25 +17230,31 @@
* the binding is automatically converted to JSON.
*
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
+ * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
* @returns {string} JSON string.
*
*
* @example
<example>
<file name="index.html">
- <pre>{{ {'name':'value'} | json }}</pre>
+ <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
+ <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
</file>
<file name="protractor.js" type="protractor">
it('should jsonify filtered objects', function() {
- expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
+ expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
+ expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
});
</file>
</example>
*
*/
function jsonFilter() {
- return function(object) {
- return toJson(object, true);
+ return function(object, spacing) {
+ if (isUndefined(spacing)) {
+ spacing = 2;
+ }
+ return toJson(object, spacing);
};
}
@@ -17130,7 +17366,7 @@
</file>
</example>
*/
-function limitToFilter(){
+function limitToFilter() {
return function(input, limit) {
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
@@ -17167,7 +17403,7 @@
n = input.length;
}
- for (; i<n; i++) {
+ for (; i < n; i++) {
out.push(input[i]);
}
@@ -17291,42 +17527,40 @@
</example>
*/
orderByFilter.$inject = ['$parse'];
-function orderByFilter($parse){
+function orderByFilter($parse) {
return function(array, sortPredicate, reverseOrder) {
if (!(isArrayLike(array))) return array;
- sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
+ sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate];
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
- sortPredicate = sortPredicate.map(function(predicate){
+ sortPredicate = sortPredicate.map(function(predicate) {
var descending = false, get = predicate || identity;
if (isString(predicate)) {
if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
descending = predicate.charAt(0) == '-';
predicate = predicate.substring(1);
}
- if ( predicate === '' ) {
+ if (predicate === '') {
// Effectively no predicate was passed so we compare identity
- return reverseComparator(function(a,b) {
+ return reverseComparator(function(a, b) {
return compare(a, b);
}, descending);
}
get = $parse(predicate);
if (get.constant) {
var key = get();
- return reverseComparator(function(a,b) {
+ return reverseComparator(function(a, b) {
return compare(a[key], b[key]);
}, descending);
}
}
- return reverseComparator(function(a,b){
+ return reverseComparator(function(a, b) {
return compare(get(a),get(b));
}, descending);
});
- var arrayCopy = [];
- for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
- return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
+ return slice.call(array).sort(reverseComparator(comparator, reverseOrder));
- function comparator(o1, o2){
- for ( var i = 0; i < sortPredicate.length; i++) {
+ function comparator(o1, o2) {
+ for (var i = 0; i < sortPredicate.length; i++) {
var comp = sortPredicate[i](o1, o2);
if (comp !== 0) return comp;
}
@@ -17334,18 +17568,35 @@
}
function reverseComparator(comp, descending) {
return descending
- ? function(a,b){return comp(b,a);}
+ ? function(a, b) {return comp(b,a);}
: comp;
}
- function compare(v1, v2){
+ function compare(v1, v2) {
var t1 = typeof v1;
var t2 = typeof v2;
- if (t1 == t2) {
- if (isDate(v1) && isDate(v2)) {
- v1 = v1.valueOf();
- v2 = v2.valueOf();
+ // Prepare values for Abstract Relational Comparison
+ // (http://www.ecma-international.org/ecma-262/5.1/#sec-11.8.5):
+ // If the resulting values are identical, return 0 to prevent
+ // incorrect re-ordering.
+ if (t1 === t2 && t1 === "object") {
+ // If types are both numbers, emulate abstract ToPrimitive() operation
+ // in order to get primitive values suitable for comparison
+ t1 = typeof (v1.valueOf ? v1 = v1.valueOf() : v1);
+ t2 = typeof (v2.valueOf ? v2 = v2.valueOf() : v2);
+ if (t1 === t2 && t1 === "object") {
+ // Object.prototype.valueOf will return the original object, by
+ // default. If we do not receive a primitive value, use ToString()
+ // instead.
+ t1 = typeof (v1.toString ? v1 = v1.toString() : v1);
+ t2 = typeof (v2.toString ? v2 = v2.toString() : v2);
+
+ // If the end result of toString() for each item is the same, do not
+ // perform relational comparison, and do not re-order objects.
+ if (t1 === t2 && v1 === v2 || t1 === "object") return 0;
}
- if (t1 == "string") {
+ }
+ if (t1 === t2) {
+ if (t1 === "string") {
v1 = v1.toLowerCase();
v2 = v2.toLowerCase();
}
@@ -17389,7 +17640,7 @@
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
'xlink:href' : 'href';
- element.on('click', function(event){
+ element.on('click', function(event) {
// if we have no href url, then don't navigate anywhere.
if (!element.attr(href)) {
event.preventDefault();
@@ -17411,9 +17662,8 @@
* make the link go to the wrong URL if the user clicks it before
* Angular has a chance to replace the `{{hash}}` markup with its
* value. Until Angular replaces the markup the link will be broken
- * and will most likely return a 404 error.
- *
- * The `ngHref` directive solves this problem.
+ * and will most likely return a 404 error. The `ngHref` directive
+ * solves this problem.
*
* The wrong way to write it:
* ```html
@@ -17868,6 +18118,11 @@
* - `pattern`
* - `required`
* - `url`
+ * - `date`
+ * - `datetimelocal`
+ * - `time`
+ * - `week`
+ * - `month`
*
* @description
* `FormController` keeps track of all its controls and nested forms as well as the state of them,
@@ -18056,7 +18311,7 @@
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
* saving or resetting it.
*/
- form.$setPristine = function () {
+ form.$setPristine = function() {
$animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
form.$dirty = false;
form.$pristine = true;
@@ -18079,7 +18334,7 @@
* Setting a form controls back to their untouched state is often useful when setting the form
* back to its pristine state.
*/
- form.$setUntouched = function () {
+ form.$setUntouched = function() {
forEach(controls, function(control) {
control.$setUntouched();
});
@@ -18092,7 +18347,7 @@
* @description
* Sets the form to its submitted state.
*/
- form.$setSubmitted = function () {
+ form.$setSubmitted = function() {
$animate.addClass(element, SUBMITTED_CLASS);
form.$submitted = true;
parentForm.$setSubmitted();
@@ -18290,9 +18545,7 @@
controller.$setSubmitted();
});
- event.preventDefault
- ? event.preventDefault()
- : event.returnValue = false; // IE
+ event.preventDefault();
};
addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
@@ -18369,7 +18622,6 @@
* @description
* Standard HTML text input with angular data binding, inherited by most of the `input` elements.
*
- * *NOTE* Not every feature offered is available for all input types.
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -18380,10 +18632,16 @@
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
- * patterns defined as scope expressions.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
+ * that contains the regular expression body that will be converted to a regular expression
+ * as in the ngPattern directive.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * If the expression evaluates to a RegExp object then this is used directly.
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
@@ -18453,7 +18711,10 @@
* the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
* date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
* modern browsers do not yet support this input type, it is important to provide cues to users on the
- * expected input format via a placeholder or label. The model must always be a Date object.
+ * expected input format via a placeholder or label.
+ *
+ * The model must always be a Date object, otherwise Angular will throw an error.
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
*
* The timezone to be used to read/write the `Date` instance in the model can be defined using
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
@@ -18536,12 +18797,15 @@
/**
* @ngdoc input
- * @name input[dateTimeLocal]
+ * @name input[datetime-local]
*
* @description
* Input with datetime validation and transformation. In browsers that do not yet support
* the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
- * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. The model must be a Date object.
+ * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
+ *
+ * The model must always be a Date object, otherwise Angular will throw an error.
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
*
* The timezone to be used to read/write the `Date` instance in the model can be defined using
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
@@ -18632,6 +18896,9 @@
* local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
* Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
*
+ * The model must always be a Date object, otherwise Angular will throw an error.
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
+ *
* The timezone to be used to read/write the `Date` instance in the model can be defined using
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
*
@@ -18718,7 +18985,10 @@
* @description
* Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
* the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
- * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object.
+ * week format (yyyy-W##), for example: `2013-W02`.
+ *
+ * The model must always be a Date object, otherwise Angular will throw an error.
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
*
* The timezone to be used to read/write the `Date` instance in the model can be defined using
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
@@ -18804,8 +19074,12 @@
* @description
* Input with month validation and transformation. In browsers that do not yet support
* the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
- * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is
- * not set to the first of the month, the first of that model's month is assumed.
+ * month format (yyyy-MM), for example: `2009-01`.
+ *
+ * The model must always be a Date object, otherwise Angular will throw an error.
+ * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
+ * If the model is not set to the first of the month, the next view to model update will set it
+ * to the first of the month.
*
* The timezone to be used to read/write the `Date` instance in the model can be defined using
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
@@ -18894,6 +19168,8 @@
* Text input with number validation and transformation. Sets the `number` validation
* error if not a valid number.
*
+ * The model must always be a number, otherwise Angular will throw an error.
+ *
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
@@ -18905,10 +19181,16 @@
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
- * patterns defined as scope expressions.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
+ * that contains the regular expression body that will be converted to a regular expression
+ * as in the ngPattern directive.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * If the expression evaluates to a RegExp object then this is used directly.
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -18972,6 +19254,12 @@
* Text input with URL validation. Sets the `url` validation error key if the content is not a
* valid URL.
*
+ * <div class="alert alert-warning">
+ * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
+ * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
+ * the built-in validators (see the {@link guide/forms Forms guide})
+ * </div>
+ *
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} required Sets `required` validation error key if the value is not entered.
@@ -18981,10 +19269,16 @@
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
- * patterns defined as scope expressions.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
+ * that contains the regular expression body that will be converted to a regular expression
+ * as in the ngPattern directive.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * If the expression evaluates to a RegExp object then this is used directly.
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -19049,6 +19343,12 @@
* Text input with email validation. Sets the `email` validation error key if not a valid email
* address.
*
+ * <div class="alert alert-warning">
+ * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
+ * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
+ * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
+ * </div>
+ *
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} required Sets `required` validation error key if the value is not entered.
@@ -19058,10 +19358,16 @@
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
- * patterns defined as scope expressions.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
+ * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
+ * that contains the regular expression body that will be converted to a regular expression
+ * as in the ngPattern directive.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
+ * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * If the expression evaluates to a RegExp object then this is used directly.
+ * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
+ * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -19227,19 +19533,6 @@
'file': noop
};
-function testFlags(validity, flags) {
- var i, flag;
- if (flags) {
- for (i=0; i<flags.length; ++i) {
- flag = flags[i];
- if (validity[flag]) {
- return true;
- }
- }
- }
- return false;
-}
-
function stringBasedInputType(ctrl) {
ctrl.$formatters.push(function(value) {
return ctrl.$isEmpty(value) ? value : value.toString();
@@ -19252,8 +19545,6 @@
}
function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
- var validity = element.prop(VALIDITY_STATE_PROPERTY);
- var placeholder = element[0].placeholder, noevent = {};
var type = lowercase(element[0].type);
// In composition mode, users are still inputing intermediate text buffer,
@@ -19273,18 +19564,13 @@
}
var listener = function(ev) {
+ if (timeout) {
+ $browser.defer.cancel(timeout);
+ timeout = null;
+ }
if (composing) return;
var value = element.val(),
event = ev && ev.type;
-
- // IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
- // We don't want to dirty the value when this happens, so we abort here. Unfortunately,
- // IE also sends input events for other non-input-related things, (such as focusing on a
- // form control), so this change is not entirely enough to solve this.
- if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
- placeholder = element[0].placeholder;
- return;
- }
// By default we will trim the value
// If the attribute ng-trim exists we will avoid trimming
@@ -19308,11 +19594,13 @@
} else {
var timeout;
- var deferListener = function(ev) {
+ var deferListener = function(ev, input, origValue) {
if (!timeout) {
timeout = $browser.defer(function() {
- listener(ev);
timeout = null;
+ if (!input || input.value !== origValue) {
+ listener(ev);
+ }
});
}
};
@@ -19324,7 +19612,7 @@
// command modifiers arrows
if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
- deferListener(event);
+ deferListener(event, this, this.value);
});
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
@@ -19338,7 +19626,7 @@
element.on('change', listener);
ctrl.$render = function() {
- element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue);
+ element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
};
}
@@ -19386,8 +19674,8 @@
// When a date is JSON'ified to wraps itself inside of an extra
// set of double quotes. This makes the date parsing code unable
// to match the date string and parse it as a date.
- if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') {
- iso = iso.substring(1, iso.length-1);
+ if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
+ iso = iso.substring(1, iso.length - 1);
}
if (ISO_DATE_REGEXP.test(iso)) {
return new Date(iso);
@@ -19448,10 +19736,10 @@
});
ctrl.$formatters.push(function(value) {
- if (!ctrl.$isEmpty(value)) {
- if (!isDate(value)) {
- throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
- }
+ if (value && !isDate(value)) {
+ throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
+ }
+ if (isValidDate(value)) {
previousDate = value;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
@@ -19460,14 +19748,14 @@
return $filter('date')(value, format, timezone);
} else {
previousDate = null;
+ return '';
}
- return '';
});
if (isDefined(attr.min) || attr.ngMin) {
var minVal;
ctrl.$validators.min = function(value) {
- return ctrl.$isEmpty(value) || isUndefined(minVal) || parseDate(value) >= minVal;
+ return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
};
attr.$observe('min', function(val) {
minVal = parseObservedDateValue(val);
@@ -19478,18 +19766,18 @@
if (isDefined(attr.max) || attr.ngMax) {
var maxVal;
ctrl.$validators.max = function(value) {
- return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
+ return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
};
attr.$observe('max', function(val) {
maxVal = parseObservedDateValue(val);
ctrl.$validate();
});
}
- // Override the standard $isEmpty to detect invalid dates as well
- ctrl.$isEmpty = function(value) {
+
+ function isValidDate(value) {
// Invalid Date: getTime() returns NaN
- return !value || (value.getTime && value.getTime() !== value.getTime());
- };
+ return value && !(value.getTime && value.getTime() !== value.getTime());
+ }
function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
@@ -19573,7 +19861,8 @@
stringBasedInputType(ctrl);
ctrl.$$parserName = 'url';
- ctrl.$validators.url = function(value) {
+ ctrl.$validators.url = function(modelValue, viewValue) {
+ var value = modelValue || viewValue;
return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
};
}
@@ -19585,7 +19874,8 @@
stringBasedInputType(ctrl);
ctrl.$$parserName = 'email';
- ctrl.$validators.email = function(value) {
+ ctrl.$validators.email = function(modelValue, viewValue) {
+ var value = modelValue || viewValue;
return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
};
}
@@ -19639,9 +19929,11 @@
element[0].checked = ctrl.$viewValue;
};
- // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue
+ // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
+ // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
+ // it to a boolean.
ctrl.$isEmpty = function(value) {
- return value !== trueValue;
+ return value === false;
};
ctrl.$formatters.push(function(value) {
@@ -19673,7 +19965,8 @@
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
+ * length.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
@@ -19689,10 +19982,14 @@
* @restrict E
*
* @description
- * HTML input element control with angular data-binding. Input control follows HTML5 input types
- * and polyfills the HTML5 validation behavior for older browsers.
+ * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
+ * input state control, and validation.
+ * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
*
- * *NOTE* Not every feature offered is available for all input types.
+ * <div class="alert alert-warning">
+ * **Note:** Not every feature offered is available for all input types.
+ * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
+ * </div>
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -19701,7 +19998,8 @@
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
+ * length.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
@@ -19828,19 +20126,25 @@
* @name ngModel.NgModelController
*
* @property {string} $viewValue Actual string value in the view.
- * @property {*} $modelValue The value in the model, that the control is bound to.
+ * @property {*} $modelValue The value in the model that the control is bound to.
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
- the control reads value from the DOM. Each function is called, in turn, passing the value
- through to the next. The last return value is used to populate the model.
- Used to sanitize / convert the value as well as validation. For validation,
- the parsers should update the validity state using
- {@link ngModel.NgModelController#$setValidity $setValidity()},
- and return `undefined` for invalid values.
+ the control reads value from the DOM. The functions are called in array order, each passing
+ its return value through to the next. The last return value is forwarded to the
+ {@link ngModel.NgModelController#$validators `$validators`} collection.
+
+Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
+`$viewValue`}.
+
+Returning `undefined` from a parser means a parse error occurred. In that case,
+no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
+will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
+is set to `true`. The parse error is stored in `ngModel.$error.parse`.
*
* @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
- the model value changes. Each function is called, in turn, passing the value through to the
- next. Used to format / convert values for display in the control and validation.
+ the model value changes. The functions are called in reverse array order, each passing the value through to the
+ next. The last return value is used as the actual DOM value.
+ Used to format / convert values for display in the control.
* ```js
* function formatter(value) {
* if (value) {
@@ -19871,8 +20175,9 @@
* is expected to return a promise when it is run during the model validation process. Once the promise
* is delivered then the validation status will be set to true when fulfilled and false when rejected.
* When the asynchronous validators are triggered, each of the validators will run in parallel and the model
- * value will only be updated once all validators have been fulfilled. Also, keep in mind that all
- * asynchronous validators will only run once all synchronous validators have passed.
+ * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
+ * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
+ * will only run once all synchronous validators have passed.
*
* Please note that if $http is used then it is important that the server returns a success HTTP response code
* in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
@@ -19893,9 +20198,6 @@
* };
* ```
*
- * @param {string} name The name of the validator.
- * @param {Function} validationFn The validation function that will be run.
- *
* @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
* view value has changed. It is called with no arguments, and its return value is ignored.
* This can be used in place of additional $watches against the model value.
@@ -19909,16 +20211,22 @@
* @property {boolean} $dirty True if user has already interacted with the control.
* @property {boolean} $valid True if there is no error.
* @property {boolean} $invalid True if at least one error on the control.
+ * @property {string} $name The name attribute of the control.
*
* @description
*
- * `NgModelController` provides API for the `ng-model` directive. The controller contains
- * services for data-binding, validation, CSS updates, and value formatting and parsing. It
- * purposefully does not contain any logic which deals with DOM rendering or listening to
- * DOM events. Such DOM related logic should be provided by other directives which make use of
- * `NgModelController` for data-binding.
+ * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
+ * The controller contains services for data-binding, validation, CSS updates, and value formatting
+ * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
+ * listening to DOM events.
+ * Such DOM related logic should be provided by other directives which make use of
+ * `NgModelController` for data-binding to control elements.
+ * Angular provides this DOM logic for most {@link input `input`} elements.
+ * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
+ * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
*
- * ## Custom Control Example
+ * @example
+ * ### Custom Control Example
* This example shows how to use `NgModelController` with a custom control to achieve
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
* collaborate together to achieve the desired result.
@@ -19928,7 +20236,7 @@
*
* We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
* module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
- * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks
+ * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
* that content using the `$sce` service.
*
* <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
@@ -19960,7 +20268,7 @@
// Listen for change events to enable binding
element.on('blur keyup change', function() {
- scope.$apply(read);
+ scope.$evalAsync(read);
});
read(); // initialize
@@ -20015,6 +20323,7 @@
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
this.$viewValue = Number.NaN;
this.$modelValue = Number.NaN;
+ this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
this.$validators = {};
this.$asyncValidators = {};
this.$parsers = [];
@@ -20033,32 +20342,33 @@
var parsedNgModel = $parse($attr.ngModel),
+ parsedNgModelAssign = parsedNgModel.assign,
+ ngModelGet = parsedNgModel,
+ ngModelSet = parsedNgModelAssign,
pendingDebounce = null,
ctrl = this;
- var ngModelGet = function ngModelGet() {
- var modelValue = parsedNgModel($scope);
- if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) {
- modelValue = modelValue();
- }
- return modelValue;
- };
-
- var ngModelSet = function ngModelSet(newValue) {
- var getterSetter;
- if (ctrl.$options && ctrl.$options.getterSetter &&
- isFunction(getterSetter = parsedNgModel($scope))) {
-
- getterSetter(ctrl.$modelValue);
- } else {
- parsedNgModel.assign($scope, ctrl.$modelValue);
- }
- };
-
this.$$setOptions = function(options) {
ctrl.$options = options;
+ if (options && options.getterSetter) {
+ var invokeModelGetter = $parse($attr.ngModel + '()'),
+ invokeModelSetter = $parse($attr.ngModel + '($$$p)');
- if (!parsedNgModel.assign && (!options || !options.getterSetter)) {
+ ngModelGet = function($scope) {
+ var modelValue = parsedNgModel($scope);
+ if (isFunction(modelValue)) {
+ modelValue = invokeModelGetter($scope);
+ }
+ return modelValue;
+ };
+ ngModelSet = function($scope, newValue) {
+ if (isFunction(parsedNgModel($scope))) {
+ invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
+ } else {
+ parsedNgModelAssign($scope, ctrl.$modelValue);
+ }
+ };
+ } else if (!parsedNgModel.assign) {
throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
$attr.ngModel, startingTag($element));
}
@@ -20091,17 +20401,18 @@
* @name ngModel.NgModelController#$isEmpty
*
* @description
- * This is called when we need to determine if the value of the input is empty.
+ * This is called when we need to determine if the value of an input is empty.
*
* For instance, the required directive does this to work out if the input has data or not.
+ *
* The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
*
* You can override this for input directives whose concept of being empty is different to the
* default. The `checkboxInputType` directive does this because in its case a value of `false`
* implies empty.
*
- * @param {*} value Model value to check.
- * @returns {boolean} True if `value` is empty.
+ * @param {*} value The value of the input to check for emptiness.
+ * @returns {boolean} True if `value` is "empty".
*/
this.$isEmpty = function(value) {
return isUndefined(value) || value === '' || value === null || value !== value;
@@ -20115,19 +20426,22 @@
* @name ngModel.NgModelController#$setValidity
*
* @description
- * Change the validity state, and notifies the form.
+ * Change the validity state, and notify the form.
*
- * This method can be called within $parsers/$formatters. However, if possible, please use the
- * `ngModel.$validators` pipeline which is designed to call this method automatically.
+ * This method can be called within $parsers/$formatters or a custom validation implementation.
+ * However, in most cases it should be sufficient to use the `ngModel.$validators` and
+ * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
*
- * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
- * to `$error[validationErrorKey]` and `$pending[validationErrorKey]`
- * so that it is available for data-binding.
+ * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
+ * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
+ * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
* The `validationErrorKey` should be in camelCase and will get converted into dash-case
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
* class and can be bound to as `{{someForm.someControl.$error.myError}}` .
* @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
- * or skipped (null).
+ * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
+ * Skipped is used by Angular when validators do not run because of parse errors and
+ * when `$asyncValidators` do not run because any of the `$validators` failed.
*/
addSetValidityMethod({
ctrl: this,
@@ -20149,15 +20463,34 @@
* @description
* Sets the control to its pristine state.
*
- * This method can be called to remove the 'ng-dirty' class and set the control to its pristine
- * state (ng-pristine class). A model is considered to be pristine when the model has not been changed
- * from when first compiled within then form.
+ * This method can be called to remove the `ng-dirty` class and set the control to its pristine
+ * state (`ng-pristine` class). A model is considered to be pristine when the control
+ * has not been changed from when first compiled.
*/
- this.$setPristine = function () {
+ this.$setPristine = function() {
ctrl.$dirty = false;
ctrl.$pristine = true;
$animate.removeClass($element, DIRTY_CLASS);
$animate.addClass($element, PRISTINE_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setDirty
+ *
+ * @description
+ * Sets the control to its dirty state.
+ *
+ * This method can be called to remove the `ng-pristine` class and set the control to its dirty
+ * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
+ * from when first compiled.
+ */
+ this.$setDirty = function() {
+ ctrl.$dirty = true;
+ ctrl.$pristine = false;
+ $animate.removeClass($element, PRISTINE_CLASS);
+ $animate.addClass($element, DIRTY_CLASS);
+ parentForm.$setDirty();
};
/**
@@ -20167,8 +20500,8 @@
* @description
* Sets the control to its untouched state.
*
- * This method can be called to remove the 'ng-touched' class and set the control to its
- * untouched state (ng-untouched class). Upon compilation, a model is set as untouched
+ * This method can be called to remove the `ng-touched` class and set the control to its
+ * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
* by default, however this function can be used to restore that state if the model has
* already been touched by the user.
*/
@@ -20185,10 +20518,9 @@
* @description
* Sets the control to its touched state.
*
- * This method can be called to remove the 'ng-untouched' class and set the control to its
- * touched state (ng-touched class). A model is considered to be touched when the user has
- * first interacted (focussed) on the model input element and then shifted focus away (blurred)
- * from the input element.
+ * This method can be called to remove the `ng-untouched` class and set the control to its
+ * touched state (`ng-touched` class). A model is considered to be touched when the user has
+ * first focused the control element and then shifted focus away from the control (blur event).
*/
this.$setTouched = function() {
ctrl.$touched = true;
@@ -20222,13 +20554,13 @@
* angular.module('cancel-update-example', [])
*
* .controller('CancelUpdateController', ['$scope', function($scope) {
- * $scope.resetWithCancel = function (e) {
+ * $scope.resetWithCancel = function(e) {
* if (e.keyCode == 27) {
* $scope.myForm.myInput1.$rollbackViewValue();
* $scope.myValue = '';
* }
* };
- * $scope.resetWithoutCancel = function (e) {
+ * $scope.resetWithoutCancel = function(e) {
* if (e.keyCode == 27) {
* $scope.myValue = '';
* }
@@ -20266,14 +20598,51 @@
* @name ngModel.NgModelController#$validate
*
* @description
- * Runs each of the registered validators (first synchronous validators and then asynchronous validators).
+ * Runs each of the registered validators (first synchronous validators and then
+ * asynchronous validators).
+ * If the validity changes to invalid, the model will be set to `undefined`,
+ * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
+ * If the validity changes to valid, it will set the model to the last available valid
+ * modelValue, i.e. either the last parsed value or the last value set from the scope.
*/
this.$validate = function() {
// ignore $validate before model is initialized
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
return;
}
- this.$$parseAndValidate();
+
+ var viewValue = ctrl.$$lastCommittedViewValue;
+ // Note: we use the $$rawModelValue as $modelValue might have been
+ // set to undefined during a view -> model update that found validation
+ // errors. We can't parse the view here, since that could change
+ // the model although neither viewValue nor the model on the scope changed
+ var modelValue = ctrl.$$rawModelValue;
+
+ // Check if the there's a parse error, so we don't unset it accidentially
+ var parserName = ctrl.$$parserName || 'parse';
+ var parserValid = ctrl.$error[parserName] ? false : undefined;
+
+ var prevValid = ctrl.$valid;
+ var prevModelValue = ctrl.$modelValue;
+
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+
+ ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
+ // If there was no change in validity, don't update the model
+ // This prevents changing an invalid modelValue to undefined
+ if (!allowInvalid && prevValid !== allValid) {
+ // Note: Don't check ctrl.$valid here, as we could have
+ // external validators (e.g. calculated on the server),
+ // that just call $setValidity and need the model value
+ // to calculate their validity.
+ ctrl.$modelValue = allValid ? modelValue : undefined;
+
+ if (ctrl.$modelValue !== prevModelValue) {
+ ctrl.$$writeModelToScope();
+ }
+ }
+ });
+
};
this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
@@ -20392,11 +20761,7 @@
// change to dirty
if (ctrl.$pristine) {
- ctrl.$dirty = true;
- ctrl.$pristine = false;
- $animate.removeClass($element, PRISTINE_CLASS);
- $animate.addClass($element, DIRTY_CLASS);
- parentForm.$setDirty();
+ this.$setDirty();
}
this.$$parseAndValidate();
};
@@ -20407,7 +20772,7 @@
var parserValid = isUndefined(modelValue) ? undefined : true;
if (parserValid) {
- for(var i = 0; i < ctrl.$parsers.length; i++) {
+ for (var i = 0; i < ctrl.$parsers.length; i++) {
modelValue = ctrl.$parsers[i](modelValue);
if (isUndefined(modelValue)) {
parserValid = false;
@@ -20417,15 +20782,20 @@
}
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
// ctrl.$modelValue has not been touched yet...
- ctrl.$modelValue = ngModelGet();
+ ctrl.$modelValue = ngModelGet($scope);
}
var prevModelValue = ctrl.$modelValue;
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+ ctrl.$$rawModelValue = modelValue;
+
if (allowInvalid) {
ctrl.$modelValue = modelValue;
writeToModelIfNeeded();
}
- ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
+
+ // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
+ // This can happen if e.g. $setViewValue is called from inside a parser
+ ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
if (!allowInvalid) {
// Note: Don't check ctrl.$valid here, as we could have
// external validators (e.g. calculated on the server),
@@ -20444,11 +20814,11 @@
};
this.$$writeModelToScope = function() {
- ngModelSet(ctrl.$modelValue);
+ ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
listener();
- } catch(e) {
+ } catch (e) {
$exceptionHandler(e);
}
});
@@ -20540,18 +20910,18 @@
// ng-change executes in apply phase
// 4. view should be changed back to 'a'
$scope.$watch(function ngModelWatch() {
- var modelValue = ngModelGet();
+ var modelValue = ngModelGet($scope);
// if scope model value and ngModel value are out of sync
// TODO(perf): why not move this to the action fn?
if (modelValue !== ctrl.$modelValue) {
- ctrl.$modelValue = modelValue;
+ ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
var formatters = ctrl.$formatters,
idx = formatters.length;
var viewValue = modelValue;
- while(idx--) {
+ while (idx--) {
viewValue = formatters[idx](viewValue);
}
if (ctrl.$viewValue !== viewValue) {
@@ -20572,6 +20942,7 @@
* @name ngModel
*
* @element input
+ * @priority 1
*
* @description
* The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
@@ -20593,7 +20964,7 @@
*
* For best practices on using `ngModel`, see:
*
- * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes]
+ * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
*
* For basic examples, how to use `ngModel`, see:
*
@@ -20605,7 +20976,7 @@
* - {@link input[email] email}
* - {@link input[url] url}
* - {@link input[date] date}
- * - {@link input[dateTimeLocal] dateTimeLocal}
+ * - {@link input[datetime-local] datetime-local}
* - {@link input[time] time}
* - {@link input[month] month}
* - {@link input[week] week}
@@ -20616,10 +20987,15 @@
* The following CSS classes are added and removed on the associated input/select/textarea element
* depending on the validity of the model.
*
- * - `ng-valid` is set if the model is valid.
- * - `ng-invalid` is set if the model is invalid.
- * - `ng-pristine` is set if the model is pristine.
- * - `ng-dirty` is set if the model is dirty.
+ * - `ng-valid`: the model is valid
+ * - `ng-invalid`: the model is invalid
+ * - `ng-valid-[key]`: for each valid key added by `$setValidity`
+ * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
+ * - `ng-pristine`: the control hasn't been interacted with yet
+ * - `ng-dirty`: the control has been interacted with
+ * - `ng-touched`: the control has been blurred
+ * - `ng-untouched`: the control hasn't been blurred
+ * - `ng-pending`: any `$asyncValidators` are unfulfilled
*
* Keep in mind that ngAnimate can detect each of these classes when added and removed.
*
@@ -20713,7 +21089,7 @@
.controller('ExampleController', ['$scope', function($scope) {
var _name = 'Brian';
$scope.user = {
- name: function (newName) {
+ name: function(newName) {
if (angular.isDefined(newName)) {
_name = newName;
}
@@ -20724,7 +21100,7 @@
</file>
* </example>
*/
-var ngModelDirective = function() {
+var ngModelDirective = ['$rootScope', function($rootScope) {
return {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'],
@@ -20768,15 +21144,17 @@
element.on('blur', function(ev) {
if (modelCtrl.$touched) return;
- scope.$apply(function() {
- modelCtrl.$setTouched();
- });
+ if ($rootScope.$$phase) {
+ scope.$evalAsync(modelCtrl.$setTouched);
+ } else {
+ scope.$apply(modelCtrl.$setTouched);
+ }
});
}
};
}
};
-};
+}];
/**
@@ -20865,8 +21243,8 @@
if (!ctrl) return;
attr.required = true; // force truthy in case we are on non input element
- ctrl.$validators.required = function(value) {
- return !attr.required || !ctrl.$isEmpty(value);
+ ctrl.$validators.required = function(modelValue, viewValue) {
+ return !attr.required || !ctrl.$isEmpty(viewValue);
};
attr.$observe('required', function() {
@@ -20887,7 +21265,7 @@
var regexp, patternExp = attr.ngPattern || attr.pattern;
attr.$observe('pattern', function(regex) {
if (isString(regex) && regex.length > 0) {
- regex = new RegExp(regex);
+ regex = new RegExp('^' + regex + '$');
}
if (regex && !regex.test) {
@@ -20915,13 +21293,14 @@
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
- var maxlength = 0;
+ var maxlength = -1;
attr.$observe('maxlength', function(value) {
- maxlength = int(value) || 0;
+ var intVal = int(value);
+ maxlength = isNaN(intVal) ? -1 : intVal;
ctrl.$validate();
});
ctrl.$validators.maxlength = function(modelValue, viewValue) {
- return ctrl.$isEmpty(modelValue) || viewValue.length <= maxlength;
+ return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
};
}
};
@@ -20940,7 +21319,7 @@
ctrl.$validate();
});
ctrl.$validators.minlength = function(modelValue, viewValue) {
- return ctrl.$isEmpty(modelValue) || viewValue.length >= minlength;
+ return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
};
}
};
@@ -21080,12 +21459,17 @@
* @name ngValue
*
* @description
- * Binds the given expression to the value of `input[select]` or `input[radio]`, so
- * that when the element is selected, the `ngModel` of that element is set to the
- * bound value.
+ * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
+ * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
+ * the bound value.
*
- * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as
- * shown below.
+ * `ngValue` is useful when dynamically generating lists of radio buttons using
+ * {@link ngRepeat `ngRepeat`}, as shown below.
+ *
+ * Likewise, `ngValue` can be used to generate `<option>` elements for
+ * the {@link select `select`} element. In that case however, only strings are supported
+ * for the `value `attribute, so the resulting `ngModel` will always be a string.
+ * Support for `select` models with non-string values is available via `ngOptions`.
*
* @element input
* @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
@@ -21173,7 +21557,7 @@
* `ngModelOptions` has an effect on the element it's declared on and its descendants.
*
* @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
- * - `updateOn`: string specifying which event should be the input bound to. You can set several
+ * - `updateOn`: string specifying which event should the input be bound to. You can set several
* events using an space delimited list. There is a special event called `default` that
* matches the default events belonging of the control.
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
@@ -21215,7 +21599,7 @@
.controller('ExampleController', ['$scope', function($scope) {
$scope.user = { name: 'say', data: '' };
- $scope.cancel = function (e) {
+ $scope.cancel = function(e) {
if (e.keyCode == 27) {
$scope.userForm.userName.$rollbackViewValue();
}
@@ -21289,7 +21673,7 @@
.controller('ExampleController', ['$scope', function($scope) {
var _name = 'Brian';
$scope.user = {
- name: function (newName) {
+ name: function(newName) {
return angular.isDefined(newName) ? (_name = newName) : _name;
}
};
@@ -21512,7 +21896,7 @@
<file name="index.html">
<script>
angular.module('bindExample', [])
- .controller('ExampleController', ['$scope', function ($scope) {
+ .controller('ExampleController', ['$scope', function($scope) {
$scope.salutation = 'Hello';
$scope.name = 'World';
}]);
@@ -21563,12 +21947,11 @@
* @name ngBindHtml
*
* @description
- * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
- * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
- * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
- * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
- * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
- * include "angular-sanitize.js" in your application.
+ * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
+ * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
+ * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
+ * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
+ * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
*
* You may also bypass sanitization for values you know are safe. To do so, bind to
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
@@ -21667,10 +22050,10 @@
attr.$removeClass(newClasses);
}
- function digestClassCounts (classes, count) {
+ function digestClassCounts(classes, count) {
var classCounts = element.data('$classCounts') || {};
var classesToUpdate = [];
- forEach(classes, function (className) {
+ forEach(classes, function(className) {
if (count > 0 || classCounts[className]) {
classCounts[className] = (classCounts[className] || 0) + count;
if (classCounts[className] === +(count > 0)) {
@@ -21682,7 +22065,7 @@
return classesToUpdate.join(' ');
}
- function updateClasses (oldClasses, newClasses) {
+ function updateClasses(oldClasses, newClasses) {
var toAdd = arrayDifference(newClasses, oldClasses);
var toRemove = arrayDifference(oldClasses, newClasses);
toAdd = digestClassCounts(toAdd, 1);
@@ -21714,23 +22097,23 @@
var values = [];
outer:
- for(var i = 0; i < tokens1.length; i++) {
+ for (var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
- for(var j = 0; j < tokens2.length; j++) {
- if(token == tokens2[j]) continue outer;
+ for (var j = 0; j < tokens2.length; j++) {
+ if (token == tokens2[j]) continue outer;
}
values.push(token);
}
return values;
}
- function arrayClasses (classVal) {
+ function arrayClasses(classVal) {
if (isArray(classVal)) {
return classVal;
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
- var classes = [], i = 0;
+ var classes = [];
forEach(classVal, function(v, k) {
if (v) {
classes = classes.concat(k.split(' '));
@@ -22489,10 +22872,8 @@
</example>
*/
/*
- * A directive that allows creation of custom onclick handlers that are defined as angular
- * expressions and are compiled and executed within the current scope.
- *
- * Events that are handled via these handler are always configured not to propagate further.
+ * A collection of directives that allows creation of custom event handlers that are defined as
+ * angular expressions and are compiled and executed within the current scope.
*/
var ngEventDirectives = {};
@@ -22511,7 +22892,11 @@
return {
restrict: 'A',
compile: function($element, attr) {
- var fn = $parse(attr[directiveName]);
+ // We expose the powerful $event object on the scope that provides access to the Window,
+ // etc. that isn't protected by the fast paths in $parse. We explicitly request better
+ // checks at the cost of speed since event handler expressions are not executed as
+ // frequently as regular change detection.
+ var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
@@ -23022,13 +23407,13 @@
terminal: true,
restrict: 'A',
$$tlb: true,
- link: function ($scope, $element, $attr, ctrl, $transclude) {
+ link: function($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
if (value) {
if (!childScope) {
- $transclude(function (clone, newScope) {
+ $transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
// Note: We only need the first/last node of the cloned nodes.
@@ -23260,15 +23645,15 @@
currentElement;
var cleanupLastIncludeContent = function() {
- if(previousElement) {
+ if (previousElement) {
previousElement.remove();
previousElement = null;
}
- if(currentScope) {
+ if (currentScope) {
currentScope.$destroy();
currentScope = null;
}
- if(currentElement) {
+ if (currentElement) {
$animate.leave(currentElement).then(function() {
previousElement = null;
});
@@ -23346,7 +23731,7 @@
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
function namespaceAdaptedClone(clone) {
$element.append(clone);
- }, undefined, undefined, $element);
+ }, {futureParentElement: $element});
return;
}
@@ -23630,7 +24015,9 @@
</example>
*/
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
- var BRACE = /{}/g;
+ var BRACE = /{}/g,
+ IS_WHEN = /^when(Minus)?(.+)$/;
+
return {
restrict: 'EA',
link: function(scope, element, attr) {
@@ -23641,34 +24028,44 @@
whensExpFns = {},
startSymbol = $interpolate.startSymbol(),
endSymbol = $interpolate.endSymbol(),
- isWhen = /^when(Minus)?(.+)$/;
+ braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
+ watchRemover = angular.noop,
+ lastCount;
forEach(attr, function(expression, attributeName) {
- if (isWhen.test(attributeName)) {
- whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] =
- element.attr(attr.$attr[attributeName]);
+ var tmpMatch = IS_WHEN.exec(attributeName);
+ if (tmpMatch) {
+ var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
+ whens[whenKey] = element.attr(attr.$attr[attributeName]);
}
});
forEach(whens, function(expression, key) {
- whensExpFns[key] =
- $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
- offset + endSymbol));
+ whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
+
});
- scope.$watch(function ngPluralizeWatch() {
- var value = parseFloat(scope.$eval(numberExp));
+ scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
+ var count = parseFloat(newVal);
+ var countIsNaN = isNaN(count);
- if (!isNaN(value)) {
- //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
- //check it against pluralization rules in $locale service
- if (!(value in whens)) value = $locale.pluralCat(value - offset);
- return whensExpFns[value](scope);
- } else {
- return '';
+ if (!countIsNaN && !(count in whens)) {
+ // If an explicit number rule such as 1, 2, 3... is defined, just use it.
+ // Otherwise, check it against pluralization rules in $locale service.
+ count = $locale.pluralCat(count - offset);
}
- }, function ngPluralizeWatchAction(newVal) {
- element.text(newVal);
+
+ // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
+ // In JS `NaN !== NaN`, so we have to exlicitly check.
+ if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
+ watchRemover();
+ watchRemover = scope.$watch(whensExpFns[count], updateElementText);
+ lastCount = count;
+ }
});
+
+ function updateElementText(newText) {
+ element.text(newText || '');
+ }
}
};
}];
@@ -23951,10 +24348,10 @@
if (trackByExp) {
trackByExpGetter = $parse(trackByExp);
} else {
- trackByIdArrayFn = function (key, value) {
+ trackByIdArrayFn = function(key, value) {
return hashKey(value);
};
- trackByIdObjFn = function (key) {
+ trackByIdObjFn = function(key) {
return key;
};
}
@@ -24034,12 +24431,12 @@
nextBlockOrder[index] = block;
} else if (nextBlockMap[trackById]) {
// if collision detected. restore lastBlockMap and throw an error
- forEach(nextBlockOrder, function (block) {
+ forEach(nextBlockOrder, function(block) {
if (block && block.scope) lastBlockMap[block.id] = block;
});
throw ngRepeatMinErr('dupes',
"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
- expression, trackById, toJson(value));
+ expression, trackById, value);
} else {
// new never before seen block
nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
@@ -24150,17 +24547,17 @@
*
* ### Overriding `.ng-hide`
*
- * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change
+ * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS:
*
* ```css
* .ng-hide {
* /* this is just another form of hiding an element */
- * display:block!important;
- * position:absolute;
- * top:-9999px;
- * left:-9999px;
+ * display: block!important;
+ * position: absolute;
+ * top: -9999px;
+ * left: -9999px;
* }
* ```
*
@@ -24180,13 +24577,13 @@
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
* /* this is required as of 1.3x to properly
* apply all styling in a show/hide animation */
- * transition:0s linear all;
+ * transition: 0s linear all;
* }
*
* .my-element.ng-hide-add-active,
* .my-element.ng-hide-remove-active {
* /* the transition is defined in the active class */
- * transition:1s linear all;
+ * transition: 1s linear all;
* }
*
* .my-element.ng-hide-add { ... }
@@ -24224,33 +24621,33 @@
</div>
</file>
<file name="glyphicons.css">
- @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css);
+ @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
</file>
<file name="animations.css">
.animate-show {
- line-height:20px;
- opacity:1;
- padding:10px;
- border:1px solid black;
- background:white;
+ line-height: 20px;
+ opacity: 1;
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
}
.animate-show.ng-hide-add.ng-hide-add-active,
.animate-show.ng-hide-remove.ng-hide-remove-active {
- -webkit-transition:all linear 0.5s;
- transition:all linear 0.5s;
+ -webkit-transition: all linear 0.5s;
+ transition: all linear 0.5s;
}
.animate-show.ng-hide {
- line-height:0;
- opacity:0;
- padding:0 10px;
+ line-height: 0;
+ opacity: 0;
+ padding: 0 10px;
}
.check-element {
- padding:10px;
- border:1px solid black;
- background:white;
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
}
</file>
<file name="protractor.js" type="protractor">
@@ -24274,13 +24671,13 @@
restrict: 'A',
multiElement: true,
link: function(scope, element, attr) {
- scope.$watch(attr.ngShow, function ngShowWatchAction(value){
+ scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
// we're adding a temporary, animation-specific class for ng-hide since this way
// we can control when the element is actually displayed on screen without having
// to have a global/greedy CSS selector that breaks when other animations are run.
// Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
$animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
- tempClasses : NG_HIDE_IN_PROGRESS_CLASS
+ tempClasses: NG_HIDE_IN_PROGRESS_CLASS
});
});
}
@@ -24324,17 +24721,17 @@
*
* ### Overriding `.ng-hide`
*
- * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change
+ * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS:
*
* ```css
* .ng-hide {
* /* this is just another form of hiding an element */
- * display:block!important;
- * position:absolute;
- * top:-9999px;
- * left:-9999px;
+ * display: block!important;
+ * position: absolute;
+ * top: -9999px;
+ * left: -9999px;
* }
* ```
*
@@ -24351,7 +24748,7 @@
* //a working example can be found at the bottom of this page
* //
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
- * transition:0.5s linear all;
+ * transition: 0.5s linear all;
* }
*
* .my-element.ng-hide-add { ... }
@@ -24389,29 +24786,29 @@
</div>
</file>
<file name="glyphicons.css">
- @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css);
+ @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
</file>
<file name="animations.css">
.animate-hide {
- -webkit-transition:all linear 0.5s;
- transition:all linear 0.5s;
- line-height:20px;
- opacity:1;
- padding:10px;
- border:1px solid black;
- background:white;
+ -webkit-transition: all linear 0.5s;
+ transition: all linear 0.5s;
+ line-height: 20px;
+ opacity: 1;
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
}
.animate-hide.ng-hide {
- line-height:0;
- opacity:0;
- padding:0 10px;
+ line-height: 0;
+ opacity: 0;
+ padding: 0 10px;
}
.check-element {
- padding:10px;
- border:1px solid black;
- background:white;
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
}
</file>
<file name="protractor.js" type="protractor">
@@ -24435,11 +24832,11 @@
restrict: 'A',
multiElement: true,
link: function(scope, element, attr) {
- scope.$watch(attr.ngHide, function ngHideWatchAction(value){
+ scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
// The comment inside of the ngShowDirective explains why we add and
// remove a temporary class for the show/hide animation
$animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
- tempClasses : NG_HIDE_IN_PROGRESS_CLASS
+ tempClasses: NG_HIDE_IN_PROGRESS_CLASS
});
});
}
@@ -24740,7 +25137,7 @@
}]);
</script>
<div ng-controller="ExampleController">
- <input ng-model="title"><br>
+ <input ng-model="title"> <br/>
<textarea ng-model="text"></textarea> <br/>
<pane title="{{title}}">{{text}}</pane>
</div>
@@ -24818,7 +25215,6 @@
compile: function(element, attr) {
if (attr.type == 'text/ng-template') {
var templateUrl = attr.id,
- // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
text = element[0].text;
$templateCache.put(templateUrl, text);
@@ -24842,34 +25238,37 @@
* elements for the `<select>` element using the array or object obtained by evaluating the
* `ngOptions` comprehension_expression.
*
+ * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
+ * similar result. However, the `ngOptions` provides some benefits such as reducing memory and
+ * increasing speed by not creating a new scope for each repeated instance, as well as providing
+ * more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
+ * used when the `select` model needs to be bound to a non-string value. This is because an option
+ * element can only be bound to string values at present.
+ *
* When an item in the `<select>` menu is selected, the array element or object property
* represented by the selected option will be bound to the model identified by the `ngModel`
* directive.
- *
- * <div class="alert alert-warning">
- * **Note:** `ngModel` compares by reference, not value. This is important when binding to an
- * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
- * </div>
*
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
*
* <div class="alert alert-warning">
- * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead
- * of {@link ng.directive:ngRepeat ngRepeat} when you want the
- * `select` model to be bound to a non-string value. This is because an option element can only
- * be bound to string values at present.
+ * **Note:** `ngModel` compares by reference, not value. This is important when binding to an
+ * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
* </div>
*
- * <div class="alert alert-info">
- * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
+ * ## `select as`
+ *
+ * Using `select as` will bind the result of the `select as` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
- * or property name (for object data sources) of the value within the collection.
- * </div>
+ * or property name (for object data sources) of the value within the collection. If a `track by` expression
+ * is used, the result of that expression will be set as the value of the `option` and `select` elements.
*
- * **Note:** Using `select as` together with `trackexpr` is not recommended.
- * Reasoning:
+ * ### `select as` with `track by`
+ *
+ * Using `select as` together with `track by` is not recommended. Reasoning:
+ *
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
* $scope.selected = {name: 'aSubItem'};
@@ -24893,8 +25292,10 @@
* * for array data sources:
* * `label` **`for`** `value` **`in`** `array`
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
- * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
- * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
+ * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
+ * (for including a filter with `track by`)
* * for object data sources:
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
@@ -25036,7 +25437,7 @@
self.removeOption = function(value) {
if (this.hasOption(value)) {
delete optionsMap[value];
- if (ngModelCtrl.$viewValue == value) {
+ if (ngModelCtrl.$viewValue === value) {
this.renderUnknownOption(value);
}
}
@@ -25080,7 +25481,7 @@
unknownOption = optionTemplate.clone();
// find "null" option
- for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
+ for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
if (children[i].value === '') {
emptyOption = nullOption = children.eq(i);
break;
@@ -25182,6 +25583,7 @@
valuesFn = $parse(match[7]),
track = match[8],
trackFn = track ? $parse(match[8]) : null,
+ trackKeysCache = {},
// This is an array of array of existing option groups in DOM.
// We try to reuse these if possible
// - optionGroupsCache[0] is the options with no option group
@@ -25227,17 +25629,16 @@
function selectionChanged() {
scope.$apply(function() {
- var optionGroup,
- collection = valuesFn(scope) || [],
- key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
+ var collection = valuesFn(scope) || [];
var viewValue;
if (multiple) {
viewValue = [];
forEach(selectElement.val(), function(selectedKey) {
+ selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
});
} else {
- var selectedKey = selectElement.val();
+ var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
viewValue = getViewValue(selectedKey, collection[selectedKey]);
}
ctrl.$setViewValue(viewValue);
@@ -25307,7 +25708,7 @@
if (multiple) {
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
} else {
- return viewValue == callExpression(compareValueFn, key, value);
+ return viewValue === callExpression(compareValueFn, key, value);
}
};
}
@@ -25359,14 +25760,17 @@
anySelected = false,
lastElement,
element,
- label;
+ label,
+ optionId;
+
+ trackKeysCache = {};
// We now build up the list of options we need (we merge later)
for (index = 0; length = keys.length, index < length; index++) {
key = index;
if (keyName) {
key = keys[index];
- if ( key.charAt(0) === '$' ) continue;
+ if (key.charAt(0) === '$') continue;
}
value = values[key];
@@ -25383,9 +25787,14 @@
// doing displayFn(scope, locals) || '' overwrites zero values
label = isDefined(label) ? label : '';
+ optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
+ if (trackFn) {
+ trackKeysCache[optionId] = key;
+ }
+
optionGroup.push({
// either the index into array or key from object
- id: (keyName ? keys[index] : index),
+ id: optionId,
label: label,
selected: selected // determine if we should be selected
});
@@ -25430,15 +25839,16 @@
}
lastElement = null; // start at the beginning
- for(index = 0, length = optionGroup.length; index < length; index++) {
+ for (index = 0, length = optionGroup.length; index < length; index++) {
option = optionGroup[index];
- if ((existingOption = existingOptions[index+1])) {
+ if ((existingOption = existingOptions[index + 1])) {
// reuse elements
lastElement = existingOption.element;
if (existingOption.label !== option.label) {
updateLabelMap(labelMap, existingOption.label, false);
updateLabelMap(labelMap, option.label, true);
lastElement.text(existingOption.label = option.label);
+ lastElement.prop('label', existingOption.label);
}
if (existingOption.id !== option.id) {
lastElement.val(existingOption.id = option.id);
@@ -25468,6 +25878,7 @@
.val(option.id)
.prop('selected', option.selected)
.attr('selected', option.selected)
+ .prop('label', option.label)
.text(option.label);
}
@@ -25488,23 +25899,28 @@
}
// remove any excessive OPTIONs in a group
index++; // increment since the existingOptions[0] is parent element not OPTION
- while(existingOptions.length > index) {
+ while (existingOptions.length > index) {
option = existingOptions.pop();
updateLabelMap(labelMap, option.label, false);
option.element.remove();
}
- forEach(labelMap, function (count, label) {
- if (count > 0) {
- selectCtrl.addOption(label);
- } else if (count < 0) {
- selectCtrl.removeOption(label);
- }
- });
}
// remove any excessive OPTGROUPs from select
- while(optionGroupsCache.length > groupIndex) {
- optionGroupsCache.pop()[0].element.remove();
+ while (optionGroupsCache.length > groupIndex) {
+ // remove all the labels in the option group
+ optionGroup = optionGroupsCache.pop();
+ for (index = 1; index < optionGroup.length; ++index) {
+ updateLabelMap(labelMap, optionGroup[index].label, false);
+ }
+ optionGroup[0].element.remove();
}
+ forEach(labelMap, function(count, label) {
+ if (count > 0) {
+ selectCtrl.addOption(label);
+ } else if (count < 0) {
+ selectCtrl.removeOption(label);
+ }
+ });
}
}
}
@@ -25528,7 +25944,7 @@
}
}
- return function (scope, element, attr) {
+ return function(scope, element, attr) {
var selectCtrlName = '$selectController',
parent = element.parent(),
selectCtrl = parent.data(selectCtrlName) ||
--
Gitblit v1.3.2