vendor/assets/javascripts/angular.js in rails-angularjs-1.4.9 vs vendor/assets/javascripts/angular.js in rails-angularjs-1.5.0
- old
+ new
@@ -1,8 +1,8 @@
/**
- * @license AngularJS v1.4.9
- * (c) 2010-2015 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.5.0
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {'use strict';
/**
@@ -55,11 +55,11 @@
}
return match;
});
- message += '\nhttp://errors.angularjs.org/1.4.9/' +
+ message += '\nhttp://errors.angularjs.org/1.5.0/' +
(module ? module + '/' : '') + code;
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
encodeURIComponent(toDebugString(templateArgs[i]));
@@ -186,33 +186,13 @@
// The name of a form control's ValidityState property.
// This is used so that it's possible for internal tests to create mock ValidityStates.
var VALIDITY_STATE_PROPERTY = 'validity';
-/**
- * @ngdoc function
- * @name angular.lowercase
- * @module ng
- * @kind function
- *
- * @description Converts the specified string to lowercase.
- * @param {string} string String to be converted to lowercase.
- * @returns {string} Lowercased string.
- */
-var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
var hasOwnProperty = Object.prototype.hasOwnProperty;
-/**
- * @ngdoc function
- * @name angular.uppercase
- * @module ng
- * @kind function
- *
- * @description Converts the specified string to uppercase.
- * @param {string} string String to be converted to uppercase.
- * @returns {string} Uppercased string.
- */
+var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
var manualLowercase = function(s) {
/* jshint bitwise: false */
@@ -228,11 +208,11 @@
};
// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
-// with correct but slower alternatives.
+// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387
if ('i' !== 'I'.toLowerCase()) {
lowercase = manualLowercase;
uppercase = manualUppercase;
}
@@ -271,11 +251,11 @@
// `null`, `undefined` and `window` are not array-like
if (obj == null || isWindow(obj)) return false;
// arrays, strings and jQuery/jqLite objects are array like
// * jqLite is either the jQuery or jqLite constructor function
- // * we have to check the existance of jqLite first as this method is called
+ // * we have to check the existence of jqLite first as this method is called
// via the forEach method when constructing the jqLite object in the first place
if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
// Support: iOS 8.2 (not reproducible in simulator)
// "length" in obj used to prevent JIT error (gh-11508)
@@ -380,11 +360,11 @@
* when using forEach the params are value, key, but it is often useful to have key, value.
* @param {function(string, *)} iteratorFn
* @returns {function(*, string)}
*/
function reverseParams(iteratorFn) {
- return function(value, key) { iteratorFn(key, value); };
+ return function(value, key) {iteratorFn(key, value);};
}
/**
* A consistent way of creating unique IDs in angular.
*
@@ -751,11 +731,15 @@
var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
function isTypedArray(value) {
return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
}
+function isArrayBuffer(obj) {
+ return toString.call(obj) === '[object ArrayBuffer]';
+}
+
var trim = function(value) {
return isString(value) ? value.trim() : value;
};
// Copied from:
@@ -788,11 +772,11 @@
/**
* @param str 'key1,key2,...'
* @returns {object} in the form of {key1:true, key2:true, ...}
*/
function makeMap(str) {
- var obj = {}, items = str.split(","), i;
+ var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
@@ -875,11 +859,11 @@
function copy(source, destination) {
var stackSource = [];
var stackDest = [];
if (destination) {
- if (isTypedArray(destination)) {
+ if (isTypedArray(destination) || isArrayBuffer(destination)) {
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
}
if (source === destination) {
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
}
@@ -949,36 +933,63 @@
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
var needsRecurse = false;
- var destination;
+ var destination = copyType(source);
- if (isArray(source)) {
- destination = [];
+ if (destination === undefined) {
+ destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
needsRecurse = true;
- } else if (isTypedArray(source)) {
- destination = new source.constructor(source);
- } else if (isDate(source)) {
- destination = new Date(source.getTime());
- } else if (isRegExp(source)) {
- destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
- destination.lastIndex = source.lastIndex;
- } else if (isFunction(source.cloneNode)) {
- destination = source.cloneNode(true);
- } else {
- destination = Object.create(getPrototypeOf(source));
- needsRecurse = true;
}
stackSource.push(source);
stackDest.push(destination);
return needsRecurse
? copyRecurse(source, destination)
: destination;
}
+
+ function copyType(source) {
+ switch (toString.call(source)) {
+ case '[object Int8Array]':
+ case '[object Int16Array]':
+ case '[object Int32Array]':
+ case '[object Float32Array]':
+ case '[object Float64Array]':
+ case '[object Uint8Array]':
+ case '[object Uint8ClampedArray]':
+ case '[object Uint16Array]':
+ case '[object Uint32Array]':
+ return new source.constructor(copyElement(source.buffer));
+
+ case '[object ArrayBuffer]':
+ //Support: IE10
+ if (!source.slice) {
+ var copied = new ArrayBuffer(source.byteLength);
+ new Uint8Array(copied).set(new Uint8Array(source));
+ return copied;
+ }
+ return source.slice(0);
+
+ case '[object Boolean]':
+ case '[object Number]':
+ case '[object String]':
+ case '[object Date]':
+ return new source.constructor(source.valueOf());
+
+ case '[object RegExp]':
+ var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
+ re.lastIndex = source.lastIndex;
+ return re;
+ }
+
+ if (isFunction(source.cloneNode)) {
+ return source.cloneNode(true);
+ }
+ }
}
/**
* Creates a shallow copy of an object, an array or a primitive.
*
@@ -1037,42 +1048,41 @@
function equals(o1, o2) {
if (o1 === o2) return true;
if (o1 === null || o2 === null) return false;
if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
- if (t1 == t2) {
- if (t1 == 'object') {
- if (isArray(o1)) {
- if (!isArray(o2)) return false;
- if ((length = o1.length) == o2.length) {
- for (key = 0; key < length; key++) {
- if (!equals(o1[key], o2[key])) return false;
- }
- return true;
- }
- } else if (isDate(o1)) {
- if (!isDate(o2)) return false;
- return equals(o1.getTime(), o2.getTime());
- } else if (isRegExp(o1)) {
- return isRegExp(o2) ? o1.toString() == o2.toString() : false;
- } else {
- if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
- isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
- keySet = createMap();
- for (key in o1) {
- if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
+ if (t1 == t2 && t1 == 'object') {
+ if (isArray(o1)) {
+ if (!isArray(o2)) return false;
+ if ((length = o1.length) == o2.length) {
+ for (key = 0; key < length; key++) {
if (!equals(o1[key], o2[key])) return false;
- keySet[key] = true;
}
- for (key in o2) {
- if (!(key in keySet) &&
- key.charAt(0) !== '$' &&
- isDefined(o2[key]) &&
- !isFunction(o2[key])) return false;
- }
return true;
}
+ } else if (isDate(o1)) {
+ if (!isDate(o2)) return false;
+ return equals(o1.getTime(), o2.getTime());
+ } else if (isRegExp(o1)) {
+ if (!isRegExp(o2)) return false;
+ return o1.toString() == o2.toString();
+ } else {
+ if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
+ isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
+ keySet = createMap();
+ 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) {
+ if (!(key in keySet) &&
+ key.charAt(0) !== '$' &&
+ isDefined(o2[key]) &&
+ !isFunction(o2[key])) return false;
+ }
+ return true;
}
}
return false;
}
@@ -1245,11 +1255,11 @@
* @param {boolean|number} [pretty=2] 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.
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
- if (typeof obj === 'undefined') return undefined;
+ if (isUndefined(obj)) return undefined;
if (!isNumber(pretty)) {
pretty = pretty ? 2 : null;
}
return JSON.stringify(obj, toJsonReplacer, pretty);
}
@@ -1272,11 +1282,14 @@
? JSON.parse(json)
: json;
}
+var ALL_COLONS = /:/g;
function timezoneToOffset(timezone, fallback) {
+ // IE/Edge do not "understand" colon (`:`) in timezone
+ timezone = timezone.replace(ALL_COLONS, '');
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}
@@ -1287,12 +1300,13 @@
}
function convertTimezoneToLocal(date, timezone, reverse) {
reverse = reverse ? -1 : 1;
- var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
- return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
+ var dateTimezoneOffset = date.getTimezoneOffset();
+ var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
+ return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
}
/**
* @returns {string} Returns the string representation of the element.
@@ -1307,11 +1321,11 @@
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); });
+ replace(/^<([\w\-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
} catch (e) {
return lowercase(elemHtml);
}
}
@@ -1750,11 +1764,10 @@
return (pos ? separator : '') + letter.toLowerCase();
});
}
var bindJQueryFired = false;
-var skipDestroyOnNextJQueryCleanData;
function bindJQuery() {
var originalCleanData;
if (bindJQueryFired) {
return;
@@ -1784,19 +1797,15 @@
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jQuery.cleanData;
jQuery.cleanData = function(elems) {
var events;
- if (!skipDestroyOnNextJQueryCleanData) {
- for (var i = 0, elem; (elem = elems[i]) != null; i++) {
- events = jQuery._data(elem, "events");
- if (events && events.$destroy) {
- jQuery(elem).triggerHandler('$destroy');
- }
+ for (var i = 0, elem; (elem = elems[i]) != null; i++) {
+ events = jQuery._data(elem, "events");
+ if (events && events.$destroy) {
+ jQuery(elem).triggerHandler('$destroy');
}
- } else {
- skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
} else {
jqLite = JQLite;
@@ -2194,10 +2203,23 @@
*/
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
/**
* @ngdoc method
+ * @name angular.Module#component
+ * @module ng
+ * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
+ * @param {Object} options Component definition object (a simplified
+ * {@link ng.$compile#directive-definition-object directive definition object})
+ *
+ * @description
+ * See {@link ng.$compileProvider#component $compileProvider.component()}.
+ */
+ component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
+
+ /**
+ * @ngdoc method
* @name angular.Module#config
* @module ng
* @param {Function} configFn Execute this function on module load. Useful for service
* configuration.
* @description
@@ -2350,10 +2372,11 @@
$$AnimateRunnerFactoryProvider,
$$AnimateAsyncRunFactoryProvider,
$BrowserProvider,
$CacheFactoryProvider,
$ControllerProvider,
+ $DateProvider,
$DocumentProvider,
$ExceptionHandlerProvider,
$FilterProvider,
$$ForceReflowProvider,
$InterpolateProvider,
@@ -2399,15 +2422,15 @@
* - `minor` – `{number}` – Minor version number, such as "9".
* - `dot` – `{number}` – Dot version number, such as "18".
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.4.9', // all of these placeholder strings will be replaced by grunt's
+ full: '1.5.0', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
- minor: 4,
- dot: 9,
- codeName: 'implicit-superannuation'
+ minor: 5,
+ dot: 0,
+ codeName: 'ennoblement-facilitation'
};
function publishExternalAPI(angular) {
extend(angular, {
@@ -2742,10 +2765,16 @@
return true;
}
return false;
}
+function jqLiteCleanData(nodes) {
+ for (var i = 0, ii = nodes.length; i < ii; i++) {
+ jqLiteRemoveData(nodes[i]);
+ }
+}
+
function jqLiteBuildFragment(html, context) {
var tmp, tag, wrap,
fragment = context.createDocumentFragment(),
nodes = [], i;
@@ -2794,11 +2823,21 @@
}
return [];
}
+function jqLiteWrapNode(node, wrapper) {
+ var parent = node.parentNode;
+ if (parent) {
+ parent.replaceChild(wrapper, node);
+ }
+
+ wrapper.appendChild(node);
+}
+
+
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
var jqLiteContains = Node.prototype.contains || function(arg) {
// jshint bitwise: false
return !!(this.compareDocumentPosition(arg) & 16);
// jshint bitwise: true
@@ -3044,11 +3083,11 @@
function jqLiteDocumentLoaded(action, win) {
win = win || window;
if (win.document.readyState === 'complete') {
- // Force the action to be run async for consistent behaviour
+ // Force the action to be run async for consistent behavior
// from the action's point of view
// i.e. it will definitely not be in a $apply
win.setTimeout(action);
} else {
// No need to unbind this handler as load is only ever called once
@@ -3130,11 +3169,12 @@
}
forEach({
data: jqLiteData,
removeData: jqLiteRemoveData,
- hasData: jqLiteHasData
+ hasData: jqLiteHasData,
+ cleanData: jqLiteCleanData
}, function(fn, name) {
JQLite[name] = fn;
});
forEach({
@@ -3485,16 +3525,11 @@
});
}
},
wrap: function(element, wrapNode) {
- wrapNode = jqLite(wrapNode).eq(0).clone()[0];
- var parent = element.parentNode;
- if (parent) {
- parent.replaceChild(wrapNode, element);
- }
- wrapNode.appendChild(element);
+ jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
},
remove: jqLiteRemove,
detach: function(element) {
@@ -3768,30 +3803,35 @@
* @description
*
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
*/
+var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');
+function extractArgs(fn) {
+ var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
+ args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
+ return args;
+}
+
function anonFn(fn) {
// For anonymous functions, showing at the very least the function signature can help in
// debugging.
- var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
- args = fnText.match(FN_ARGS);
+ var args = extractArgs(fn);
if (args) {
return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
}
return 'fn';
}
function annotate(fn, strictDi, name) {
var $inject,
- fnText,
argDecl,
last;
if (typeof fn === 'function') {
if (!($inject = fn.$inject)) {
@@ -3802,12 +3842,11 @@
name = fn.name || anonFn(fn);
}
throw $injectorMinErr('strictdi',
'{0} is not using explicit annotation and cannot be invoked in strict mode', name);
}
- fnText = fn.toString().replace(STRIP_COMMENTS, '');
- argDecl = fnText.match(FN_ARGS);
+ argDecl = extractArgs(fn);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
@@ -4193,13 +4232,25 @@
* @name $provide#service
* @description
*
* Register a **service constructor**, which will be invoked with `new` to create the service
* instance.
- * This is short for registering a service where its provider's `$get` property is the service
- * constructor function that will be used to instantiate the service instance.
+ * This is short for registering a service where its provider's `$get` property is a factory
+ * function that returns an instance instantiated by the injector from the service constructor
+ * function.
*
+ * Internally it looks a bit like this:
+ *
+ * ```
+ * {
+ * $get: function() {
+ * return $injector.instantiate(constructor);
+ * }
+ * }
+ * ```
+ *
+ *
* You should use {@link auto.$provide#service $provide.service(class)} if you define your service
* as a type/class.
*
* @param {string} name The name of the instance.
* @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
@@ -4344,19 +4395,24 @@
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
- instanceInjector = (instanceCache.$injector =
+ protoInstanceInjector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
- return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
- }));
+ return instanceInjector.invoke(
+ provider.$get, provider, undefined, serviceName);
+ }),
+ instanceInjector = protoInstanceInjector;
+ providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
+ var runBlocks = loadModules(modulesToLoad);
+ instanceInjector = protoInstanceInjector.get('$injector');
+ instanceInjector.strictDi = strictDi;
+ forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
- forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
-
return instanceInjector;
////////////////////////////////////
// $provider
////////////////////////////////////
@@ -4501,52 +4557,71 @@
path.shift();
}
}
}
- function invoke(fn, self, locals, serviceName) {
- if (typeof locals === 'string') {
- serviceName = locals;
- locals = null;
- }
+ function injectionArgs(fn, locals, serviceName) {
var args = [],
- $inject = createInjector.$$annotate(fn, strictDi, serviceName),
- length, i,
- key;
+ $inject = createInjector.$$annotate(fn, strictDi, serviceName);
- for (i = 0, length = $inject.length; i < length; i++) {
- key = $inject[i];
+ for (var i = 0, length = $inject.length; i < length; i++) {
+ var key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
- args.push(
- locals && locals.hasOwnProperty(key)
- ? locals[key]
- : getService(key, serviceName)
- );
+ args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
+ getService(key, serviceName));
}
+ return args;
+ }
+
+ function isClass(func) {
+ // IE 9-11 do not support classes and IE9 leaks with the code below.
+ if (msie <= 11) {
+ return false;
+ }
+ // Workaround for MS Edge.
+ // Check https://connect.microsoft.com/IE/Feedback/Details/2211653
+ return typeof func === 'function'
+ && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func));
+ }
+
+ function invoke(fn, self, locals, serviceName) {
+ if (typeof locals === 'string') {
+ serviceName = locals;
+ locals = null;
+ }
+
+ var args = injectionArgs(fn, locals, serviceName);
if (isArray(fn)) {
- fn = fn[length];
+ fn = fn[fn.length - 1];
}
- // http://jsperf.com/angularjs-invoke-apply-vs-switch
- // #5388
- return fn.apply(self, args);
+ if (!isClass(fn)) {
+ // http://jsperf.com/angularjs-invoke-apply-vs-switch
+ // #5388
+ return fn.apply(self, args);
+ } else {
+ args.unshift(null);
+ return new (Function.prototype.bind.apply(fn, args))();
+ }
}
+
function instantiate(Type, locals, serviceName) {
// 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) {}]);
- // Object creation: http://jsperf.com/create-constructor/2
- var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
- var returnedValue = invoke(Type, instance, locals, serviceName);
-
- return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
+ var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
+ var args = injectionArgs(Type, locals, serviceName);
+ // Empty object at position 0 is ignored for invocation with `new`, but required.
+ args.unshift(null);
+ return new (Function.prototype.bind.apply(ctor, args))();
}
+
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
@@ -5157,12 +5232,12 @@
* $animate.off('enter');
*
* // remove all the animation event listeners listening for `enter` on the given element and its children
* $animate.off('enter', container);
*
- * // remove the event listener function provided by `listenerFn` that is set
- * // to listen for `enter` on the given `element` as well as its children
+ * // remove the event listener function provided by `callback` that is set
+ * // to listen for `enter` on the given `container` as well as its children
* $animate.off('enter', container, callback);
* ```
*
* @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
* @param {DOMElement=} container the container element the event listener was placed on
@@ -5381,11 +5456,11 @@
* @name $animate#animate
* @kind function
*
* @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
* If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
- * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
+ * on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and
* `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
* style in `to`, the style in `from` is applied immediately, and no animation is run.
* If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
* method (or as part of the `options` parameter):
*
@@ -5403,11 +5478,11 @@
* @param {DOMElement} element the element which the CSS styles will be applied to
* @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
* @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
* @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
* this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
- * (Note that if no animation is detected then this value will not be appplied to the element.)
+ * (Note that if no animation is detected then this value will not be applied to the element.)
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
animate: function(element, from, to, className, options) {
@@ -5641,11 +5716,11 @@
if (options.from) {
element.css(options.from);
options.from = null;
}
- /* jshint newcap: false*/
+ /* jshint newcap: false */
var closed, runner = new $$AnimateRunner();
return {
start: run,
end: run
};
@@ -6567,11 +6642,11 @@
*
* #### `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 recommended that this feature be used on directives
- * which are not strictly behavioural (such as {@link ngClick}), and which
+ * which are not strictly behavioral (such as {@link ngClick}), and which
* do not manipulate or replace child nodes (such as {@link ngInclude}).
*
* #### `priority`
* When there are multiple directives defined on a single DOM element, sometimes it
* is necessary to specify the order in which the directives are applied. The `priority` is used
@@ -6605,40 +6680,67 @@
* directive's element. These local properties are useful for aliasing values for templates. The keys in
* the object hash map to the name of the property on the isolate scope; the values define how the property
* is bound to the parent scope, via matching attributes on the directive's element:
*
* * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
- * always a string since DOM attributes are strings. If no `attr` name is specified then the
- * attribute name is assumed to be the same as the local name.
- * Given `<widget my-attr="hello {{name}}">` and widget definition
- * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
- * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
- * `localName` property on the widget scope. The `name` is read from the parent scope (not
- * component scope).
+ * always a string since DOM attributes are strings. If no `attr` name is specified then the
+ * attribute name is assumed to be the same as the local name. Given `<my-component
+ * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
+ * the directive's scope property `localName` will reflect the interpolated value of `hello
+ * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
+ * scope. The `name` is read from the parent scope (not the directive's scope).
*
- * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
- * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
- * name is specified then the attribute name is assumed to be the same as the local name.
- * Given `<widget my-attr="parentModel">` and widget definition of
- * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
+ * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
+ * passed via the attribute `attr`. The expression is evaluated 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 local
+ * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
+ * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
+ * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
+ * `localModel` and vice versa. Optional attributes should be marked as such with a question mark:
+ * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't
+ * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`})
+ * will be thrown upon discovering changes to the local value, since it will be impossible to sync
+ * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
+ * method is used for tracking changes, and the equality check is based on object identity.
+ * However, if an object literal or an array literal is passed as the binding expression, the
+ * equality check is done by value (using the {@link angular.equals} function). It's also possible
+ * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
+ * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
+ *
+ * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
+ * expression passed via the attribute `attr`. The expression is evaluated 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
+ * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
+ *
+ * For example, given `<my-component my-attr="parentModel">` and directive definition of
+ * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
* 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. 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).
+ * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
+ * two caveats:
+ * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
+ * sets the same value. That means if your bound value is an object, changes to its properties
+ * in the isolated scope will be reflected in the parent scope (because both reference the same object).
+ * 2. one-way binding watches changes to the **identity** of the parent value. That means the
+ * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
+ * to the value has changed. In most cases, this should not be of concern, but can be important
+ * to know if you one-way bind to an object, and then replace that object in the isolated scope.
+ * If you now change a property of the object in your parent scope, the change will not be
+ * propagated to the isolated scope, because the identity of the object on the parent scope
+ * has not changed. Instead you must assign a new object.
*
- * * `&` 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
- * local name. Given `<widget my-attr="count = count + value">` and widget definition of
- * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
- * a function wrapper for the `count = count + value` expression. Often it's desirable to
- * pass data from the isolated scope via an expression to the parent scope, this can be
- * done by passing a map of local variable names and values into the expression wrapper fn.
- * For example, if the expression is `increment(amount)` then we can specify the amount value
- * by calling the `localFn` as `localFn({amount: 22})`.
+ * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
+ * back to the parent. However, it does not make this completely impossible.
*
+ * * `&` 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 local name.
+ * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
+ * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
+ * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
+ * via an expression to the parent scope. This can be done by passing a map of local variable names
+ * and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
+ * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
+ *
* In general it's possible to apply more than one directive to one element, but there might be limitations
* depending on the type of scope required by the directives. The following points will help explain these limitations.
* For simplicity only two directives are taken into account, but it is also applicable for several directives:
*
* * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
@@ -6657,13 +6759,23 @@
* `true` or an object hash with the same format as the `scope` property. Additionally, a controller
* alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller
* definition: `controller: 'myCtrl as myAlias'`.
*
* When an isolate scope is used for a directive (see above), `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.
+ * allow a component to have its properties bound to the controller, rather than to scope.
*
+ * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
+ * properties. You can access these bindings once they have been initialized by providing a controller method called
+ * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
+ * initialized.
+ *
+ * <div class="alert alert-warning">
+ * **Deprecation warning:** although bindings for non-ES6 class controllers are currently
+ * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization
+ * code that relies upon bindings inside a `$onInit` method on the controller, instead.
+ * </div>
+ *
* It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
* This will set up the scope bindings to the controller directly. Note that `scope` can still be used
* to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
* scope (useful for component directives).
*
@@ -6678,30 +6790,50 @@
*
* * `$scope` - Current scope associated with the element
* * `$element` - Current element
* * `$attrs` - Current attributes object for the element
* * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
- * `function([scope], cloneLinkingFn, futureParentElement)`.
- * * `scope`: optional argument to override the scope.
- * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
- * * `futureParentElement`:
+ * `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
+ * * `scope`: (optional) override the scope.
+ * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
+ * * `futureParentElement` (optional):
* * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
* * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
* * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
* and when the `cloneLinkinFn` is passed,
* as those elements need to created and cloned in a special way when they are defined outside their
* usual containers (e.g. like `<svg>`).
* * See also the `directive.templateNamespace` property.
+ * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
+ * then the default translusion is provided.
+ * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
+ * `true` if the specified slot contains content (i.e. one or more DOM nodes).
*
+ * The controller can provide the following methods that act as life-cycle hooks:
+ * * `$onInit` - Called on each controller after all the controllers on an element have been constructed and
+ * had their bindings initialized (and before the pre & post linking functions for the directives on
+ * this element). This is a good place to put initialization code for your controller.
*
* #### `require`
* Require another directive and inject its controller as the fourth argument to the linking function. The
- * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
- * injected argument will be an array in corresponding order. If no such directive can be
- * found, or if the directive does not have a controller, then an error is raised (unless no link function
- * is specified, in which case error checking is skipped). The name can be prefixed with:
+ * `require` property can be a string, an array or an object:
+ * * a **string** containing the name of the directive to pass to the linking function
+ * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
+ * linking function will be an array of controllers in the same order as the names in the `require` property
+ * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
+ * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
+ * controllers.
*
+ * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
+ * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
+ * have been constructed but before `$onInit` is called.
+ * See the {@link $compileProvider#component} helper for an example of how this can be used.
+ *
+ * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
+ * raised (unless no link function is specified and the required controllers are not being bound to the directive
+ * controller, in which case error checking is skipped). The name can be prefixed with:
+ *
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
* * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
* * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
* * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
@@ -6789,19 +6921,11 @@
* #### `transclude`
* Extract the contents of the element where the directive appears and make it available to the directive.
* The contents are compiled and provided to the directive as a **transclusion function**. See the
* {@link $compile#transclusion Transclusion} section below.
*
- * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
- * directive's element or the entire element:
*
- * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
- * * `'element'` - transclude the whole of the directive's element including any directives on this
- * element that defined at a lower priority than this directive. When used, the `template`
- * property is ignored.
- *
- *
* #### `compile`
*
* ```js
* function compile(tElement, tAttrs, transclude) { ... }
* ```
@@ -6824,11 +6948,11 @@
* should be done in a linking function rather than in a compile function.
* </div>
* <div class="alert alert-warning">
* **Note:** The compile function cannot handle directives that recursively use themselves in their
- * own templates or compile functions. Compiling these directives results in an infinite loop and a
+ * own templates or compile functions. Compiling these directives results in an infinite loop and
* stack overflow errors.
*
* This can be avoided by manually using $compile in the postLink function to imperatively compile
* a directive's template instead of relying on automatic template compilation via `template` or
* `templateUrl` declaration or manual compilation inside the compile function.
@@ -6926,10 +7050,38 @@
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
* DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
* Testing Transclusion Directives}.
* </div>
*
+ * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
+ * directive's element, the entire element or multiple parts of the element contents:
+ *
+ * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
+ * * `'element'` - transclude the whole of the directive's element including any directives on this
+ * element that defined at a lower priority than this directive. When used, the `template`
+ * property is ignored.
+ * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
+ *
+ * **Mult-slot transclusion** is declared by providing an object for the `transclude` property.
+ *
+ * This object is a map where the keys are the name of the slot to fill and the value is an element selector
+ * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
+ * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
+ *
+ * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
+ *
+ * If the element selector is prefixed with a `?` then that slot is optional.
+ *
+ * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
+ * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
+ *
+ * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
+ * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
+ * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
+ * injectable into the directive's controller.
+ *
+ *
* #### Transclusion Functions
*
* When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
* function** to the directive's `link` function and `controller`. This transclusion function is a special
* **linking function** that will return the compiled contents linked to a new transclusion scope.
@@ -6946,11 +7098,11 @@
* When you call a transclusion function you can pass in a **clone attach function**. This function accepts
* two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
* content and the `scope` is the newly created transclusion scope, to which the clone is bound.
*
* <div class="alert alert-info">
- * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
+ * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
* since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
* </div>
*
* It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
* attach function**:
@@ -6978,11 +7130,11 @@
* (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
* then you are also responsible for calling `$destroy` on the transclusion scope.
* </div>
*
* The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
- * automatically destroy their transluded clones as necessary so you do not need to worry about this if
+ * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
* you are simply using {@link ngTransclude} to inject the transclusion into your directive.
*
*
* #### Transclusion Scopes
*
@@ -7023,14 +7175,13 @@
* ### Attributes
*
* The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
* `link()` or `compile()` functions. It has a variety of uses.
*
- * accessing *Normalized attribute names:*
- * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
- * the attributes object allows for normalized access to
- * the attributes.
+ * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways:
+ * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access
+ * to the attributes.
*
* * *Directive inter-communication:* All directives share the same instance of the attributes
* object which allows the directives to use the attributes object as inter directive
* communication.
*
@@ -7216,11 +7367,11 @@
// The assumption is that future DOM event attribute names will begin with
// 'on' and be composed of only English letters.
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
function parseIsolateBindings(scope, directiveName, isController) {
- var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
+ var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
forEach(scope, function(definition, scopeName) {
var match = definition.match(LOCAL_REGEXP);
@@ -7303,12 +7454,12 @@
* Register a new directive with the compiler.
*
* @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
* will match as <code>ng-bind</code>), or an object map of directives where the keys are the
* names and the values are the factories.
- * @param {Function|Array} directiveFactory An injectable directive factory function. See
- * {@link guide/directive} for more info.
+ * @param {Function|Array} directiveFactory An injectable directive factory function. See the
+ * {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
* @returns {ng.$compileProvider} Self for chaining.
*/
this.directive = function registerDirective(name, directiveFactory) {
assertNotHasOwnProperty(name, 'directive');
if (isString(name)) {
@@ -7351,11 +7502,133 @@
forEach(name, reverseParams(registerDirective));
}
return this;
};
+ /**
+ * @ngdoc method
+ * @name $compileProvider#component
+ * @module ng
+ * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`)
+ * @param {Object} options Component definition object (a simplified
+ * {@link ng.$compile#directive-definition-object directive definition object}),
+ * with the following properties (all optional):
+ *
+ * - `controller` – `{(string|function()=}` – controller constructor function that should be
+ * associated with newly created scope or the name of a {@link ng.$compile#-controller-
+ * registered controller} if passed as a string. An empty `noop` function by default.
+ * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
+ * If present, the controller will be published to scope under the `controllerAs` name.
+ * If not present, this will default to be `$ctrl`.
+ * - `template` – `{string=|function()=}` – html template as a string or a function that
+ * returns an html template as a string which should be used as the contents of this component.
+ * Empty string by default.
+ *
+ * If `template` is a function, then it is {@link auto.$injector#invoke injected} with
+ * the following locals:
+ *
+ * - `$element` - Current element
+ * - `$attrs` - Current attributes object for the element
+ *
+ * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
+ * template that should be used as the contents of this component.
+ *
+ * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
+ * the following locals:
+ *
+ * - `$element` - Current element
+ * - `$attrs` - Current attributes object for the element
+ *
+ * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
+ * Component properties are always bound to the component controller and not to the scope.
+ * See {@link ng.$compile#-bindtocontroller- `bindToController`}.
+ * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
+ * Disabled by default.
+ * - `$...` – `{function()=}` – additional annotations to provide to the directive factory function.
+ *
+ * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
+ * @description
+ * Register a **component definition** with the compiler. This is a shorthand for registering a special
+ * type of directive, which represents a self-contained UI component in your application. Such components
+ * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
+ *
+ * Component definitions are very simple and do not require as much configuration as defining general
+ * directives. Component definitions usually consist only of a template and a controller backing it.
+ *
+ * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
+ * `bindToController`. They always have **isolate scope** and are restricted to elements.
+ *
+ * Here are a few examples of how you would usually define components:
+ *
+ * ```js
+ * var myMod = angular.module(...);
+ * myMod.component('myComp', {
+ * template: '<div>My name is {{$ctrl.name}}</div>',
+ * controller: function() {
+ * this.name = 'shahar';
+ * }
+ * });
+ *
+ * myMod.component('myComp', {
+ * template: '<div>My name is {{$ctrl.name}}</div>',
+ * bindings: {name: '@'}
+ * });
+ *
+ * myMod.component('myComp', {
+ * templateUrl: 'views/my-comp.html',
+ * controller: 'MyCtrl as ctrl',
+ * bindings: {name: '@'}
+ * });
+ *
+ * ```
+ * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
+ *
+ * <br />
+ * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
+ */
+ this.component = function registerComponent(name, options) {
+ var controller = options.controller || function() {};
+ function factory($injector) {
+ function makeInjectable(fn) {
+ if (isFunction(fn) || isArray(fn)) {
+ return function(tElement, tAttrs) {
+ return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
+ };
+ } else {
+ return fn;
+ }
+ }
+
+ var template = (!options.template && !options.templateUrl ? '' : options.template);
+ return {
+ controller: controller,
+ controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
+ template: makeInjectable(template),
+ templateUrl: makeInjectable(options.templateUrl),
+ transclude: options.transclude,
+ scope: {},
+ bindToController: options.bindings || {},
+ restrict: 'E',
+ require: options.require
+ };
+ }
+
+ // Copy any annotation properties (starting with $) over to the factory function
+ // These could be used by libraries such as the new component router
+ forEach(options, function(val, key) {
+ if (key.charAt(0) === '$') {
+ factory[key] = val;
+ }
+ });
+
+ factory.$inject = ['$injector'];
+
+ return this.directive(name, factory);
+ };
+
+
/**
* @ngdoc method
* @name $compileProvider#aHrefSanitizationWhitelist
* @kind function
*
@@ -7448,10 +7721,12 @@
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
+ var SIMPLE_ATTR_NAME = /^\w/;
+ var specialAttrHolder = document.createElement('div');
var Attributes = function(element, attributesToCopy) {
if (attributesToCopy) {
var keys = Object.keys(attributesToCopy);
var i, l, key;
@@ -7583,11 +7858,11 @@
}
}
nodeName = nodeName_(this.$$element);
- if ((nodeName === 'a' && key === 'href') ||
+ if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
(nodeName === 'img' && key === 'src')) {
// sanitize a[href] and img[src] values
this[key] = value = $$sanitizeUri(value, key === 'src');
} else if (nodeName === 'img' && key === 'srcset') {
// sanitize img[srcset] values
@@ -7627,11 +7902,15 @@
if (writeAttr !== false) {
if (value === null || isUndefined(value)) {
this.$$element.removeAttr(attrName);
} else {
- this.$$element.attr(attrName, value);
+ if (SIMPLE_ATTR_NAME.test(attrName)) {
+ this.$$element.attr(attrName, value);
+ } else {
+ setSpecialAttr(this.$$element[0], attrName, value);
+ }
}
}
// fire observers
var $$observers = this.$$observers;
@@ -7681,10 +7960,22 @@
arrayRemove(listeners, fn);
};
}
};
+ function setSpecialAttr(element, attrName, value) {
+ // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
+ // so we have to jump through some hoops to get such an attribute
+ // https://github.com/angular/angular.js/pull/13318
+ specialAttrHolder.innerHTML = "<span " + attrName + ">";
+ var attributes = specialAttrHolder.firstChild.attributes;
+ var attribute = attributes[0];
+ // We have to remove the attribute from its container element before we can add it to the destination element
+ attributes.removeNamedItem(attribute.name);
+ attribute.value = value;
+ element.attributes.setNamedItem(attribute);
+ }
function safeAddClass($element, className) {
try {
$element.addClass(className);
} catch (e) {
@@ -7694,11 +7985,11 @@
}
var startSymbol = $interpolate.startSymbol(),
endSymbol = $interpolate.endSymbol(),
- denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
+ denormalizeTemplate = (startSymbol == '{{' && endSymbol == '}}')
? identity
: function denormalizeTemplate(template) {
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
},
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
@@ -7738,17 +8029,23 @@
if (!($compileNodes instanceof jqLite)) {
// jquery always rewraps, whereas we need to preserve the original selector so that we can
// modify it.
$compileNodes = jqLite($compileNodes);
}
+
+ var NOT_EMPTY = /\S+/;
+
// 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) {
- if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
- $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
+ for (var i = 0, len = $compileNodes.length; i < len; i++) {
+ var domNode = $compileNodes[i];
+
+ if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) {
+ jqLiteWrapNode(domNode, $compileNodes[i] = document.createElement('span'));
}
- });
+ }
+
var compositeLinkFn =
compileNodes($compileNodes, transcludeFn, $compileNodes,
maxPriority, ignoreDirective, previousCompileContext);
compile.$$addScopeClass($compileNodes);
var namespace = null;
@@ -7815,11 +8112,11 @@
// TODO: Make this detect MathML as well...
var node = parentElement && parentElement[0];
if (!node) {
return 'html';
} else {
- return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
+ return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
}
}
/**
* Compile function matches each node in nodeList against the directives. Once all directives
@@ -7949,10 +8246,21 @@
transcludeControllers: controllers,
futureParentElement: futureParentElement
});
};
+ // We need to attach the transclusion slots onto the `boundTranscludeFn`
+ // so that they are available inside the `controllersBoundTransclude` function
+ var boundSlots = boundTranscludeFn.$$slots = createMap();
+ for (var slotName in transcludeFn.$$slots) {
+ if (transcludeFn.$$slots[slotName]) {
+ boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
+ } else {
+ boundSlots[slotName] = null;
+ }
+ }
+
return boundTranscludeFn;
}
/**
* Looks for directives on the given node and adds them to the directive collection which is
@@ -8108,10 +8416,41 @@
return linkFn(scope, element, attrs, controllers, transcludeFn);
};
}
/**
+ * A function generator that is used to support both eager and lazy compilation
+ * linking function.
+ * @param eager
+ * @param $compileNodes
+ * @param transcludeFn
+ * @param maxPriority
+ * @param ignoreDirective
+ * @param previousCompileContext
+ * @returns {Function}
+ */
+ function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
+ if (eager) {
+ return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
+ }
+
+ var compiled;
+
+ return function() {
+ if (!compiled) {
+ compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
+
+ // Null out all of these references in order to make them eligible for garbage collection
+ // since this is a potentially long lived closure
+ $compileNodes = transcludeFn = previousCompileContext = null;
+ }
+
+ return compiled.apply(this, arguments);
+ };
+ }
+
+ /**
* Once the directives have been collected, their compile functions are executed. This method
* is responsible for inlining directive templates as well as terminating the application
* of the directives if the terminal directive has been reached.
*
* @param {Array} directives Array of collected directives to execute their compile function.
@@ -8151,10 +8490,12 @@
directiveName,
$template,
replaceDirective = originalReplaceDirective,
childTranscludeFn = transcludeFn,
linkFn,
+ didScanForMultipleTransclusion = false,
+ mightHaveMultipleTransclusionError = false,
directiveValue;
// executes all directives on the current element
for (var i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
@@ -8193,10 +8534,31 @@
newScopeDirective = newScopeDirective || directive;
}
directiveName = directive.name;
+ // If we encounter a condition that can result in transclusion on the directive,
+ // then scan ahead in the remaining directives for others that may cause a multiple
+ // transclusion error to be thrown during the compilation process. If a matching directive
+ // is found, then we know that when we encounter a transcluded directive, we need to eagerly
+ // compile the `transclude` function rather than doing it lazily in order to throw
+ // exceptions at the correct time
+ if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
+ || (directive.transclude && !directive.$$tlb))) {
+ var candidateDirective;
+
+ for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) {
+ if ((candidateDirective.transclude && !candidateDirective.$$tlb)
+ || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
+ mightHaveMultipleTransclusionError = true;
+ break;
+ }
+ }
+
+ didScanForMultipleTransclusion = true;
+ }
+
if (!directive.templateUrl && directive.controller) {
directiveValue = directive.controller;
controllerDirectives = controllerDirectives || createMap();
assertNoDuplicate("'" + directiveName + "' controller",
controllerDirectives[directiveName], directive, $compileNode);
@@ -8222,11 +8584,11 @@
jqLite(document.createComment(' ' + directiveName + ': ' +
templateAttrs[directiveName] + ' '));
compileNode = $compileNode[0];
replaceWith(jqCollection, sliceArgs($template), compileNode);
- childTranscludeFn = compile($template, transcludeFn, terminalPriority,
+ childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
replaceDirective && replaceDirective.name, {
// Don't pass in:
// - controllerDirectives - otherwise we'll create duplicates controllers
// - newIsolateScopeDirective or templateDirective - combining templates with
// element transclusion doesn't make sense.
@@ -8234,14 +8596,73 @@
// We need only nonTlbTranscludeDirective so that we prevent putting transclusion
// on the same element more than once.
nonTlbTranscludeDirective: nonTlbTranscludeDirective
});
} else {
+
+ var slots = createMap();
+
$template = jqLite(jqLiteClone(compileNode)).contents();
+
+ if (isObject(directiveValue)) {
+
+ // We have transclusion slots,
+ // collect them up, compile them and store their transclusion functions
+ $template = [];
+
+ var slotMap = createMap();
+ var filledSlots = createMap();
+
+ // Parse the element selectors
+ forEach(directiveValue, function(elementSelector, slotName) {
+ // If an element selector starts with a ? then it is optional
+ var optional = (elementSelector.charAt(0) === '?');
+ elementSelector = optional ? elementSelector.substring(1) : elementSelector;
+
+ slotMap[elementSelector] = slotName;
+
+ // We explicitly assign `null` since this implies that a slot was defined but not filled.
+ // Later when calling boundTransclusion functions with a slot name we only error if the
+ // slot is `undefined`
+ slots[slotName] = null;
+
+ // filledSlots contains `true` for all slots that are either optional or have been
+ // filled. This is used to check that we have not missed any required slots
+ filledSlots[slotName] = optional;
+ });
+
+ // Add the matching elements into their slot
+ forEach($compileNode.contents(), function(node) {
+ var slotName = slotMap[directiveNormalize(nodeName_(node))];
+ if (slotName) {
+ filledSlots[slotName] = true;
+ slots[slotName] = slots[slotName] || [];
+ slots[slotName].push(node);
+ } else {
+ $template.push(node);
+ }
+ });
+
+ // Check for required slots that were not filled
+ forEach(filledSlots, function(filled, slotName) {
+ if (!filled) {
+ throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
+ }
+ });
+
+ for (var slotName in slots) {
+ if (slots[slotName]) {
+ // Only define a transclusion function if the slot was filled
+ slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
+ }
+ }
+ }
+
$compileNode.empty(); // clear contents
- childTranscludeFn = compile($template, transcludeFn, undefined,
+ childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
+ childTranscludeFn.$$slots = slots;
}
}
if (directive.template) {
hasTemplate = true;
@@ -8400,10 +8821,15 @@
} else if (isArray(require)) {
value = [];
for (var i = 0, ii = require.length; i < ii; i++) {
value[i] = getControllers(directiveName, require[i], $element, elementControllers);
}
+ } else if (isObject(require)) {
+ value = {};
+ forEach(require, function(controller, property) {
+ value[property] = getControllers(directiveName, controller, $element, elementControllers);
+ });
}
return value || null;
}
@@ -8437,11 +8863,11 @@
}
return elementControllers;
}
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
- var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
+ var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
attrs, removeScopeBindingWatches, removeControllerBindingWatches;
if (compileNode === linkNode) {
attrs = templateAttrs;
$element = templateAttrs.$$element;
@@ -8460,10 +8886,14 @@
if (boundTranscludeFn) {
// track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
transcludeFn = controllersBoundTransclude;
transcludeFn.$$boundTransclude = boundTranscludeFn;
+ // expose the slots on the `$transclude` function
+ transcludeFn.isSlotFilled = function(slotName) {
+ return !!boundTranscludeFn.$$slots[slotName];
+ };
}
if (controllerDirectives) {
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
}
@@ -8504,10 +8934,25 @@
removeControllerBindingWatches =
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
}
}
+ // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
+ forEach(controllerDirectives, function(controllerDirective, name) {
+ var require = controllerDirective.require;
+ if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
+ extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
+ }
+ });
+
+ // Trigger the `$onInit` method on all controllers that have one
+ forEach(elementControllers, function(controller) {
+ if (isFunction(controller.instance.$onInit)) {
+ controller.instance.$onInit();
+ }
+ });
+
// PRELINKING
for (i = 0, ii = preLinkFns.length; i < ii; i++) {
linkFn = preLinkFns[i];
invokeLinkFn(linkFn,
linkFn.isolateScope ? isolateScope : scope,
@@ -8539,15 +8984,15 @@
);
}
// This is the function that is injected as `$transclude`.
// Note: all arguments are optional!
- function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
+ function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
var transcludeControllers;
-
// No scope passed in:
if (!isScope(scope)) {
+ slotName = futureParentElement;
futureParentElement = cloneAttachFn;
cloneAttachFn = scope;
scope = undefined;
}
@@ -8555,11 +9000,27 @@
transcludeControllers = elementControllers;
}
if (!futureParentElement) {
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
}
- return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
+ if (slotName) {
+ // slotTranscludeFn can be one of three things:
+ // * a transclude function - a filled slot
+ // * `null` - an optional slot that was not filled
+ // * `undefined` - a slot that was not declared (i.e. invalid)
+ var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
+ if (slotTranscludeFn) {
+ return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
+ } else if (isUndefined(slotTranscludeFn)) {
+ throw $compileMinErr('noslot',
+ 'No parent directive that requires a transclusion with slot name "{0}". ' +
+ 'Element: {1}',
+ slotName, startingTag($element));
+ }
+ } else {
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
+ }
}
}
}
// Depending upon the context in which a directive finds itself it might need to have a new isolated
@@ -8987,45 +9448,37 @@
if (parent) {
parent.replaceChild(newNode, firstElementToRemove);
}
- // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
+ // Append all the `elementsToRemove` to a fragment. This will...
+ // - remove them from the DOM
+ // - allow them to still be traversed with .nextSibling
+ // - allow a single fragment.qSA to fetch all elements being removed
var fragment = document.createDocumentFragment();
- fragment.appendChild(firstElementToRemove);
+ for (i = 0; i < removeCount; i++) {
+ fragment.appendChild(elementsToRemove[i]);
+ }
if (jqLite.hasData(firstElementToRemove)) {
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
// data here because there's no public interface in jQuery to do that and copying over
// event listeners (which is the main use of private data) wouldn't work anyway.
jqLite.data(newNode, jqLite.data(firstElementToRemove));
- // Remove data of the replaced element. We cannot just call .remove()
- // on the element it since that would deallocate scope that is needed
- // for the new node. Instead, remove the data "manually".
- if (!jQuery) {
- delete jqLite.cache[firstElementToRemove[jqLite.expando]];
- } else {
- // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
- // the replaced element. The cleanData version monkey-patched by Angular would cause
- // the scope to be trashed and we do need the very same scope to work with the new
- // element. However, we cannot just cache the non-patched version and use it here as
- // that would break if another library patches the method after Angular does (one
- // example is jQuery UI). Instead, set a flag indicating scope destroying should be
- // skipped this one time.
- skipDestroyOnNextJQueryCleanData = true;
- jQuery.cleanData([firstElementToRemove]);
- }
+ // Remove $destroy event listeners from `firstElementToRemove`
+ jqLite(firstElementToRemove).off('$destroy');
}
- for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
- var element = elementsToRemove[k];
- jqLite(element).remove(); // must do this way to clean up expando
- fragment.appendChild(element);
- delete elementsToRemove[k];
- }
+ // Cleanup any data/listeners on the elements and children.
+ // This includes invoking the $destroy event on any elements with listeners.
+ jqLite.cleanData(fragment.querySelectorAll('*'));
+ // Update the jqLite collection to only contain the `newNode`
+ for (i = 1; i < removeCount; i++) {
+ delete elementsToRemove[i];
+ }
elementsToRemove[0] = newNode;
elementsToRemove.length = 1;
}
@@ -9050,11 +9503,11 @@
forEach(bindings, function(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
- parentGet, parentSet, compare;
+ parentGet, parentSet, compare, removeWatch;
switch (mode) {
case '@':
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
@@ -9064,14 +9517,19 @@
if (isString(value)) {
destination[scopeName] = value;
}
});
attrs.$$observers[attrName].$$scope = scope;
- if (isString(attrs[attrName])) {
+ lastValue = attrs[attrName];
+ if (isString(lastValue)) {
// If the attribute has been provided then we trigger an interpolation to ensure
// the value is there for use in the link fn
- destination[scopeName] = $interpolate(attrs[attrName])(scope);
+ destination[scopeName] = $interpolate(lastValue)(scope);
+ } else if (isBoolean(lastValue)) {
+ // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted
+ // the value to boolean rather than a string, so we special case this situation
+ destination[scopeName] = lastValue;
}
break;
case '=':
if (!hasOwnProperty.call(attrs, attrName)) {
@@ -9088,12 +9546,12 @@
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = destination[scopeName] = parentGet(scope);
throw $compileMinErr('nonassign',
- "Expression '{0}' used with directive '{1}' is non-assignable!",
- attrs[attrName], directive.name);
+ "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!",
+ attrs[attrName], attrName, directive.name);
};
lastValue = destination[scopeName] = parentGet(scope);
var parentValueWatch = function parentValueWatch(parentValue) {
if (!compare(parentValue, destination[scopeName])) {
// we are out of sync and need to copy
@@ -9106,19 +9564,36 @@
}
}
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
- var removeWatch;
if (definition.collection) {
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
removeWatchCollection.push(removeWatch);
break;
+ case '<':
+ if (!hasOwnProperty.call(attrs, attrName)) {
+ if (optional) break;
+ attrs[attrName] = void 0;
+ }
+ if (optional && !attrs[attrName]) break;
+
+ parentGet = $parse(attrs[attrName]);
+
+ destination[scopeName] = parentGet(scope);
+
+ removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
+ destination[scopeName] = newParentValue;
+ }, parentGet.literal);
+
+ removeWatchCollection.push(removeWatch);
+ break;
+
case '&':
// Don't assign Object.prototype method to scope
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
// Don't assign noop to destination if expression is not valid
@@ -10034,11 +10509,11 @@
* The defaults can also be set at runtime via the `$http.defaults` object in the same
* fashion. For example:
*
* ```
* module.run(function($http) {
- * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
+ * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
* });
* ```
*
* 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.
@@ -10262,17 +10737,17 @@
* Angular will strip the prefix, before processing the JSON.
*
*
* ### Cross Site Request Forgery (XSRF) Protection
*
- * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
- * an unauthorized site can gain your user's private data. Angular provides a mechanism
- * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
- * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
- * JavaScript that runs on your domain could read the cookie, your server can be assured that
- * the XHR came from JavaScript running on your domain. The header will not be set for
- * cross-domain requests.
+ * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
+ * which the attacker can trick an authenticated user into unknowingly executing actions on your
+ * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the
+ * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
+ * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the
+ * cookie, your server can be assured that the XHR came from JavaScript running on your domain.
+ * The header will not be set for cross-domain requests.
*
* To take advantage of this, your server needs to set a token in a JavaScript readable session
* cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
* server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
* that only JavaScript running on your domain could have sent the request. The token must be
@@ -10427,11 +10902,11 @@
</file>
</example>
*/
function $http(requestConfig) {
- if (!angular.isObject(requestConfig)) {
+ if (!isObject(requestConfig)) {
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
}
if (!isString(requestConfig.url)) {
throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url);
@@ -10547,11 +11022,11 @@
reqHeaders = extend({}, config.headers),
defHeaderName, lowercaseDefHeaderName, reqHeaderName;
defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
- // using for-in instead of forEach to avoid unecessary iteration after header has been found
+ // using for-in instead of forEach to avoid unnecessary iteration after header has been found
defaultHeadersIteration:
for (defHeaderName in defHeaders) {
lowercaseDefHeaderName = lowercase(defHeaderName);
for (reqHeaderName in reqHeaders) {
@@ -11046,10 +11521,18 @@
*
* @description
*
* Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
*
+ * <div class="alert alert-danger">
+ * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
+ * template within a Python Jinja template (or any other template language). Mixing templating
+ * languages is **very dangerous**. The embedding template language will not safely escape Angular
+ * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
+ * security bugs!
+ * </div>
+ *
* @example
<example name="custom-interpolation-markup" module="customInterpolationApp">
<file name="index.html">
<script>
var customInterpolationApp = angular.module('customInterpolationApp', []);
@@ -11146,10 +11629,19 @@
}
return value;
}
+ //TODO: this is the same as the constantWatchDelegate in parse.js
+ function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
+ var unwatch;
+ return unwatch = scope.$watch(function constantInterpolateWatch(scope) {
+ unwatch();
+ return constantInterp(scope);
+ }, listener, objectEquality);
+ }
+
/**
* @ngdoc service
* @name $interpolate
* @kind function
*
@@ -11241,10 +11733,23 @@
* interpolated string. The function has these parameters:
*
* - `context`: evaluation context for all expressions embedded in the interpolated text
*/
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
+ // Provide a quick exit and simplified result function for text with no interpolation
+ if (!text.length || text.indexOf(startSymbol) === -1) {
+ var constantInterp;
+ if (!mustHaveExpression) {
+ var unescapedText = unescapeText(text);
+ constantInterp = valueFn(unescapedText);
+ constantInterp.exp = text;
+ constantInterp.expressions = [];
+ constantInterp.$$watchDelegate = constantWatchDelegate;
+ }
+ return constantInterp;
+ }
+
allOrNothing = !!allOrNothing;
var startIndex,
endIndex,
index = 0,
expressions = [],
@@ -11377,12 +11882,12 @@
return $interpolate;
}];
}
function $IntervalProvider() {
- this.$get = ['$rootScope', '$window', '$q', '$$q',
- function($rootScope, $window, $q, $$q) {
+ this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
+ function($rootScope, $window, $q, $$q, $browser) {
var intervals = {};
/**
* @ngdoc service
@@ -11519,15 +12024,16 @@
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise;
count = isDefined(count) ? count : 0;
- promise.then(null, null, (!hasParams) ? fn : function() {
- fn.apply(null, args);
- });
-
promise.$$intervalId = setInterval(function tick() {
+ if (skipApply) {
+ $browser.defer(callback);
+ } else {
+ $rootScope.$evalAsync(callback);
+ }
deferred.notify(iteration++);
if (count > 0 && iteration >= count) {
deferred.resolve(iteration);
clearInterval(promise.$$intervalId);
@@ -11539,10 +12045,18 @@
}, delay);
intervals[promise.$$intervalId] = deferred;
return promise;
+
+ function callback() {
+ if (!hasParams) {
+ fn(iteration);
+ } else {
+ fn.apply(null, args);
+ }
+ }
}
/**
* @ngdoc method
@@ -12778,27 +13292,26 @@
+ 'Expression: {0}', fullExpression);
}
return name;
}
-function getStringValue(name, fullExpression) {
- // From the JavaScript docs:
+function getStringValue(name) {
// Property names must be strings. This means that non-string objects cannot be used
// as keys in an object. Any non-string object, including a number, is typecasted
// into a string via the toString method.
+ // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names
//
- // So, to ensure that we are checking the same `name` that JavaScript would use,
- // we cast it to a string, if possible.
- // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
- // this is, this will handle objects that misbehave.
- name = name + '';
- if (!isString(name)) {
- throw $parseMinErr('iseccst',
- 'Cannot convert object to primitive value! '
- + 'Expression: {0}', fullExpression);
- }
- return name;
+ // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it
+ // to a string. It's not always possible. If `name` is an object and its `toString` method is
+ // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown:
+ //
+ // TypeError: Cannot convert object to primitive value
+ //
+ // For performance reasons, we don't catch this error here and allow it to propagate up the call
+ // stack. Note that you'll get the same error in JavaScript if you try to access a property using
+ // such a 'broken' object as a key.
+ return name + '';
}
function ensureSafeObject(obj, fullExpression) {
// nifty check if obj is Function that is fast and works across iframes and other contexts
if (obj) {
@@ -13055,10 +13568,11 @@
AST.Literal = 'Literal';
AST.ArrayExpression = 'ArrayExpression';
AST.Property = 'Property';
AST.ObjectExpression = 'ObjectExpression';
AST.ThisExpression = 'ThisExpression';
+AST.LocalsExpression = 'LocalsExpression';
// Internal use only
AST.NGValueParameter = 'NGValueParameter';
AST.prototype = {
@@ -13355,11 +13869,12 @@
constants: {
'true': { type: AST.Literal, value: true },
'false': { type: AST.Literal, value: false },
'null': { type: AST.Literal, value: null },
'undefined': {type: AST.Literal, value: undefined },
- 'this': {type: AST.ThisExpression }
+ 'this': {type: AST.ThisExpression },
+ '$locals': {type: AST.LocalsExpression }
}
};
function ifDefined(v, d) {
return typeof v !== 'undefined' ? v : d;
@@ -13475,10 +13990,14 @@
break;
case AST.ThisExpression:
ast.constant = false;
ast.toWatch = [];
break;
+ case AST.LocalsExpression:
+ ast.constant = false;
+ ast.toWatch = [];
+ break;
}
}
function getInputs(body) {
if (body.length != 1) return;
@@ -13718,10 +14237,13 @@
case AST.MemberExpression:
left = nameId && (nameId.context = this.nextId()) || this.nextId();
intoId = intoId || this.nextId();
self.recurse(ast.object, left, undefined, function() {
self.if_(self.notNull(left), function() {
+ if (create && create !== 1) {
+ self.addEnsureSafeAssignContext(left);
+ }
if (ast.computed) {
right = self.nextId();
self.recurse(ast.property, right);
self.getStringValue(right);
self.addEnsureSafeMemberName(right);
@@ -13841,10 +14363,14 @@
break;
case AST.ThisExpression:
this.assign(intoId, 's');
recursionFn('s');
break;
+ case AST.LocalsExpression:
+ this.assign(intoId, 'l');
+ recursionFn('l');
+ break;
case AST.NGValueParameter:
this.assign(intoId, 'v');
recursionFn('v');
break;
}
@@ -13948,11 +14474,11 @@
ensureSafeFunction: function(item) {
return 'ensureSafeFunction(' + item + ',text)';
},
getStringValue: function(item) {
- this.assign(item, 'getStringValue(' + item + ',text)');
+ this.assign(item, 'getStringValue(' + item + ')');
},
ensureSafeAssignContext: function(item) {
return 'ensureSafeAssignContext(' + item + ',text)';
},
@@ -14168,10 +14694,14 @@
};
case AST.ThisExpression:
return function(scope) {
return context ? {value: scope} : scope;
};
+ case AST.LocalsExpression:
+ return function(scope, locals) {
+ return context ? {value: locals} : locals;
+ };
case AST.NGValueParameter:
return function(scope, locals, assign, inputs) {
return context ? {value: assign} : assign;
};
}
@@ -14332,12 +14862,15 @@
var value;
if (lhs != null) {
rhs = right(scope, locals, assign, inputs);
rhs = getStringValue(rhs);
ensureSafeMemberName(rhs, expression);
- if (create && create !== 1 && lhs && !(lhs[rhs])) {
- lhs[rhs] = {};
+ if (create && create !== 1) {
+ ensureSafeAssignContext(lhs);
+ if (lhs && !(lhs[rhs])) {
+ lhs[rhs] = {};
+ }
}
value = lhs[rhs];
ensureSafeObject(value, expression);
}
if (context) {
@@ -14348,12 +14881,15 @@
};
},
nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
return function(scope, locals, assign, inputs) {
var lhs = left(scope, locals, assign, inputs);
- if (create && create !== 1 && lhs && !(lhs[right])) {
- lhs[right] = {};
+ if (create && create !== 1) {
+ ensureSafeAssignContext(lhs);
+ if (lhs && !(lhs[right])) {
+ lhs[right] = {};
+ }
}
var value = lhs != null ? lhs[right] : undefined;
if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
ensureSafeObject(value, expression);
}
@@ -14465,14 +15001,23 @@
},
$parseOptionsExpensive = {
csp: noUnsafeEval,
expensiveChecks: true
};
+ var runningChecksEnabled = false;
- return function $parse(exp, interceptorFn, expensiveChecks) {
+ $parse.$$runningExpensiveChecks = function() {
+ return runningChecksEnabled;
+ };
+
+ return $parse;
+
+ function $parse(exp, interceptorFn, expensiveChecks) {
var parsedExpression, oneTime, cacheKey;
+ expensiveChecks = expensiveChecks || runningChecksEnabled;
+
switch (typeof exp) {
case 'string':
exp = exp.trim();
cacheKey = exp;
@@ -14494,22 +15039,49 @@
parsedExpression.$$watchDelegate = parsedExpression.literal ?
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
} else if (parsedExpression.inputs) {
parsedExpression.$$watchDelegate = inputsWatchDelegate;
}
+ if (expensiveChecks) {
+ parsedExpression = expensiveChecksInterceptor(parsedExpression);
+ }
cache[cacheKey] = parsedExpression;
}
return addInterceptor(parsedExpression, interceptorFn);
case 'function':
return addInterceptor(exp, interceptorFn);
default:
return addInterceptor(noop, interceptorFn);
}
- };
+ }
+ function expensiveChecksInterceptor(fn) {
+ if (!fn) return fn;
+ expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate;
+ expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign);
+ expensiveCheckFn.constant = fn.constant;
+ expensiveCheckFn.literal = fn.literal;
+ for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) {
+ fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]);
+ }
+ expensiveCheckFn.inputs = fn.inputs;
+
+ return expensiveCheckFn;
+
+ function expensiveCheckFn(scope, locals, assign, inputs) {
+ var expensiveCheckOldValue = runningChecksEnabled;
+ runningChecksEnabled = true;
+ try {
+ return fn(scope, locals, assign, inputs);
+ } finally {
+ runningChecksEnabled = expensiveCheckOldValue;
+ }
+ }
+ }
+
function expressionInputDirtyCheck(newValue, oldValueOfValue) {
if (newValue == null || oldValueOfValue == null) { // null/undefined
return newValue === oldValueOfValue;
}
@@ -14621,17 +15193,13 @@
}
function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
var unwatch;
return unwatch = scope.$watch(function constantWatch(scope) {
- return parsedExpression(scope);
- }, function constantListener(value, old, scope) {
- if (isFunction(listener)) {
- listener.apply(this, arguments);
- }
unwatch();
- }, objectEquality);
+ return parsedExpression(scope);
+ }, listener, objectEquality);
}
function addInterceptor(parsedExpression, interceptorFn) {
if (!interceptorFn) return parsedExpression;
var watchDelegate = parsedExpression.$$watchDelegate;
@@ -14720,11 +15288,11 @@
* });
* ```
*
* Note: progress/notify callbacks are not currently supported via the ES6-style interface.
*
- * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
+ * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise.
*
* However, the more traditional CommonJS-style usage is still available, and documented below.
*
* [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
* interface for interacting with an object that represents the result of an action that is
@@ -14910,23 +15478,11 @@
* debugging purposes.
* @returns {object} Promise manager.
*/
function qFactory(nextTick, exceptionHandler) {
var $qMinErr = minErr('$q', TypeError);
- function callOnce(self, resolveFn, rejectFn) {
- var called = false;
- function wrap(fn) {
- return function(value) {
- if (called) return;
- called = true;
- fn.call(self, value);
- };
- }
- return [wrap(resolveFn), wrap(rejectFn)];
- }
-
/**
* @ngdoc method
* @name ng.$q#defer
* @kind function
*
@@ -14934,11 +15490,16 @@
* Creates a `Deferred` object which represents a task which will finish in the future.
*
* @returns {Deferred} Returns a new instance of deferred.
*/
var defer = function() {
- return new Deferred();
+ var d = new Deferred();
+ //Necessary to support unbound execution :/
+ d.resolve = simpleBind(d, d.resolve);
+ d.reject = simpleBind(d, d.reject);
+ d.notify = simpleBind(d, d.notify);
+ return d;
};
function Promise() {
this.$$state = { status: 0 };
}
@@ -15007,14 +15568,10 @@
nextTick(function() { processQueue(state); });
}
function Deferred() {
this.promise = new Promise();
- //Necessary to support unbound execution :/
- this.resolve = simpleBind(this, this.resolve);
- this.reject = simpleBind(this, this.reject);
- this.notify = simpleBind(this, this.notify);
}
extend(Deferred.prototype, {
resolve: function(val) {
if (this.promise.$$state.status) return;
@@ -15028,27 +15585,38 @@
}
},
$$resolve: function(val) {
- var then, fns;
-
- fns = callOnce(this, this.$$resolve, this.$$reject);
+ var then;
+ var that = this;
+ var done = false;
try {
if ((isObject(val) || isFunction(val))) then = val && val.then;
if (isFunction(then)) {
this.promise.$$state.status = -1;
- then.call(val, fns[0], fns[1], this.notify);
+ then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify));
} else {
this.promise.$$state.value = val;
this.promise.$$state.status = 1;
scheduleProcessQueue(this.promise.$$state);
}
} catch (e) {
- fns[1](e);
+ rejectPromise(e);
exceptionHandler(e);
}
+
+ function resolvePromise(val) {
+ if (done) return;
+ done = true;
+ that.$$resolve(val);
+ }
+ function rejectPromise(val) {
+ if (done) return;
+ done = true;
+ that.$$reject(val);
+ }
},
reject: function(reason) {
if (this.promise.$$state.status) return;
this.$$reject(reason);
@@ -15233,15 +15801,10 @@
var $Q = function Q(resolver) {
if (!isFunction(resolver)) {
throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
}
- if (!(this instanceof Q)) {
- // More useful when $Q is the Promise itself.
- return new Q(resolver);
- }
-
var deferred = new Deferred();
function resolveFn(value) {
deferred.resolve(value);
}
@@ -15253,10 +15816,14 @@
resolver(resolveFn, rejectFn);
return deferred.promise;
};
+ // Let's make the instanceof operator work for promises, so that
+ // `new $q(fn) instanceof $q` would evaluate to true.
+ $Q.prototype = Promise.prototype;
+
$Q.defer = defer;
$Q.reject = reject;
$Q.when = when;
$Q.resolve = resolve;
$Q.all = all;
@@ -15386,12 +15953,12 @@
}
ChildScope.prototype = parent;
return ChildScope;
}
- this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
- function($injector, $exceptionHandler, $parse, $browser) {
+ this.$get = ['$exceptionHandler', '$parse', '$browser',
+ function($exceptionHandler, $parse, $browser) {
function destroyChildScope($event) {
$event.currentScope.$$destroyed = true;
}
@@ -15671,11 +16238,11 @@
* of `watchExpression` changes.
*
* - `newVal` contains the current value of the `watchExpression`
* - `oldVal` contains the previous value of the `watchExpression`
* - `scope` refers to the current scope
- * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
+ * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
* comparing for reference equality.
* @returns {function()} Returns a deregistration function for this listener.
*/
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
var get = $parse(watchExp);
@@ -16036,11 +16603,11 @@
expect(scope.counter).toEqual(2);
* ```
*
*/
$digest: function() {
- var watch, value, last,
+ var watch, value, last, fn, get,
watchers,
length,
dirty, ttl = TTL,
next, current, target = this,
watchLog = [],
@@ -16082,19 +16649,21 @@
try {
watch = watchers[length];
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if (watch) {
- if ((value = watch.get(current)) !== (last = watch.last) &&
+ get = watch.get;
+ if ((value = get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value === 'number' && typeof last === 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value, null) : value;
- watch.fn(value, ((last === initWatchVal) ? value : last), current);
+ fn = watch.fn;
+ fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
watchLog[logIdx].push({
msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
@@ -16290,11 +16859,11 @@
$rootScope.$digest();
}
});
}
- asyncQueue.push({scope: this, expression: expr, locals: locals});
+ asyncQueue.push({scope: this, expression: $parse(expr), locals: locals});
},
$$postDigest: function(fn) {
postDigestQueue.push(fn);
},
@@ -16382,10 +16951,11 @@
* - `function(scope)`: execute the function with current `scope` parameter.
*/
$applyAsync: function(expr) {
var scope = this;
expr && applyAsyncQueue.push($applyAsyncExpression);
+ expr = $parse(expr);
scheduleApplyAsync();
function $applyAsyncExpression() {
scope.$eval(expr);
}
@@ -16885,17 +17455,19 @@
* @ngdoc method
* @name $sceDelegateProvider#resourceUrlWhitelist
* @kind function
*
* @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
- * provided. This must be an array or null. A snapshot of this array is used so further
- * changes to the array are ignored.
+ * provided. This must be an array or null. A snapshot of this array is used so further
+ * changes to the array are ignored.
*
- * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
- * allowed in this array.
+ * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
+ * allowed in this array.
*
- * Note: **an empty whitelist array will block all URLs**!
+ * <div class="alert alert-warning">
+ * **Note:** an empty whitelist array will block all URLs!
+ * </div>
*
* @return {Array} the currently set whitelist array.
*
* The **default value** when no whitelist has been explicitly set is `['self']` allowing only
* same origin resource requests.
@@ -16914,21 +17486,21 @@
* @ngdoc method
* @name $sceDelegateProvider#resourceUrlBlacklist
* @kind function
*
* @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
- * provided. This must be an array or null. A snapshot of this array is used so further
- * changes to the array are ignored.
+ * provided. This must be an array or null. A snapshot of this array is used so further
+ * changes to the array are ignored.
*
- * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
- * allowed in this array.
+ * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
+ * allowed in this array.
*
- * The typical usage for the blacklist is to **block
- * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
- * these would otherwise be trusted but actually return content from the redirected domain.
+ * The typical usage for the blacklist is to **block
+ * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
+ * these would otherwise be trusted but actually return content from the redirected domain.
*
- * Finally, **the blacklist overrides the whitelist** and has the final say.
+ * Finally, **the blacklist overrides the whitelist** and has the final say.
*
* @return {Array} the currently set blacklist array.
*
* The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
* is no blacklist.)
@@ -17083,10 +17655,15 @@
* @description
* Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
* returns the originally supplied value if the queried context type is a supertype of the
* created type. If this condition isn't satisfied, throws an exception.
*
+ * <div class="alert alert-danger">
+ * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting
+ * (XSS) vulnerability in your application.
+ * </div>
+ *
* @param {string} type The kind of context in which this value is to be used.
* @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
* `$sceDelegate.trustAs`} call.
* @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
* `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
@@ -17890,30 +18467,67 @@
}
var $compileMinErr = minErr('$compile');
/**
- * @ngdoc service
- * @name $templateRequest
- *
+ * @ngdoc provider
+ * @name $templateRequestProvider
* @description
- * The `$templateRequest` service runs security checks then downloads the provided template using
- * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
- * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
- * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
- * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
- * when `tpl` is of type string and `$templateCache` has the matching entry.
+ * Used to configure the options passed to the {@link $http} service when making a template request.
*
- * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
- * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
- *
- * @return {Promise} a promise for the HTTP response data of the given URL.
- *
- * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
+ * For example, it can be used for specifying the "Accept" header that is sent to the server, when
+ * requesting a template.
*/
function $TemplateRequestProvider() {
+
+ var httpOptions;
+
+ /**
+ * @ngdoc method
+ * @name $templateRequestProvider#httpOptions
+ * @description
+ * The options to be passed to the {@link $http} service when making the request.
+ * You can use this to override options such as the "Accept" header for template requests.
+ *
+ * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the
+ * options if not overridden here.
+ *
+ * @param {string=} value new value for the {@link $http} options.
+ * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter.
+ */
+ this.httpOptions = function(val) {
+ if (val) {
+ httpOptions = val;
+ return this;
+ }
+ return httpOptions;
+ };
+
+ /**
+ * @ngdoc service
+ * @name $templateRequest
+ *
+ * @description
+ * The `$templateRequest` service runs security checks then downloads the provided template using
+ * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
+ * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
+ * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
+ * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
+ * when `tpl` is of type string and `$templateCache` has the matching entry.
+ *
+ * If you want to pass custom options to the `$http` service, such as setting the Accept header you
+ * can configure this via {@link $templateRequestProvider#httpOptions}.
+ *
+ * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
+ * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
+ *
+ * @return {Promise} a promise for the HTTP response data of the given URL.
+ *
+ * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
+ */
this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
+
function handleRequestFn(tpl, ignoreRequestError) {
handleRequestFn.totalPendingRequests++;
// We consider the template cache holds only trusted templates, so
// there's no need to go through whitelisting again for keys that already
@@ -17932,16 +18546,14 @@
});
} else if (transformResponse === defaultHttpResponseTransform) {
transformResponse = null;
}
- var httpOptions = {
- cache: $templateCache,
- transformResponse: transformResponse
- };
-
- return $http.get(tpl, httpOptions)
+ return $http.get(tpl, extend({
+ cache: $templateCache,
+ transformResponse: transformResponse
+ }, httpOptions))
['finally'](function() {
handleRequestFn.totalPendingRequests--;
})
.then(function(response) {
$templateCache.put(tpl, response.data);
@@ -19392,17 +20004,17 @@
}
}
var dateTimezoneOffset = date.getTimezoneOffset();
if (timezone) {
- dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
+ dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
date = convertTimezoneToLocal(date, timezone, true);
}
forEach(parts, function(value) {
fn = DATE_FORMATS[value];
text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
- : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
+ : value === "''" ? "'" : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
});
return text;
};
}
@@ -19602,12 +20214,13 @@
*
* @description
* Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
* for strings and numerically for numbers. Note: if you notice numbers are not being sorted
* as expected, make sure they are actually being saved as numbers and not strings.
+ * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported.
*
- * @param {Array} array The array to sort.
+ * @param {Array} array The array (or array-like object) to sort.
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
* used by the comparator to determine the order of elements.
*
* Can be one of:
*
@@ -19790,11 +20403,14 @@
*/
orderByFilter.$inject = ['$parse'];
function orderByFilter($parse) {
return function(array, sortPredicate, reverseOrder) {
- if (!(isArrayLike(array))) return array;
+ if (array == null) return array;
+ if (!isArrayLike(array)) {
+ throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array);
+ }
if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
var predicates = processPredicates(sortPredicate, reverseOrder);
@@ -20500,11 +21116,11 @@
* is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
* state.
*
* However, if the method is used programmatically, for example by adding dynamically created controls,
* or controls that have been previously removed without destroying their corresponding DOM element,
- * it's the developers responsiblity to make sure the current state propagates to the parent form.
+ * it's the developers responsibility to make sure the current state propagates to the parent form.
*
* For example, if an input control is added that is already `$dirty` and has `$error` properties,
* calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
*/
form.$addControl = function(control) {
@@ -20977,12 +21593,12 @@
* 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.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * 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 evaluates to 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$')`.<br />
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
@@ -21265,11 +21881,11 @@
* @ngdoc input
* @name input[time]
*
* @description
* Input with time 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
+ * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
* 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.
@@ -21612,12 +22228,12 @@
* 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.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * 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 evaluates to 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$')`.<br />
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
@@ -21710,12 +22326,12 @@
* 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.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * 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 evaluates to 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$')`.<br />
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
@@ -21809,12 +22425,12 @@
* 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.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * 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 evaluates to 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$')`.<br />
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
@@ -22270,15 +22886,11 @@
var node = element[0];
var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
if (nativeValidation) {
ctrl.$parsers.push(function(value) {
var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
- // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
- // - also sets validity.badInput (should only be validity.typeMismatch).
- // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
- // - can ignore this case as we can still read out the erroneous email...
- return validity.badInput && !validity.typeMismatch ? undefined : value;
+ return validity.badInput || validity.typeMismatch ? undefined : value;
});
}
}
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
@@ -22446,12 +23058,12 @@
* @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. 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 ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * 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 evaluates to 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$')`.<br />
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
@@ -22485,12 +23097,12 @@
* @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. 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 ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * 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 evaluates to 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$')`.<br />
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
@@ -23712,11 +24324,11 @@
* You can specify which of the CSP related Angular features should be deactivated by providing
* a value for the `ng-csp` attribute. The options are as follows:
*
* * no-inline-style: this stops Angular from injecting CSS styles into the DOM
*
- * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
+ * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings
*
* You can use these values in the following combinations:
*
*
* * No declaration means that Angular will assume that you can do inline styles, but it will do
@@ -23729,11 +24341,11 @@
*
* * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
* inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
*
* * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
- * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
+ * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
*
* * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
* styles nor use eval, which is the same as an empty: ng-csp.
* E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
*
@@ -24761,11 +25373,11 @@
return {
restrict: 'ECA',
priority: -400,
require: 'ngInclude',
link: function(scope, $element, $attr, ctrl) {
- if (/SVG/.test($element[0].toString())) {
+ if (toString.call($element[0]).match(/SVG/)) {
// WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
// support innerHTML, so detect this here and try to generate the contents
// specially.
$element.empty();
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
@@ -24990,11 +25602,13 @@
INVALID_CLASS = 'ng-invalid',
PRISTINE_CLASS = 'ng-pristine',
DIRTY_CLASS = 'ng-dirty',
UNTOUCHED_CLASS = 'ng-untouched',
TOUCHED_CLASS = 'ng-touched',
- PENDING_CLASS = 'ng-pending';
+ PENDING_CLASS = 'ng-pending',
+ EMPTY_CLASS = 'ng-empty',
+ NOT_EMPTY_CLASS = 'ng-not-empty';
var ngModelMinErr = minErr('ngModel');
/**
* @ngdoc type
@@ -25294,10 +25908,21 @@
*/
this.$isEmpty = function(value) {
return isUndefined(value) || value === '' || value === null || value !== value;
};
+ this.$$updateEmptyClasses = function(value) {
+ if (ctrl.$isEmpty(value)) {
+ $animate.removeClass($element, NOT_EMPTY_CLASS);
+ $animate.addClass($element, EMPTY_CLASS);
+ } else {
+ $animate.removeClass($element, EMPTY_CLASS);
+ $animate.addClass($element, NOT_EMPTY_CLASS);
+ }
+ };
+
+
var currentValidationRunId = 0;
/**
* @ngdoc method
* @name ngModel.NgModelController#$setValidity
@@ -25657,10 +26282,11 @@
// a native validator on the element. In this case the validation state may have changed even though
// the viewValue has stayed empty.
if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
return;
}
+ ctrl.$$updateEmptyClasses(viewValue);
ctrl.$$lastCommittedViewValue = viewValue;
// change to dirty
if (ctrl.$pristine) {
this.$setDirty();
@@ -25755,11 +26381,11 @@
* When used with standard inputs, the view value will always be a string (which is in some cases
* parsed into another type, such as a `Date` object for `input[date]`.)
* However, custom controls might also pass objects to this method. In this case, we should make
* a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
* perform a deep watch of objects, it only looks for a change of identity. If you only change
- * the property of the object then ngModel will not realise that the object has changed and
+ * the property of the object then ngModel will not realize that the object has changed and
* will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
* not change properties of the copy once it has been passed to `$setViewValue`.
* Otherwise you may cause the model value on the scope to change incorrectly.
*
* <div class="alert alert-info">
@@ -25839,10 +26465,11 @@
var viewValue = modelValue;
while (idx--) {
viewValue = formatters[idx](viewValue);
}
if (ctrl.$viewValue !== viewValue) {
+ ctrl.$$updateEmptyClasses(viewValue);
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
ctrl.$render();
ctrl.$$runValidators(modelValue, viewValue, noop);
}
@@ -25869,11 +26496,12 @@
*
* - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
* require.
* - Providing validation behavior (i.e. required, number, email, url).
* - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
- * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
+ * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
+ * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
* - Registering the control with its parent {@link ng.directive:form form}.
*
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
* current scope. If the property doesn't already exist on this scope, it will be created
* implicitly and added to the scope.
@@ -25897,10 +26525,26 @@
* - {@link input[month] month}
* - {@link input[week] week}
* - {@link ng.directive:select select}
* - {@link ng.directive:textarea textarea}
*
+ * # Complex Models (objects or collections)
+ *
+ * By default, `ngModel` watches the model by reference, not value. This is important to know when
+ * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the
+ * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered.
+ *
+ * The model must be assigned an entirely new object or collection before a re-rendering will occur.
+ *
+ * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression
+ * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or
+ * if the select is given the `multiple` attribute.
+ *
+ * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the
+ * first level of the object (or only changing the properties of an item in the collection if it's an array) will still
+ * not trigger a re-rendering of the model.
+ *
* # CSS classes
* The following CSS classes are added and removed on the associated input/select/textarea element
* depending on the validity of the model.
*
* - `ng-valid`: the model is valid
@@ -25910,17 +26554,20 @@
* - `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
+ * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
+ * by the {@link ngModel.NgModelController#$isEmpty} method
+ * - `ng-not-empty`: the view contains a non-empty value
*
* Keep in mind that ngAnimate can detect each of these classes when added and removed.
*
* ## Animation Hooks
*
* Animations within models are triggered when any of the associated CSS classes are added and removed
- * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
+ * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
* `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
* The animations that are triggered within ngModel are similar to how they work in ngClass and
* animations can be hooked into using CSS transitions, keyframes as well as JS animations.
*
* The following example shows a simple way to utilize CSS transitions to style an input element
@@ -26809,18 +27456,14 @@
// we can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
var optionTemplate = document.createElement('option'),
optGroupTemplate = document.createElement('optgroup');
-
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
- // if ngModel is not defined, we don't need to do anything
- var ngModelCtrl = ctrls[1];
- if (!ngModelCtrl) return;
-
var selectCtrl = ctrls[0];
+ var ngModelCtrl = ctrls[1];
var multiple = attr.multiple;
// The emptyOption allows the application developer to provide their own custom "empty"
// option when the viewValue does not match any of the option values.
var emptyOption;
@@ -27076,11 +27719,11 @@
options.items.forEach(function updateOption(option) {
var group;
var groupElement;
var optionElement;
- if (option.group) {
+ if (isDefined(option.group)) {
// This option is to live in a group
// See if we have already created this group
group = groupMap[option.group];
@@ -27150,11 +27793,11 @@
}
return {
restrict: 'A',
terminal: true,
- require: ['select', '?ngModel'],
+ require: ['select', 'ngModel'],
link: {
pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
// Deactivate the SelectController.register method to prevent
// option directives from accidentally registering themselves
// (and unwanted $destroy handlers etc.)
@@ -27378,11 +28021,11 @@
// Otherwise, check it against pluralization rules in $locale service.
count = $locale.pluralCat(count - offset);
}
// 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.
+ // In JS `NaN !== NaN`, so we have to explicitly check.
if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
watchRemover();
var whenExpFn = whensExpFns[count];
if (isUndefined(whenExpFn)) {
if (newVal != null) {
@@ -27495,11 +28138,11 @@
* <div class="alert alert-success">
* If you are working with objects that have an identifier property, you should track
* by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
* will not have to rebuild the DOM elements for items it has already rendered, even if the
* JavaScript objects in the collection have been substituted for new ones. For large collections,
- * this signifincantly improves rendering performance. If you don't have a unique identifier,
+ * this significantly improves rendering performance. If you don't have a unique identifier,
* `track by $index` can also provide a performance boost.
* </div>
* ```html
* <div ng-repeat="model in collection track by model.id">
* {{model.name}}
@@ -27572,10 +28215,12 @@
*
* **.leave** - when an item is removed from the list or when an item is filtered out
*
* **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
*
+ * See the example below for defining CSS animations with ngRepeat.
+ *
* @element ANY
* @scope
* @priority 1000
* @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
* formats are currently supported:
@@ -27624,26 +28269,15 @@
* (and not as operator, inside an expression).
*
* For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
*
* @example
- * This example initializes the scope to a list of names and
- * then uses `ngRepeat` to display every person:
- <example module="ngAnimate" deps="angular-animate.js" animations="true">
+ * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed
+ * results by name. New (entering) and removed (leaving) items are animated.
+ <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true">
<file name="index.html">
- <div ng-init="friends = [
- {name:'John', age:25, gender:'boy'},
- {name:'Jessie', age:30, gender:'girl'},
- {name:'Johanna', age:28, gender:'girl'},
- {name:'Joy', age:15, gender:'girl'},
- {name:'Mary', age:28, gender:'girl'},
- {name:'Peter', age:95, gender:'boy'},
- {name:'Sebastian', age:50, gender:'boy'},
- {name:'Erika', age:27, gender:'girl'},
- {name:'Patrick', age:40, gender:'boy'},
- {name:'Samantha', age:60, gender:'girl'}
- ]">
+ <div ng-controller="repeatController">
I have {{friends.length}} friends. They are:
<input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
<ul class="example-animate-container">
<li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
@@ -27652,21 +28286,37 @@
<strong>No results found...</strong>
</li>
</ul>
</div>
</file>
+ <file name="script.js">
+ angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
+ $scope.friends = [
+ {name:'John', age:25, gender:'boy'},
+ {name:'Jessie', age:30, gender:'girl'},
+ {name:'Johanna', age:28, gender:'girl'},
+ {name:'Joy', age:15, gender:'girl'},
+ {name:'Mary', age:28, gender:'girl'},
+ {name:'Peter', age:95, gender:'boy'},
+ {name:'Sebastian', age:50, gender:'boy'},
+ {name:'Erika', age:27, gender:'girl'},
+ {name:'Patrick', age:40, gender:'boy'},
+ {name:'Samantha', age:60, gender:'girl'}
+ ];
+ });
+ </file>
<file name="animations.css">
.example-animate-container {
background:white;
border:1px solid black;
list-style:none;
margin:0;
padding:0 10px;
}
.animate-repeat {
- line-height:40px;
+ line-height:30px;
list-style:none;
box-sizing:border-box;
}
.animate-repeat.ng-move,
@@ -27684,11 +28334,11 @@
.animate-repeat.ng-leave,
.animate-repeat.ng-move.ng-move-active,
.animate-repeat.ng-enter.ng-enter-active {
opacity:1;
- max-height:40px;
+ max-height:30px;
}
</file>
<file name="protractor.js" type="protractor">
var friends = element.all(by.repeater('friend in friends'));
@@ -28541,71 +29191,190 @@
* @restrict EAC
*
* @description
* Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
*
- * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
+ * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name
+ * as the value of the `ng-transclude` or `ng-transclude-slot` attribute.
*
+ * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing
+ * content of this element will be removed before the transcluded content is inserted.
+ * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case
+ * that no transcluded content is provided.
+ *
* @element ANY
*
+ * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty
+ * or its value is the same as the name of the attribute then the default slot is used.
+ *
* @example
- <example module="transcludeExample">
- <file name="index.html">
- <script>
- angular.module('transcludeExample', [])
- .directive('pane', function(){
- return {
- restrict: 'E',
- transclude: true,
- scope: { title:'@' },
- template: '<div style="border: 1px solid black;">' +
- '<div style="background-color: gray">{{title}}</div>' +
- '<ng-transclude></ng-transclude>' +
- '</div>'
- };
- })
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.title = 'Lorem Ipsum';
- $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
- }]);
- </script>
- <div ng-controller="ExampleController">
- <input ng-model="title" aria-label="title"> <br/>
- <textarea ng-model="text" aria-label="text"></textarea> <br/>
- <pane title="{{title}}">{{text}}</pane>
- </div>
- </file>
- <file name="protractor.js" type="protractor">
- it('should have transcluded', function() {
- var titleElement = element(by.model('title'));
- titleElement.clear();
- titleElement.sendKeys('TITLE');
- var textElement = element(by.model('text'));
- textElement.clear();
- textElement.sendKeys('TEXT');
- expect(element(by.binding('title')).getText()).toEqual('TITLE');
- expect(element(by.binding('text')).getText()).toEqual('TEXT');
- });
- </file>
- </example>
+ * ### Basic transclusion
+ * This example demonstrates basic transclusion of content into a component directive.
+ * <example name="simpleTranscludeExample" module="transcludeExample">
+ * <file name="index.html">
+ * <script>
+ * angular.module('transcludeExample', [])
+ * .directive('pane', function(){
+ * return {
+ * restrict: 'E',
+ * transclude: true,
+ * scope: { title:'@' },
+ * template: '<div style="border: 1px solid black;">' +
+ * '<div style="background-color: gray">{{title}}</div>' +
+ * '<ng-transclude></ng-transclude>' +
+ * '</div>'
+ * };
+ * })
+ * .controller('ExampleController', ['$scope', function($scope) {
+ * $scope.title = 'Lorem Ipsum';
+ * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
+ * }]);
+ * </script>
+ * <div ng-controller="ExampleController">
+ * <input ng-model="title" aria-label="title"> <br/>
+ * <textarea ng-model="text" aria-label="text"></textarea> <br/>
+ * <pane title="{{title}}">{{text}}</pane>
+ * </div>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * it('should have transcluded', function() {
+ * var titleElement = element(by.model('title'));
+ * titleElement.clear();
+ * titleElement.sendKeys('TITLE');
+ * var textElement = element(by.model('text'));
+ * textElement.clear();
+ * textElement.sendKeys('TEXT');
+ * expect(element(by.binding('title')).getText()).toEqual('TITLE');
+ * expect(element(by.binding('text')).getText()).toEqual('TEXT');
+ * });
+ * </file>
+ * </example>
*
+ * @example
+ * ### Transclude fallback content
+ * This example shows how to use `NgTransclude` with fallback content, that
+ * is displayed if no transcluded content is provided.
+ *
+ * <example module="transcludeFallbackContentExample">
+ * <file name="index.html">
+ * <script>
+ * angular.module('transcludeFallbackContentExample', [])
+ * .directive('myButton', function(){
+ * return {
+ * restrict: 'E',
+ * transclude: true,
+ * scope: true,
+ * template: '<button style="cursor: pointer;">' +
+ * '<ng-transclude>' +
+ * '<b style="color: red;">Button1</b>' +
+ * '</ng-transclude>' +
+ * '</button>'
+ * };
+ * });
+ * </script>
+ * <!-- fallback button content -->
+ * <my-button id="fallback"></my-button>
+ * <!-- modified button content -->
+ * <my-button id="modified">
+ * <i style="color: green;">Button2</i>
+ * </my-button>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * it('should have different transclude element content', function() {
+ * expect(element(by.id('fallback')).getText()).toBe('Button1');
+ * expect(element(by.id('modified')).getText()).toBe('Button2');
+ * });
+ * </file>
+ * </example>
+ *
+ * @example
+ * ### Multi-slot transclusion
+ * This example demonstrates using multi-slot transclusion in a component directive.
+ * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample">
+ * <file name="index.html">
+ * <style>
+ * .title, .footer {
+ * background-color: gray
+ * }
+ * </style>
+ * <div ng-controller="ExampleController">
+ * <input ng-model="title" aria-label="title"> <br/>
+ * <textarea ng-model="text" aria-label="text"></textarea> <br/>
+ * <pane>
+ * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
+ * <pane-body><p>{{text}}</p></pane-body>
+ * </pane>
+ * </div>
+ * </file>
+ * <file name="app.js">
+ * angular.module('multiSlotTranscludeExample', [])
+ * .directive('pane', function(){
+ * return {
+ * restrict: 'E',
+ * transclude: {
+ * 'title': '?paneTitle',
+ * 'body': 'paneBody',
+ * 'footer': '?paneFooter'
+ * },
+ * template: '<div style="border: 1px solid black;">' +
+ * '<div class="title" ng-transclude="title">Fallback Title</div>' +
+ * '<div ng-transclude="body"></div>' +
+ * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
+ * '</div>'
+ * };
+ * })
+ * .controller('ExampleController', ['$scope', function($scope) {
+ * $scope.title = 'Lorem Ipsum';
+ * $scope.link = "https://google.com";
+ * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
+ * }]);
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * it('should have transcluded the title and the body', function() {
+ * var titleElement = element(by.model('title'));
+ * titleElement.clear();
+ * titleElement.sendKeys('TITLE');
+ * var textElement = element(by.model('text'));
+ * textElement.clear();
+ * textElement.sendKeys('TEXT');
+ * expect(element(by.css('.title')).getText()).toEqual('TITLE');
+ * expect(element(by.binding('text')).getText()).toEqual('TEXT');
+ * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer');
+ * });
+ * </file>
+ * </example>
*/
+var ngTranscludeMinErr = minErr('ngTransclude');
var ngTranscludeDirective = ngDirective({
restrict: 'EAC',
link: function($scope, $element, $attrs, controller, $transclude) {
+
+ if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
+ // If the attribute is of the form: `ng-transclude="ng-transclude"`
+ // then treat it like the default
+ $attrs.ngTransclude = '';
+ }
+
+ function ngTranscludeCloneAttachFn(clone) {
+ if (clone.length) {
+ $element.empty();
+ $element.append(clone);
+ }
+ }
+
if (!$transclude) {
- throw minErr('ngTransclude')('orphan',
+ throw ngTranscludeMinErr('orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
}
- $transclude(function(clone) {
- $element.empty();
- $element.append(clone);
- });
+ // If there is no slot name defined or the slot name is not optional
+ // then transclude the slot
+ var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
+ $transclude(ngTranscludeCloneAttachFn, null, slotName);
}
});
/**
* @ngdoc directive
@@ -28733,10 +29502,13 @@
};
// Tell the select control that an option, with the given value, has been added
self.addOption = function(value, element) {
+ // Skip comment nodes, as they only pollute the `optionsMap`
+ if (element[0].nodeType === NODE_TYPE_COMMENT) return;
+
assertNotHasOwnProperty(value, '"option value"');
if (value === '') {
self.emptyOption = element;
}
var count = optionsMap.get(value) || 0;
@@ -28817,11 +29589,11 @@
* the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
* If you want dynamic value attributes, you can use interpolation inside the value attribute.
*
* <div class="alert alert-warning">
* Note that the value of a `select` directive used without `ngOptions` is always a string.
- * When the model needs to be bound to a non-string value, you must either explictly convert it
+ * When the model needs to be bound to a non-string value, you must either explicitly convert it
* using a directive (see example below) or use `ngOptions` to specify the set of options.
* This is because an option element can only be bound to string values at present.
* </div>
*
* If the viewValue of `ngModel` does not match any of the options, then the control
@@ -29105,11 +29877,10 @@
var optionDirective = ['$interpolate', function($interpolate) {
return {
restrict: 'E',
priority: 100,
compile: function(element, attr) {
-
if (isDefined(attr.value)) {
// If the value attribute is defined, check if it contains an interpolation
var interpolateValueFn = $interpolate(attr.value, true);
} else {
// If the value attribute is not defined then we fall back to the
@@ -29119,11 +29890,10 @@
attr.$set('value', element.text());
}
}
return function(scope, element, attr) {
-
// This is an optimization over using ^^ since we don't want to have to search
// all the way to the root of the DOM for every single option element
var selectCtrlName = '$selectController',
parent = element.parent(),
selectCtrl = parent.data(selectCtrlName) ||
@@ -29156,11 +29926,11 @@
* `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we
* cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide}
* for more info.
*
* The validator will set the `required` error key to true if the `required` attribute is set and
- * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty` with the
+ * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the
* {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the
* `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
* custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based.
*
* @example
@@ -29642,9 +30412,10 @@
"posSuf": ""
}
]
},
"id": "en-us",
+ "localeID": "en_US",
"pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
});
}]);
jqLite(document).ready(function() {
\ No newline at end of file