diff --git a/src/angular.js b/src/angular.js index b804d6490ac..7f2339570ce 100644 --- a/src/angular.js +++ b/src/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.0 + * @license AngularJS v1.3.16 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -37,45 +37,28 @@ function minErr(module, ErrorConstructor) { ErrorConstructor = ErrorConstructor || Error; - return function () { + return function() { var code = arguments[0], prefix = '[' + (module ? module + ':' : '') + code + '] ', template = arguments[1], templateArgs = arguments, - stringify = function (obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else if (typeof obj !== 'string') { - return JSON.stringify(obj); - } - return obj; - }, + message, i; - message = prefix + template.replace(/\{\d+\}/g, function (match) { + message = prefix + template.replace(/\{\d+\}/g, function(match) { var index = +match.slice(1, -1), arg; if (index + 2 < templateArgs.length) { - arg = templateArgs[index + 2]; - if (typeof arg === 'function') { - return arg.toString().replace(/ ?\{[\s\S]*$/, ''); - } else if (typeof arg === 'undefined') { - return 'undefined'; - } else if (typeof arg !== 'string') { - return toJson(arg); - } - return arg; + return toDebugString(templateArgs[index + 2]); } return match; }); - message = message + '\nhttp://errors.angularjs.org/1.3.0/' + + message = message + '\nhttp://errors.angularjs.org/1.3.16/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { - message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + - encodeURIComponent(stringify(arguments[i])); + message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + + encodeURIComponent(toDebugString(arguments[i])); } return new ErrorConstructor(message); }; @@ -126,16 +109,16 @@ function minErr(module, ErrorConstructor) { isWindow: true, isScope: true, isFile: true, + isFormData: true, isBlob: true, isBoolean: true, isPromiseLike: true, trim: true, + escapeForRegexp: true, isElement: true, makeMap: true, - size: true, includes: true, arrayRemove: true, - isLeafNode: true, copy: true, shallowCopy: true, equals: true, @@ -166,6 +149,7 @@ function minErr(module, ErrorConstructor) { createMap: true, NODE_TYPE_ELEMENT: true, + NODE_TYPE_ATTRIBUTE: true, NODE_TYPE_TEXT: true, NODE_TYPE_COMMENT: true, NODE_TYPE_DOCUMENT: true, @@ -205,7 +189,7 @@ var VALIDITY_STATE_PROPERTY = 'validity'; * @param {string} string String to be converted to lowercase. * @returns {string} Lowercased string. */ -var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; +var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; var hasOwnProperty = Object.prototype.hasOwnProperty; /** @@ -218,7 +202,7 @@ var hasOwnProperty = Object.prototype.hasOwnProperty; * @param {string} string String to be converted to uppercase. * @returns {string} Uppercased string. */ -var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; +var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; var manualLowercase = function(s) { @@ -244,8 +228,8 @@ if ('i' !== 'I'.toLowerCase()) { } -var /** holds major version number for IE or NaN for real browsers */ - msie, +var + msie, // holds major version number for IE, or NaN if UA is not IE. jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding slice = [].slice, @@ -277,7 +261,9 @@ function isArrayLike(obj) { return false; } - var length = obj.length; + // Support: iOS 8.2 (not reproducible in simulator) + // "length" in obj used to prevent JIT error (gh-11508) + var length = "length" in Object(obj) && obj.length; if (obj.nodeType === NODE_TYPE_ELEMENT && length) { return true; @@ -301,6 +287,11 @@ function isArrayLike(obj) { * * It is worth noting that `.forEach` does not iterate over inherited properties because it filters * using the `hasOwnProperty` method. + * + * Unlike ES262's + * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), + * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just + * return the value provided. * ```js var values = {name: 'misko', gender: 'male'}; @@ -349,18 +340,12 @@ function forEach(obj, iterator, context) { } function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys.sort(); + return Object.keys(obj).sort(); } function forEachSorted(obj, iterator, context) { var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys.length; i++) { iterator.call(context, obj[keys[i]], keys[i]); } return keys; @@ -399,8 +384,7 @@ function nextUid() { function setHashKey(obj, h) { if (h) { obj.$$hashKey = h; - } - else { + } else { delete obj.$$hashKey; } } @@ -415,6 +399,7 @@ function setHashKey(obj, h) { * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. + * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy). * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). @@ -444,7 +429,7 @@ function int(str) { function inherit(parent, extra) { - return extend(new (extend(function() {}, {prototype:parent}))(), extra); + return extend(Object.create(parent), extra); } /** @@ -482,6 +467,8 @@ noop.$inject = []; return (transformationFn || angular.identity)(value); }; ``` + * @param {*} value to be returned. + * @returns {*} the value passed in. */ function identity($) {return $;} identity.$inject = []; @@ -501,7 +488,7 @@ function valueFn(value) {return function() {return value;};} * @param {*} value Reference to check. * @returns {boolean} True if `value` is undefined. */ -function isUndefined(value){return typeof value === 'undefined';} +function isUndefined(value) {return typeof value === 'undefined';} /** @@ -516,7 +503,7 @@ function isUndefined(value){return typeof value === 'undefined';} * @param {*} value Reference to check. * @returns {boolean} True if `value` is defined. */ -function isDefined(value){return typeof value !== 'undefined';} +function isDefined(value) {return typeof value !== 'undefined';} /** @@ -532,7 +519,7 @@ function isDefined(value){return typeof value !== 'undefined';} * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Object` but not `null`. */ -function isObject(value){ +function isObject(value) { // http://jsperf.com/isobject4 return value !== null && typeof value === 'object'; } @@ -550,7 +537,7 @@ function isObject(value){ * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `String`. */ -function isString(value){return typeof value === 'string';} +function isString(value) {return typeof value === 'string';} /** @@ -562,10 +549,16 @@ function isString(value){return typeof value === 'string';} * @description * Determines if a reference is a `Number`. * + * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. + * + * If you wish to exclude these then you can use the native + * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) + * method. + * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Number`. */ -function isNumber(value){return typeof value === 'number';} +function isNumber(value) {return typeof value === 'number';} /** @@ -611,7 +604,7 @@ var isArray = Array.isArray; * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Function`. */ -function isFunction(value){return typeof value === 'function';} +function isFunction(value) {return typeof value === 'function';} /** @@ -648,6 +641,11 @@ function isFile(obj) { } +function isFormData(obj) { + return toString.call(obj) === '[object FormData]'; +} + + function isBlob(obj) { return toString.call(obj) === '[object Blob]'; } @@ -667,6 +665,14 @@ var trim = function(value) { return isString(value) ? value.trim() : value; }; +// Copied from: +// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 +// Prereq: s is a string. +var escapeForRegexp = function(s) { + return s.replace(/([-()\[\]{}+?*.$\^|,:#=0) + if (index >= 0) array.splice(index, 1); return value; } -function isLeafNode (node) { - if (node) { - switch (nodeName_(node)) { - case "option": - case "pre": - case "title": - return true; - } - } - return false; -} - /** * @ngdoc function * @name angular.copy @@ -763,7 +729,7 @@ function isLeafNode (node) { * Creates a deep copy of `source`, which should be an object or an array. * * * If no destination is supplied, a copy of the object or array is created. - * * If a destination is provided, all of its elements (for array) or properties (for objects) + * * If a destination is provided, all of its elements (for arrays) or properties (for objects) * are deleted and then all elements/properties from the source are copied to it. * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. * * If `source` is identical to 'destination' an exception will be thrown. @@ -850,7 +816,7 @@ function copy(source, destination, stackSource, stackDest) { var result; if (isArray(source)) { destination.length = 0; - for ( var i = 0; i < source.length; i++) { + for (var i = 0; i < source.length; i++) { result = copy(source[i], null, stackSource, stackDest); if (isObject(source[i])) { stackSource.push(source[i]); @@ -867,8 +833,8 @@ function copy(source, destination, stackSource, stackDest) { delete destination[key]; }); } - for ( var key in source) { - if(source.hasOwnProperty(key)) { + for (var key in source) { + if (source.hasOwnProperty(key)) { result = copy(source[key], null, stackSource, stackDest); if (isObject(source[key])) { stackSource.push(source[key]); @@ -949,7 +915,7 @@ function equals(o1, o2) { if (isArray(o1)) { if (!isArray(o2)) return false; if ((length = o1.length) == o2.length) { - for(key=0; key').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : elemHtml. match(/^(<[^>]+>)/)[1]. replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); - } catch(e) { + } catch (e) { return lowercase(elemHtml); } @@ -1142,7 +1113,7 @@ function startingTag(element) { function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); - } catch(e) { + } catch (e) { // Ignore any invalid uri component } } @@ -1155,14 +1126,14 @@ function tryDecodeURIComponent(value) { function parseKeyValue(/**string*/keyValue) { var obj = {}, key_value, key; forEach((keyValue || "").split('&'), function(keyValue) { - if ( keyValue ) { + if (keyValue) { key_value = keyValue.replace(/\+/g,'%20').split('='); key = tryDecodeURIComponent(key_value[0]); - if ( isDefined(key) ) { + if (isDefined(key)) { var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; if (!hasOwnProperty.call(obj, key)) { obj[key] = val; - } else if(isArray(obj[key])) { + } else if (isArray(obj[key])) { obj[key].push(val); } else { obj[key] = [obj[key],val]; @@ -1235,7 +1206,7 @@ var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; function getNgAttribute(element, ngAttr) { var attr, i, ii = ngAttrPrefixes.length; element = jqLite(element); - for (i=0; i @@ -1440,13 +1411,13 @@ function angularInit(element, bootstrap) { * @param {DOMElement} element DOM element which is the root of angular application. * @param {Array=} modules an array of modules to load into the application. * Each item in the array should be the name of a predefined module or a (DI annotated) - * function that will be invoked by the injector as a run block. + * function that will be invoked by the injector as a `config` block. * See: {@link angular.module modules} * @param {Object=} config an object for defining configuration options for the application. The * following keys are supported: * - * - `strictDi`: disable automatic function annotation for the application. This is meant to - * assist in finding bugs which break minified code. + * * `strictDi` - disable automatic function annotation for the application. This is meant to + * assist in finding bugs which break minified code. Defaults to `false`. * * @returns {auto.$injector} Returns the newly created injector for this app. */ @@ -1510,8 +1481,12 @@ function bootstrap(element, modules, config) { forEach(extraModules, function(module) { modules.push(module); }); - doBootstrap(); + return doBootstrap(); }; + + if (isFunction(angular.resumeDeferredBootstrap)) { + angular.resumeDeferredBootstrap(); + } } /** @@ -1538,7 +1513,12 @@ function reloadWithDebugInfo() { * @param {DOMElement} element DOM element which is the root of angular application. */ function getTestability(rootElement) { - return angular.element(rootElement).injector().get('$$testability'); + var injector = angular.element(rootElement).injector(); + if (!injector) { + throw ngMinErr('test', + 'no injector found for element argument to getTestability'); + } + return injector.get('$$testability'); } var SNAKE_CASE_REGEXP = /[A-Z]/g; @@ -1698,6 +1678,7 @@ function createMap() { } var NODE_TYPE_ELEMENT = 1; +var NODE_TYPE_ATTRIBUTE = 2; var NODE_TYPE_TEXT = 3; var NODE_TYPE_COMMENT = 8; var NODE_TYPE_DOCUMENT = 9; @@ -1934,10 +1915,17 @@ function setupModuleLoader(window) { * @ngdoc method * @name angular.Module#filter * @module ng - * @param {string} name Filter name. + * @param {string} name Filter name - this must be a valid angular expression identifier * @param {Function} filterFactory Factory function for creating new instance of filter. * @description * See {@link ng.$filterProvider#register $filterProvider.register()}. + * + *
+ * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. + * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace + * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores + * (`myapp_subsection_filterx`). + *
*/ filter: invokeLater('$filterProvider', 'register'), @@ -1999,7 +1987,7 @@ function setupModuleLoader(window) { config(configFn); } - return moduleInstance; + return moduleInstance; /** * @param {string} provider @@ -2020,6 +2008,34 @@ function setupModuleLoader(window) { } +/* global: toDebugString: true */ + +function serializeObject(obj) { + var seen = []; + + return JSON.stringify(obj, function(key, val) { + val = toJsonReplacer(key, val); + if (isObject(val)) { + + if (seen.indexOf(val) >= 0) return '<>'; + + seen.push(val); + } + return val; + }); +} + +function toDebugString(obj) { + if (typeof obj === 'function') { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (typeof obj === 'undefined') { + return 'undefined'; + } else if (typeof obj !== 'string') { + return serializeObject(obj); + } + return obj; +} + /* global angularModule: true, version: true, @@ -2103,7 +2119,8 @@ function setupModuleLoader(window) { $TimeoutProvider, $$RAFProvider, $$AsyncCallbackProvider, - $WindowProvider + $WindowProvider, + $$jqLiteProvider */ @@ -2122,15 +2139,15 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.3.0', // all of these placeholder strings will be replaced by grunt's + full: '1.3.16', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, - dot: 0, - codeName: 'superluminal-nudge' + dot: 16, + codeName: 'cookie-oatmealification' }; -function publishExternalAPI(angular){ +function publishExternalAPI(angular) { extend(angular, { 'bootstrap': bootstrap, 'copy': copy, @@ -2256,12 +2273,24 @@ function publishExternalAPI(angular){ $timeout: $TimeoutProvider, $window: $WindowProvider, $$rAF: $$RAFProvider, - $$asyncCallback : $$AsyncCallbackProvider + $$asyncCallback: $$AsyncCallbackProvider, + $$jqLite: $$jqLiteProvider }); } ]); } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* global JQLitePrototype: true, addEventListenerFn: true, removeEventListenerFn: true, @@ -2290,7 +2319,7 @@ function publishExternalAPI(angular){ * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most * commonly needed functionality with the goal of having a very small footprint. * - * To use jQuery, simply load it before `DOMContentLoaded` event fired. + * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. * *
**Note:** all element references in Angular are always wrapped with jQuery or * jqLite; they are never raw DOM references.
@@ -2306,7 +2335,7 @@ function publishExternalAPI(angular){ * - [`children()`](http://api.jquery.com/children/) - Does not support selectors * - [`clone()`](http://api.jquery.com/clone/) * - [`contents()`](http://api.jquery.com/contents/) - * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()` + * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'. * - [`data()`](http://api.jquery.com/data/) * - [`detach()`](http://api.jquery.com/detach/) * - [`empty()`](http://api.jquery.com/empty/) @@ -2349,10 +2378,12 @@ function publishExternalAPI(angular){ * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current - * element or its parent. + * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to + * be enabled. * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the * current element. This getter should be used only on elements that contain a directive which starts a new isolate * scope. Calling `scope()` on this element always returns the original non-isolate scope. + * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * @@ -2384,7 +2415,7 @@ function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; -var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"}; +var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; var jqLiteMinErr = minErr('jqLite'); /** @@ -2513,7 +2544,7 @@ function jqLiteClone(element) { return element.cloneNode(true); } -function jqLiteDealoc(element, onlyDescendants){ +function jqLiteDealoc(element, onlyDescendants) { if (!onlyDescendants) jqLiteRemoveData(element); if (element.querySelectorAll) { @@ -2620,7 +2651,7 @@ function jqLiteData(element, key, value) { function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf( " " + selector + " " ) > -1); + indexOf(" " + selector + " ") > -1); } function jqLiteRemoveClass(element, cssClasses) { @@ -2679,13 +2710,13 @@ function jqLiteAddNodes(root, elements) { function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); + return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); } function jqLiteInheritedData(element, name, value) { // if element is the document object work with the html element instead // this makes $(document).scope() possible - if(element.nodeType == NODE_TYPE_DOCUMENT) { + if (element.nodeType == NODE_TYPE_DOCUMENT) { element = element.documentElement; } var names = isArray(name) ? name : [name]; @@ -2743,7 +2774,7 @@ var JQLitePrototype = JQLite.prototype = { } // check if document is already loaded - if (document.readyState === 'complete'){ + if (document.readyState === 'complete') { setTimeout(trigger); } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 @@ -2751,12 +2782,11 @@ var JQLitePrototype = JQLite.prototype = { // jshint -W064 JQLite(window).on('load', trigger); // fallback to window.onload for others // jshint +W064 - this.on('DOMContentLoaded', trigger); } }, toString: function() { var value = []; - forEach(this, function(e){ value.push('' + e);}); + forEach(this, function(e) { value.push('' + e);}); return '[' + value.join(', ') + ']'; }, @@ -2784,11 +2814,11 @@ forEach('input,select,option,textarea,button,form,details'.split(','), function( BOOLEAN_ELEMENTS[value] = true; }); var ALIASED_ATTR = { - 'ngMinlength' : 'minlength', - 'ngMaxlength' : 'maxlength', - 'ngMin' : 'min', - 'ngMax' : 'max', - 'ngPattern' : 'pattern' + 'ngMinlength': 'minlength', + 'ngMaxlength': 'maxlength', + 'ngMin': 'min', + 'ngMax': 'max', + 'ngPattern': 'pattern' }; function getBooleanAttrName(element, name) { @@ -2847,7 +2877,11 @@ forEach({ } }, - attr: function(element, name, value){ + attr: function(element, name, value) { + var nodeType = element.nodeType; + if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { + return; + } var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { @@ -2860,7 +2894,7 @@ forEach({ } } else { return (element[name] || - (element.attributes.getNamedItem(name)|| noop).specified) + (element.attributes.getNamedItem(name) || noop).specified) ? lowercasedName : undefined; } @@ -2900,7 +2934,7 @@ forEach({ if (isUndefined(value)) { if (element.multiple && nodeName_(element) === 'select') { var result = []; - forEach(element.options, function (option) { + forEach(element.options, function(option) { if (option.selected) { result.push(option.value || option.text); } @@ -2921,7 +2955,7 @@ forEach({ }, empty: jqLiteEmpty -}, function(fn, name){ +}, function(fn, name) { /** * Properties: writes return selection, reads return first value */ @@ -2973,7 +3007,7 @@ forEach({ }); function createEventHandler(element, events) { - var eventHandler = function (event, type) { + var eventHandler = function(event, type) { // jQuery specific api event.isDefaultPrevented = function() { return event.defaultPrevented; @@ -3029,7 +3063,7 @@ function createEventHandler(element, events) { forEach({ removeData: jqLiteRemoveData, - on: function jqLiteOn(element, type, fn, unsupported){ + on: function jqLiteOn(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); // Do not add event handlers to non-elements because they will not be cleaned up. @@ -3065,7 +3099,7 @@ forEach({ var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !target.contains(related)) ){ + if (!related || (related !== target && !target.contains(related))) { handle(event, type); } }); @@ -3099,7 +3133,7 @@ forEach({ replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node){ + forEach(new JQLite(replaceNode), function(node) { if (index) { parent.insertBefore(node, index.nextSibling); } else { @@ -3111,7 +3145,7 @@ forEach({ children: function(element) { var children = []; - forEach(element.childNodes, function(element){ + forEach(element.childNodes, function(element) { if (element.nodeType === NODE_TYPE_ELEMENT) children.push(element); }); @@ -3137,7 +3171,7 @@ forEach({ prepend: function(element, node) { if (element.nodeType === NODE_TYPE_ELEMENT) { var index = element.firstChild; - forEach(new JQLite(node), function(child){ + forEach(new JQLite(node), function(child) { element.insertBefore(child, index); }); } @@ -3174,7 +3208,7 @@ forEach({ toggleClass: function(element, selector, condition) { if (selector) { - forEach(selector.split(' '), function(className){ + forEach(selector.split(' '), function(className) { var classCondition = condition; if (isUndefined(classCondition)) { classCondition = !jqLiteHasClass(element, className); @@ -3239,14 +3273,14 @@ forEach({ }); } } -}, function(fn, name){ +}, function(fn, name) { /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; - for(var i = 0, ii = this.length; i < ii; i++) { + for (var i = 0, ii = this.length; i < ii; i++) { if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); if (isDefined(value)) { @@ -3265,6 +3299,27 @@ forEach({ JQLite.prototype.unbind = JQLite.prototype.off; }); + +// Provider for private $$jqLite service +function $$jqLiteProvider() { + this.$get = function $$jqLite() { + return extend(JQLite, { + hasClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteHasClass(node, classes); + }, + addClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteAddClass(node, classes); + }, + removeClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteRemoveClass(node, classes); + } + }); + }; +} + /** * Computes a hash of an 'obj'. * Hash of a: @@ -3348,9 +3403,10 @@ HashMap.prototype = { * Creates an injector object that can be used for retrieving services as well as for * dependency injection (see {@link guide/di dependency injection}). * - * @param {Array.} modules A list of module functions or their aliases. See - * {@link angular.module}. The `ng` module must be explicitly added. + * {@link angular.module}. The `ng` module must be explicitly added. + * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which + * disallows argument name annotation inference. * @returns {injector} Injector object. See {@link auto.$injector $injector}. * * @example @@ -3496,8 +3552,10 @@ function annotate(fn, strictDi, name) { * ## Inference * * In JavaScript calling `toString()` on a function returns the function definition. The definition - * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with - * minification, and obfuscation tools since these tools change the argument names. + * can then be parsed and the function arguments can be extracted. This method of discovering + * annotations is disallowed when the injector is in strict mode. + * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the + * argument names. * * ## `$inject` Annotation * By adding an `$inject` property onto a function the injection parameters can be specified. @@ -3514,6 +3572,7 @@ function annotate(fn, strictDi, name) { * Return an instance of the service. * * @param {string} name The name of the instance to retrieve. + * @param {string=} caller An optional string to provide the origin of the function call for error messages. * @return {*} The instance. */ @@ -3524,8 +3583,8 @@ function annotate(fn, strictDi, name) { * @description * Invoke the method and supply the method arguments from the `$injector`. * - * @param {!Function} fn The function to invoke. Function parameters are injected according to the - * {@link guide/di $inject Annotation} rules. + * @param {Function|Array.} fn The injectable function to invoke. Function parameters are + * injected according to the {@link guide/di $inject Annotation} rules. * @param {Object=} self The `this` for the invoked method. * @param {Object=} locals Optional object. If preset then any argument names are read from this * object first, before the `$injector` is consulted. @@ -3582,6 +3641,8 @@ function annotate(fn, strictDi, name) { * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * + * You can disallow this method by using strict injection mode. + * * This method does not work with code minification / obfuscation. For this reason the following * annotation strategies are supported. * @@ -3634,6 +3695,8 @@ function annotate(fn, strictDi, name) { * @param {Function|Array.} fn Function for which dependent service names need to * be retrieved as described above. * + * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. + * * @returns {Array.} The names of the services which the function requires. */ @@ -3788,8 +3851,8 @@ function annotate(fn, strictDi, name) { * configure your service in a provider. * * @param {string} name The name of the instance. - * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand - * for `$provide.provider(name, {$get: $getFn})`. + * @param {Function|Array.} $getFn The injectable $getFn for the instance creation. + * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. * @returns {Object} registered provider instance * * @example @@ -3824,7 +3887,8 @@ function annotate(fn, strictDi, name) { * as a type/class. * * @param {string} name The name of the instance. - * @param {Function} constructor A class (constructor function) that will be instantiated. + * @param {Function|Array.} constructor An injectable class (constructor function) + * that will be instantiated. * @returns {Object} registered provider instance * * @example @@ -3923,7 +3987,7 @@ function annotate(fn, strictDi, name) { * object which replaces or wraps and delegates to the original service. * * @param {string} name The name of the service to decorate. - * @param {function()} decorator This function will be invoked when the service needs to be + * @param {Function|Array.} decorator This function will be invoked when the service needs to be * instantiated and should return the decorated service instance. The function is called using * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. * Local injection arguments: @@ -3960,14 +4024,17 @@ function createInjector(modulesToLoad, strictDi) { } }, providerInjector = (providerCache.$injector = - createInternalInjector(providerCache, function() { + createInternalInjector(providerCache, function(serviceName, caller) { + if (angular.isString(caller)) { + path.push(caller); + } throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); })), instanceCache = {}, instanceInjector = (instanceCache.$injector = - createInternalInjector(instanceCache, function(servicename) { - var provider = providerInjector.get(servicename + providerSuffix); - return instanceInjector.invoke(provider.$get, provider, undefined, servicename); + createInternalInjector(instanceCache, function(serviceName, caller) { + var provider = providerInjector.get(serviceName + providerSuffix, caller); + return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); @@ -4002,7 +4069,7 @@ function createInjector(modulesToLoad, strictDi) { function enforceReturnValue(name, factory) { return function enforcedReturnValue() { - var result = instanceInjector.invoke(factory, this, undefined, name); + var result = instanceInjector.invoke(factory, this); if (isUndefined(result)) { throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); } @@ -4043,7 +4110,7 @@ function createInjector(modulesToLoad, strictDi) { //////////////////////////////////// // Module Loading //////////////////////////////////// - function loadModules(modulesToLoad){ + function loadModules(modulesToLoad) { var runBlocks = [], moduleFn; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; @@ -4051,7 +4118,7 @@ function createInjector(modulesToLoad, strictDi) { function runInvokeQueue(queue) { var i, ii; - for(i = 0, ii = queue.length; i < ii; i++) { + for (i = 0, ii = queue.length; i < ii; i++) { var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); @@ -4097,7 +4164,7 @@ function createInjector(modulesToLoad, strictDi) { function createInternalInjector(cache, factory) { - function getService(serviceName) { + function getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', @@ -4108,7 +4175,7 @@ function createInjector(modulesToLoad, strictDi) { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; - return cache[serviceName] = factory(serviceName); + return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; @@ -4127,11 +4194,11 @@ function createInjector(modulesToLoad, strictDi) { } var args = [], - $inject = annotate(fn, strictDi, serviceName), + $inject = createInjector.$$annotate(fn, strictDi, serviceName), length, i, key; - for(i = 0, length = $inject.length; i < length; i++) { + for (i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', @@ -4140,7 +4207,7 @@ function createInjector(modulesToLoad, strictDi) { args.push( locals && locals.hasOwnProperty(key) ? locals[key] - : getService(key) + : getService(key, serviceName) ); } if (isArray(fn)) { @@ -4153,14 +4220,11 @@ function createInjector(modulesToLoad, strictDi) { } function instantiate(Type, locals, serviceName) { - var Constructor = function() {}, - instance, returnedValue; - // Check if Type is annotated and use just the given function at n-1 as parameter // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); - Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; - instance = new Constructor(); - returnedValue = invoke(Type, instance, locals, serviceName); + // Object creation: http://jsperf.com/create-constructor/2 + var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); + var returnedValue = invoke(Type, instance, locals, serviceName); return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; } @@ -4169,7 +4233,7 @@ function createInjector(modulesToLoad, strictDi) { invoke: invoke, instantiate: instantiate, get: getService, - annotate: annotate, + annotate: createInjector.$$annotate, has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } @@ -4196,7 +4260,7 @@ function $AnchorScrollProvider() { * @name $anchorScrollProvider#disableAutoScrolling * * @description - * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to + * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.
* Use this method to disable automatic scrolling. * @@ -4347,7 +4411,6 @@ function $AnchorScrollProvider() { */ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { var document = $window.document; - var scrollScheduled = false; // Helper function to get first anchor from a NodeList // (using `Array#some()` instead of `angular#forEach()` since it's more performant @@ -4521,7 +4584,7 @@ var $AnimateProvider = ['$provide', function($provide) { * @return {RegExp} The current CSS className expression value. If null then there is no expression value */ this.classNameFilter = function(expression) { - if(arguments.length === 1) { + if (arguments.length === 1) { this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; } return this.$$classNameFilter; @@ -4616,7 +4679,7 @@ var $AnimateProvider = ['$provide', function($provide) { * page}. */ return { - animate : function(element, from, to) { + animate: function(element, from, to) { applyStyles(element, { from: from, to: to }); return asyncPromise(); }, @@ -4637,7 +4700,7 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object=} options an optional collection of styles that will be applied to the element. * @return {Promise} the animation callback promise */ - enter : function(element, parent, after, options) { + enter: function(element, parent, after, options) { applyStyles(element, options); after ? after.after(element) : parent.prepend(element); @@ -4655,7 +4718,8 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - leave : function(element, options) { + leave: function(element, options) { + applyStyles(element, options); element.remove(); return asyncPromise(); }, @@ -4678,7 +4742,7 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - move : function(element, parent, after, options) { + move: function(element, parent, after, options) { // Do not remove element before insert. Removing will cause data associated with the // element to be dropped. Insert will implicitly do the remove. return this.enter(element, parent, after, options); @@ -4697,16 +4761,16 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - addClass : function(element, className, options) { + addClass: function(element, className, options) { return this.setClass(element, className, [], options); }, - $$addClassImmediately : function(element, className, options) { + $$addClassImmediately: function(element, className, options) { element = jqLite(element); className = !isString(className) ? (isArray(className) ? className.join(' ') : '') : className; - forEach(element, function (element) { + forEach(element, function(element) { jqLiteAddClass(element, className); }); applyStyles(element, options); @@ -4726,16 +4790,16 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - removeClass : function(element, className, options) { + removeClass: function(element, className, options) { return this.setClass(element, [], className, options); }, - $$removeClassImmediately : function(element, className, options) { + $$removeClassImmediately: function(element, className, options) { element = jqLite(element); className = !isString(className) ? (isArray(className) ? className.join(' ') : '') : className; - forEach(element, function (element) { + forEach(element, function(element) { jqLiteRemoveClass(element, className); }); applyStyles(element, options); @@ -4756,7 +4820,7 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - setClass : function(element, add, remove, options) { + setClass: function(element, add, remove, options) { var self = this; var STORAGE_KEY = '$$animateClasses'; var createdCache = false; @@ -4766,7 +4830,7 @@ var $AnimateProvider = ['$provide', function($provide) { if (!cache) { cache = { classes: {}, - options : options + options: options }; createdCache = true; } else if (options && cache.options) { @@ -4803,20 +4867,20 @@ var $AnimateProvider = ['$provide', function($provide) { return cache.promise; }, - $$setClassImmediately : function(element, add, remove, options) { + $$setClassImmediately: function(element, add, remove, options) { add && this.$$addClassImmediately(element, add); remove && this.$$removeClassImmediately(element, remove); applyStyles(element, options); return asyncPromise(); }, - enabled : noop, - cancel : noop + enabled: noop, + cancel: noop }; }]; }]; -function $$AsyncCallbackProvider(){ +function $$AsyncCallbackProvider() { this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { return $$rAF.supported ? function(fn) { return $$rAF(fn); } @@ -4846,8 +4910,7 @@ function $$AsyncCallbackProvider(){ /** * @param {object} window The global window object. * @param {object} document jQuery wrapped document. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. + * @param {object} $log window.console or an object with the same interface. * @param {object} $sniffer $sniffer service */ function Browser(window, document, $log, $sniffer) { @@ -4878,7 +4941,7 @@ function Browser(window, document, $log, $sniffer) { } finally { outstandingRequestCount--; if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { + while (outstandingRequestCallbacks.length) { try { outstandingRequestCallbacks.pop()(); } catch (e) { @@ -4889,6 +4952,11 @@ function Browser(window, document, $log, $sniffer) { } } + function getHash(url) { + var index = url.indexOf('#'); + return index === -1 ? '' : url.substr(index + 1); + } + /** * @private * Note: this method is used only by scenario runner @@ -4899,7 +4967,7 @@ function Browser(window, document, $log, $sniffer) { // force browser to execute all pollFns - this is needed so that cookies and other pollers fire // at some deterministic time in respect to the test runner's actions. Leaving things up to the // regular poller would result in flaky tests. - forEach(pollFns, function(pollFn){ pollFn(); }); + forEach(pollFns, function(pollFn) { pollFn(); }); if (outstandingRequestCount === 0) { callback(); @@ -4941,7 +5009,7 @@ function Browser(window, document, $log, $sniffer) { */ function startPoller(interval, setTimeout) { (function check() { - forEach(pollFns, function(pollFn){ pollFn(); }); + forEach(pollFns, function(pollFn) { pollFn(); }); pollTimeout = setTimeout(check, interval); })(); } @@ -4998,7 +5066,7 @@ function Browser(window, document, $log, $sniffer) { // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. // See https://github.com/angular/angular.js/commit/ffb2701 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { - return; + return self; } var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); lastBrowserUrl = url; @@ -5018,8 +5086,10 @@ function Browser(window, document, $log, $sniffer) { } if (replace) { location.replace(url); - } else { + } else if (!sameBase) { location.href = url; + } else { + location.hash = getHash(url); } } return self; @@ -5054,11 +5124,19 @@ function Browser(window, document, $log, $sniffer) { fireUrlChange(); } + function getCurrentState() { + try { + return history.state; + } catch (e) { + // MSIE can reportedly throw when there is no state (UNCONFIRMED). + } + } + // This variable should be used *only* inside the cacheState function. var lastCachedState = null; function cacheState() { // This should be the only place in $browser where `history.state` is read. - cachedState = window.history.state; + cachedState = getCurrentState(); cachedState = isUndefined(cachedState) ? null : cachedState; // Prevent callbacks fo fire twice if both hashchange & popstate were fired. @@ -5197,8 +5275,8 @@ function Browser(window, document, $log, $sniffer) { // - 20 cookies per unique domain // - 4096 bytes per cookie if (cookieLength > 4096) { - $log.warn("Cookie '"+ name + - "' possibly not set or overflowed because it was too large ("+ + $log.warn("Cookie '" + name + + "' possibly not set or overflowed because it was too large (" + cookieLength + " > 4096 bytes)!"); } } @@ -5276,9 +5354,9 @@ function Browser(window, document, $log, $sniffer) { } -function $BrowserProvider(){ +function $BrowserProvider() { this.$get = ['$window', '$log', '$sniffer', '$document', - function( $window, $log, $sniffer, $document){ + function($window, $log, $sniffer, $document) { return new Browser($window, $document, $log, $sniffer); }]; } @@ -5652,9 +5730,10 @@ function $CacheFactoryProvider() { * ``` * * **Note:** the `script` tag containing the template does not need to be included in the `head` of - * the document, but it must be below the `ng-app` definition. + * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, + * element with ng-app attribute), otherwise the template will be ignored. * - * Adding via the $templateCache service: + * Adding via the `$templateCache` service: * * ```js * var myApp = angular.module('myApp', []); @@ -5682,6 +5761,17 @@ function $TemplateCacheProvider() { }]; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: @@ -5746,7 +5836,8 @@ function $TemplateCacheProvider() { * templateNamespace: 'html', * scope: false, * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, - * controllerAs: 'stringAlias', + * controllerAs: 'stringIdentifier', + * bindToController: false, * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], * compile: function compile(tElement, tAttrs, transclude) { * return { @@ -5797,7 +5888,7 @@ function $TemplateCacheProvider() { * #### `multiElement` * When this property is set to true, the HTML compiler will collect DOM nodes between * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them - * together as the directive elements. It is recomended that this feature be used on directives + * together as the directive elements. It is recommended that this feature be used on directives * which are not strictly behavioural (such as {@link ngClick}), and which * do not manipulate or replace child nodes (such as {@link ngInclude}). * @@ -5846,7 +5937,9 @@ function $TemplateCacheProvider() { * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If + * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use + * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). * * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. * If no `attr` name is specified then the attribute name is assumed to be the same as the @@ -5860,7 +5953,7 @@ function $TemplateCacheProvider() { * * * #### `bindToController` - * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will + * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will * allow a component to have its properties bound to the controller, rather than to scope. When the controller * is instantiated, the initial values of the isolate scope bindings are already available. * @@ -5891,7 +5984,8 @@ function $TemplateCacheProvider() { * 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. The name can be prefixed with: + * 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: * * * (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. @@ -6062,9 +6156,15 @@ function $TemplateCacheProvider() { * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared * between all directive linking functions. * - * * `controller` - a controller instance - A controller instance if at least one directive on the - * element defines a controller. The controller is shared among all the directives, which allows - * the directives to use the controllers as a communication channel. + * * `controller` - the directive's required controller instance(s) - Instances are shared + * among all directives, which allows the directives to use the controllers as a communication + * channel. The exact value depends on the directive's `require` property: + * * `string`: the controller instance + * * `array`: array of controller instances + * * no controller(s) required: `undefined` + * + * If a required controller cannot be found, and it is optional, the instance is `null`, + * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. * * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. * This is the same as the `$transclude` @@ -6302,10 +6402,17 @@ function $TemplateCacheProvider() { * * * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. + * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. + * + *
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it + * e.g. will not use the right outer scope. Please pass the transclude function as a + * `parentBoundTranscludeFn` to the link function instead. + *
+ * * @param {number} maxPriority only apply directives lower than given priority (Only effects the * root element(s), not their children) - * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template + * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. @@ -6317,6 +6424,19 @@ function $TemplateCacheProvider() { * * `clonedElement` - is a clone of the original `element` passed into the compiler. * * `scope` - is the current scope with which the linking function is working with. * + * * `options` - An optional object hash with linking options. If `options` is provided, then the following + * keys may be used to control linking behavior: + * + * * `parentBoundTranscludeFn` - the transclude function made available to + * directives; if given, it will be passed through to the link functions of + * directives found in `element` during compilation. + * * `transcludeControllers` - an object hash with keys that map controller names + * to controller instances; if given, it will make the controllers + * available to directives. + * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add + * the cloned elements; only needed for transcludes that are allowed to contain non html + * elements (e.g. SVG elements). See also the directive.controller property. + * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * @@ -6362,8 +6482,8 @@ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/, + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; @@ -6373,7 +6493,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; function parseIsolateBindings(scope, directiveName) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; + var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; var bindings = {}; @@ -6388,15 +6508,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } bindings[scopeName] = { - attrName: match[3] || scopeName, - mode: match[1], - optional: match[2] === '?' + mode: match[1][0], + collection: match[2] === '*', + optional: match[3] === '?', + attrName: match[4] || scopeName }; }); return bindings; } + function assertValidDirectiveName(name) { + var letter = name.charAt(0); + if (!letter || letter !== lowercase(letter)) { + throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name); + } + return name; + } + /** * @ngdoc method * @name $compileProvider#directive @@ -6415,6 +6544,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { this.directive = function registerDirective(name, directiveFactory) { assertNotHasOwnProperty(name, 'directive'); if (isString(name)) { + assertValidDirectiveName(name); assertArg(directiveFactory, 'directiveFactory'); if (!hasDirectives.hasOwnProperty(name)) { hasDirectives[name] = []; @@ -6462,7 +6592,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * The sanitization is a security measure aimed at preventing XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` @@ -6529,14 +6659,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * `ng-binding` CSS class * * `$binding` data property containing an array of the binding expressions * - * You may want to use this in production for a significant performance boost. See + * You may want to disable this in production for a significant performance boost. See * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. * * The default value is true. */ var debugInfoEnabled = true; this.debugInfoEnabled = function(enabled) { - if(isDefined(enabled)) { + if (isDefined(enabled)) { debugInfoEnabled = enabled; return this; } @@ -6566,6 +6696,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }; Attributes.prototype = { + /** + * @ngdoc method + * @name $compile.directive.Attributes#$normalize + * @kind function + * + * @description + * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or + * `data-`) to its normalized, camelCase form. + * + * Also there is special case for Moz prefix starting with upper case letter. + * + * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} + * + * @param {string} name Name to normalize + */ $normalize: directiveNormalize, @@ -6580,8 +6725,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * @param {string} classVal The className value that will be added to the element */ - $addClass : function(classVal) { - if(classVal && classVal.length > 0) { + $addClass: function(classVal) { + if (classVal && classVal.length > 0) { $animate.addClass(this.$$element, classVal); } }, @@ -6597,8 +6742,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * @param {string} classVal The className value that will be removed from the element */ - $removeClass : function(classVal) { - if(classVal && classVal.length > 0) { + $removeClass: function(classVal) { + if (classVal && classVal.length > 0) { $animate.removeClass(this.$$element, classVal); } }, @@ -6615,7 +6760,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @param {string} newClasses The current CSS className value * @param {string} oldClasses The former CSS className value */ - $updateClass : function(newClasses, oldClasses) { + $updateClass: function(newClasses, oldClasses) { var toAdd = tokenDifference(newClasses, oldClasses); if (toAdd && toAdd.length) { $animate.addClass(this.$$element, toAdd); @@ -6645,13 +6790,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { booleanKey = getBooleanAttrName(node, key), aliasedKey = getAliasedAttrName(node, key), observer = key, - normalizedVal, nodeName; if (booleanKey) { this.$$element.prop(key, value); attrName = booleanKey; - } else if(aliasedKey) { + } else if (aliasedKey) { this[aliasedKey] = value; observer = aliasedKey; } @@ -6689,22 +6833,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // for each tuples var nbrUrisWith2parts = Math.floor(rawUris.length / 2); - for (var i=0; i - forEach($compileNodes, function(node, index){ + forEach($compileNodes, function(node, index) { if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { $compileNodes[index] = jqLite(node).wrap('').parent()[0]; } @@ -6835,8 +6979,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { maxPriority, ignoreDirective, previousCompileContext); compile.$$addScopeClass($compileNodes); var namespace = null; - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){ + return function publicLinkFn(scope, cloneConnectFn, options) { assertArg(scope, 'scope'); + + options = options || {}; + var parentBoundTranscludeFn = options.parentBoundTranscludeFn, + transcludeControllers = options.transcludeControllers, + futureParentElement = options.futureParentElement; + + // When `parentBoundTranscludeFn` is passed, it is a + // `controllersBoundTransclude` function (it was previously passed + // as `transclude` to directive.link) so we must unwrap it to get + // its `boundTranscludeFn` + if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { + parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; + } + if (!namespace) { namespace = detectNamespaceForChildElements(futureParentElement); } @@ -6878,7 +7036,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (!node) { return 'html'; } else { - return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html'; + return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; } } @@ -6960,7 +7118,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { stableNodeList = nodeList; } - for(i = 0, ii = linkFns.length; i < ii;) { + for (i = 0, ii = linkFns.length; i < ii;) { node = stableNodeList[linkFns[i++]]; nodeLinkFn = linkFns[i++]; childLinkFn = linkFns[i++]; @@ -6973,7 +7131,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { childScope = scope; } - if ( nodeLinkFn.transcludeOnThisElement ) { + if (nodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn( scope, nodeLinkFn.transclude, parentBoundTranscludeFn, nodeLinkFn.elementTranscludeOnThisElement); @@ -7006,7 +7164,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { transcludedScope.$$transcluded = true; } - return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement); + return transcludeFn(transcludedScope, cloneFn, { + parentBoundTranscludeFn: previousBoundTranscludeFn, + transcludeControllers: controllers, + futureParentElement: futureParentElement + }); }; return boundTranscludeFn; @@ -7028,7 +7190,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { match, className; - switch(nodeType) { + switch (nodeType) { case NODE_TYPE_ELEMENT: /* Element */ // use the node name: addDirective(directives, @@ -7047,7 +7209,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // support ngAttr attribute binding ngAttrName = directiveNormalize(name); if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { - name = snake_case(ngAttrName.substr(6), '-'); + name = name.replace(PREFIX_REGEXP, '') + .substr(8).replace(/_(.)/g, function(match, letter) { + return letter.toUpperCase(); + }); } var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); @@ -7074,6 +7239,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // use class as directive className = node.className; + if (isObject(className)) { + // Maybe SVGAnimatedString + className = className.animVal; + } if (isString(className) && className !== '') { while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { nName = directiveNormalize(match[2]); @@ -7120,7 +7289,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var nodes = []; var depth = 0; if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { - var startNode = node; do { if (!node) { throw $compileMinErr('uterdir', @@ -7204,7 +7372,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directiveValue; // executes all directives on the current element - for(var i = 0, ii = directives.length; i < ii; i++) { + for (var i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; var attrStart = directive.$$start; var attrEnd = directive.$$end; @@ -7448,7 +7616,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName); } - return value; + return value || null; } else if (isArray(require)) { value = []; forEach(require, function(require) { @@ -7475,7 +7643,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { isolateScope = scope.$new(true); } - transcludeFn = boundTranscludeFn && controllersBoundTransclude; + if (boundTranscludeFn) { + // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` + // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` + transcludeFn = controllersBoundTransclude; + transcludeFn.$$boundTransclude = boundTranscludeFn; + } + if (controllerDirectives) { // TODO: merge `controllers` and `elementControllers` into single object. controllers = {}; @@ -7510,8 +7684,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (newIsolateScopeDirective) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || templateDirective === newIsolateScopeDirective.$$originalDirective))); compile.$$addScopeClass($element, true); @@ -7537,7 +7709,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { isolateBindingContext[scopeName] = value; }); attrs.$$observers[attrName].$$scope = scope; - if( attrs[attrName] ) { + if (attrs[attrName]) { // If the attribute has been provided then we trigger an interpolation to ensure // the value is there for use in the link fn isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope); @@ -7552,7 +7724,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (parentGet.literal) { compare = equals; } else { - compare = function(a,b) { return a === b || (a !== a && b !== b); }; + compare = function(a, b) { return a === b || (a !== a && b !== b); }; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest @@ -7576,7 +7748,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return lastValue = parentValue; }; parentValueWatch.$stateful = true; - var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); + var unwatch; + if (definition.collection) { + unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); + } else { + unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); + } isolateScope.$on('$destroy', unwatch); break; @@ -7597,7 +7774,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } // PRELINKING - for(i = 0, ii = preLinkFns.length; i < ii; i++) { + for (i = 0, ii = preLinkFns.length; i < ii; i++) { linkFn = preLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, @@ -7618,7 +7795,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); // POSTLINKING - for(i = postLinkFns.length - 1; i >= 0; i--) { + for (i = postLinkFns.length - 1; i >= 0; i--) { linkFn = postLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, @@ -7678,11 +7855,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (name === ignoreDirective) return null; var match = null; if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i directive.priority) && + if ((maxPriority === undefined || maxPriority > directive.priority) && directive.restrict.indexOf(location) != -1) { if (startAttrName) { directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); @@ -7690,7 +7867,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { tDirectives.push(directive); match = directive; } - } catch(e) { $exceptionHandler(e); } + } catch (e) { $exceptionHandler(e); } } } return match; @@ -7707,8 +7884,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { */ function directiveIsMultiElement(name) { if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i'+template+''; + wrapper.innerHTML = '<' + type + '>' + template + ''; return wrapper.childNodes[0].childNodes; default: return template; @@ -7951,7 +8127,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { - var interpolateFn = $interpolate(value, true); + var trustedContext = getTrustedContext(node, name); + allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; + + var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); // no interpolation found -> ignore if (!interpolateFn) return; @@ -7976,16 +8155,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { "ng- versions (such as ng-click instead of onclick) instead."); } - // If the attribute was removed, then we are done - if (!attr[name]) { - return; + // If the attribute has changed since last $interpolate()ed + var newValue = attr[name]; + if (newValue !== value) { + // we need to interpolate again since the attribute value has been updated + // (e.g. by another directive's compile function) + // ensure unset/empty values make interpolateFn falsy + interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); + value = newValue; } - // we need to interpolate again, in case the attribute value has been updated - // (e.g. by another directive's compile function) - interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name), - ALL_OR_NOTHING_ATTRS[name] || allOrNothing); - // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; @@ -8004,7 +8183,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { //skip animations when the first digest occurs (when //both the new and the old values are the same) since //the CSS classes are the non-interpolated values - if(name === 'class' && newValue != oldValue) { + if (name === 'class' && newValue != oldValue) { attr.$updateClass(newValue, oldValue); } else { attr.$set(name, newValue); @@ -8034,7 +8213,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { i, ii; if ($rootElement) { - for(i = 0, ii = $rootElement.length; i < ii; i++) { + for (i = 0, ii = $rootElement.length; i < ii; i++) { if ($rootElement[i] == firstElementToRemove) { $rootElement[i++] = newNode; for (var j = i, j2 = j + removeCount - 1, @@ -8109,23 +8288,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { try { linkFn(scope, $element, attrs, controllers, transcludeFn); - } catch(e) { + } catch (e) { $exceptionHandler(e, startingTag($element)); } } }]; } -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; /** * Converts all accepted directives format into proper directive name. - * All of these will become 'myDirective': - * my:Directive - * my-directive - * x-my-directive - * data-my:directive - * - * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function directiveNormalize(name) { @@ -8182,7 +8354,7 @@ function nodesetLinkingFn( /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn -){} +) {} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, @@ -8190,7 +8362,7 @@ function directiveLinkingFn( /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn -){} +) {} function tokenDifference(str1, str2) { var values = '', @@ -8198,10 +8370,10 @@ function tokenDifference(str1, str2) { tokens2 = str2.split(/\s+/); outer: - for(var i = 0; i < tokens1.length; i++) { + for (var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; + for (var j = 0; j < tokens2.length; j++) { + if (token == tokens2[j]) continue outer; } values += (values.length > 0 ? ' ' : '') + token; } @@ -8225,6 +8397,8 @@ function removeComments(jqNodes) { return jqNodes; } +var $controllerMinErr = minErr('$controller'); + /** * @ngdoc provider * @name $controllerProvider @@ -8284,6 +8458,10 @@ function $ControllerProvider() { * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global * `window` object (not recommended) * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * @@ -8307,8 +8485,13 @@ function $ControllerProvider() { identifier = ident; } - if(isString(expression)) { - match = expression.match(CNTRL_REG), + if (isString(expression)) { + match = expression.match(CNTRL_REG); + if (!match) { + throw $controllerMinErr('ctrlfmt', + "Badly formed controller string '{0}'. " + + "Must match `__name__ as __id__` or `__name__`.", expression); + } constructor = match[1], identifier = identifier || match[3]; expression = controllers.hasOwnProperty(constructor) @@ -8329,10 +8512,10 @@ function $ControllerProvider() { // // This feature is not intended for use by applications, and is thus not documented // publicly. - var Constructor = function() {}; - Constructor.prototype = (isArray(expression) ? + // Object creation: http://jsperf.com/create-constructor/2 + var controllerPrototype = (isArray(expression) ? expression[expression.length - 1] : expression).prototype; - instance = new Constructor(); + instance = Object.create(controllerPrototype || null); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); @@ -8393,8 +8576,8 @@ function $ControllerProvider() {
*/ -function $DocumentProvider(){ - this.$get = ['$window', function(window){ +function $DocumentProvider() { + this.$get = ['$window', function(window) { return jqLite(window.document); }]; } @@ -8415,8 +8598,8 @@ function $DocumentProvider(){ * ## Example: * * ```js - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { - * return function (exception, cause) { + * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { + * return function(exception, cause) { * exception.message += ' (caused by "' + cause + '")'; * throw exception; * }; @@ -8447,6 +8630,36 @@ function $ExceptionHandlerProvider() { }]; } +var APPLICATION_JSON = 'application/json'; +var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; +var JSON_START = /^\[|^\{(?!\{)/; +var JSON_ENDS = { + '[': /]$/, + '{': /}$/ +}; +var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; + +function defaultHttpResponseTransform(data, headers) { + if (isString(data)) { + // Strip json vulnerability protection prefix and trim whitespace + var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); + + if (tempData) { + var contentType = headers('Content-Type'); + if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { + data = fromJson(tempData); + } + } + } + + return data; +} + +function isJsonLike(str) { + var jsonStart = str.match(JSON_START); + return jsonStart && JSON_ENDS[jsonStart[0]].test(str); +} + /** * Parse headers into key value object * @@ -8454,7 +8667,7 @@ function $ExceptionHandlerProvider() { * @returns {Object} Parsed headers as key value object */ function parseHeaders(headers) { - var parsed = {}, key, val, i; + var parsed = createMap(), key, val, i; if (!headers) return parsed; @@ -8491,7 +8704,11 @@ function headersGetter(headers) { if (!headersObj) headersObj = parseHeaders(headers); if (name) { - return headersObj[lowercase(name)] || null; + var value = headersObj[lowercase(name)]; + if (value === void 0) { + value = null; + } + return value; } return headersObj; @@ -8505,16 +8722,17 @@ function headersGetter(headers) { * This function is used for both request and response transforming * * @param {*} data Data to transform. - * @param {function(string=)} headers Http headers getter fn. + * @param {function(string=)} headers HTTP headers getter fn. + * @param {number} status HTTP status code of the response. * @param {(Function|Array.)} fns Function or an array of functions. * @returns {*} Transformed data. */ -function transformData(data, headers, fns) { +function transformData(data, headers, status, fns) { if (isFunction(fns)) - return fns(data, headers); + return fns(data, headers, status); forEach(fns, function(fn) { - data = fn(data, headers); + data = fn(data, headers, status); }); return data; @@ -8533,12 +8751,6 @@ function isSuccess(status) { * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. * */ function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/, - APPLICATION_JSON = 'application/json', - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; - /** * @ngdoc property * @name $httpProvider#defaults @@ -8546,6 +8758,11 @@ function $HttpProvider() { * * Object containing default values for all {@link ng.$http $http} requests. * + * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} + * that will provide the cache for all requests who set their `cache` property to `true`. + * If you set the `default.cache = false` then only requests that specify their own custom + * cache object will be cached. See {@link $http#caching $http Caching} for more information. + * * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. * Defaults value is `'XSRF-TOKEN'`. * @@ -8559,25 +8776,15 @@ function $HttpProvider() { * - **`defaults.headers.post`** * - **`defaults.headers.put`** * - **`defaults.headers.patch`** + * **/ var defaults = this.defaults = { // transform incoming response data - transformResponse: [function defaultHttpResponseTransform(data, headers) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - var contentType = headers('Content-Type'); - if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) || - (JSON_START.test(data) && JSON_END.test(data))) { - data = fromJson(data); - } - } - return data; - }], + transformResponse: [defaultHttpResponseTransform], // transform outgoing request data transformRequest: [function(d) { - return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; + return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; }], // default headers @@ -8623,9 +8830,18 @@ function $HttpProvider() { }; /** - * Are ordered by request, i.e. they are applied in the same order as the + * @ngdoc property + * @name $httpProvider#interceptors + * @description + * + * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} + * pre-processing of request or postprocessing of responses. + * + * These service factories are ordered by request, i.e. they are applied in the same order as the * array, on request, but reverse order, on response. - */ + * + * {@link ng.$http#interceptors Interceptors detailed info} + **/ var interceptorFactories = this.interceptors = []; this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', @@ -8775,12 +8991,27 @@ function $HttpProvider() { * In addition, you can supply a `headers` property in the config object passed when * calling `$http(config)`, which overrides the defaults without changing them globally. * + * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, + * Use the `headers` property, setting the desired header to `undefined`. For example: + * + * ```js + * var req = { + * method: 'POST', + * url: 'http://example.com', + * headers: { + * 'Content-Type': undefined + * }, + * data: { test: 'test' } + * } + * + * $http(req).success(function(){...}).error(function(){...}); + * ``` * * ## Transforming Requests and Responses * * Both requests and responses can be transformed using transformation functions: `transformRequest` * and `transformResponse`. These properties can be a single function that returns - * the transformed value (`{function(data, headersGetter)`) or an array of such transformation functions, + * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, * which allows you to `push` or `unshift` a new transformation function into the transformation chain. * * ### Default Transformations @@ -9021,12 +9252,14 @@ function $HttpProvider() { * `{function(data, headersGetter)|Array.}` – * transform function or an array of such functions. The transform function takes the http * request body and headers and returns its transformed (typically serialized) version. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} * - **transformResponse** – - * `{function(data, headersGetter)|Array.}` – + * `{function(data, headersGetter, status)|Array.}` – * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} + * response body, headers and status and returns its transformed (typically deserialized) version. + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the * GET request, otherwise if a cache instance built with * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for @@ -9147,20 +9380,23 @@ function $HttpProvider() { */ function $http(requestConfig) { - var config = { + + if (!angular.isObject(requestConfig)) { + throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); + } + + var config = extend({ method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse - }; - var headers = mergeHeaders(requestConfig); + }, requestConfig); - extend(config, requestConfig); - config.headers = headers; + config.headers = mergeHeaders(requestConfig); config.method = uppercase(config.method); var serverRequest = function(config) { - headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + var headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); // strip content-type if data is undefined if (isUndefined(reqData)) { @@ -9176,7 +9412,7 @@ function $HttpProvider() { } // send request - return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + return sendReq(config, reqData).then(transformResponse, transformResponse); }; var chain = [serverRequest, undefined]; @@ -9192,7 +9428,7 @@ function $HttpProvider() { } }); - while(chain.length) { + while (chain.length) { var thenFn = chain.shift(); var rejectFn = chain.shift(); @@ -9200,6 +9436,8 @@ function $HttpProvider() { } promise.success = function(fn) { + assertArgFn(fn, 'fn'); + promise.then(function(response) { fn(response.data, response.status, response.headers, config); }); @@ -9207,6 +9445,8 @@ function $HttpProvider() { }; promise.error = function(fn) { + assertArgFn(fn, 'fn'); + promise.then(null, function(response) { fn(response.data, response.status, response.headers, config); }); @@ -9221,13 +9461,30 @@ function $HttpProvider() { if (!response.data) { resp.data = response.data; } else { - resp.data = transformData(response.data, response.headers, config.transformResponse); + resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); } return (isSuccess(response.status)) ? resp : $q.reject(resp); } + function executeHeaderFns(headers) { + var headerContent, processedHeaders = {}; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + processedHeaders[header] = headerContent; + } + } else { + processedHeaders[header] = headerFn; + } + }); + + return processedHeaders; + } + function mergeHeaders(config) { var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), @@ -9250,23 +9507,7 @@ function $HttpProvider() { } // execute if header value is a function for merged headers - execHeaders(reqHeaders); - return reqHeaders; - - function execHeaders(headers) { - var headerContent; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(); - if (headerContent != null) { - headers[header] = headerContent; - } else { - delete headers[header]; - } - } - }); - } + return executeHeaderFns(reqHeaders); } } @@ -9409,11 +9650,12 @@ function $HttpProvider() { * !!! ACCESSES CLOSURE VARS: * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests */ - function sendReq(config, reqData, reqHeaders) { + function sendReq(config, reqData) { var deferred = $q.defer(), promise = deferred.promise, cache, cachedResp, + reqHeaders = config.headers, url = buildUrl(config.url, config.params); $http.pendingRequests.push(config); @@ -9432,8 +9674,7 @@ function $HttpProvider() { if (isDefined(cachedResp)) { if (isPromiseLike(cachedResp)) { // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; + cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); } else { // serving from cache if (isArray(cachedResp)) { @@ -9507,10 +9748,13 @@ function $HttpProvider() { status: status, headers: headersGetter(headers), config: config, - statusText : statusText + statusText: statusText }); } + function resolvePromiseWithResult(result) { + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); + } function removePendingReq() { var idx = $http.pendingRequests.indexOf(config); @@ -9528,7 +9772,7 @@ function $HttpProvider() { forEach(value, function(v) { if (isObject(v)) { - if (isDate(v)){ + if (isDate(v)) { v = v.toISOString(); } else { v = toJson(v); @@ -9538,7 +9782,7 @@ function $HttpProvider() { encodeUriQuery(v)); }); }); - if(parts.length > 0) { + if (parts.length > 0) { url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } return url; @@ -9625,7 +9869,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc statusText); }; - var requestError = function () { + var requestError = function() { // The response is always empty // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error completeRequest(callback, -1, null, null, ''); @@ -9672,7 +9916,9 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc function completeRequest(callback, status, response, headersString, statusText) { // cancel timeout and subsequent timeout promise resolution - timeoutId && $browserDefer.cancel(timeoutId); + if (timeoutId !== undefined) { + $browserDefer.cancel(timeoutId); + } jsonpDone = xhr = null; callback(status, response, headersString, statusText); @@ -9681,7 +9927,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc }; function jsonpReq(url, callbackId, done) { - // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: + // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: // - fetches local scripts via XHR and evals them // - adds and immediately removes script elements from the document var script = rawDocument.createElement('script'), callback = null; @@ -9767,7 +10013,7 @@ function $InterpolateProvider() { * @param {string=} value new value to set the starting symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ - this.startSymbol = function(value){ + this.startSymbol = function(value) { if (value) { startSymbol = value; return this; @@ -9785,7 +10031,7 @@ function $InterpolateProvider() { * @param {string=} value new value to set the ending symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ - this.endSymbol = function(value){ + this.endSymbol = function(value) { if (value) { endSymbol = value; return this; @@ -9911,9 +10157,9 @@ function $InterpolateProvider() { concat = [], expressionPositions = []; - while(index < textLength) { - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { + while (index < textLength) { + if (((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { if (index !== startIndex) { concat.push(unescapeText(text.substring(index, startIndex))); } @@ -9947,20 +10193,20 @@ function $InterpolateProvider() { if (!mustHaveExpression || expressions.length) { var compute = function(values) { - for(var i = 0, ii = expressions.length; i < ii; i++) { + for (var i = 0, ii = expressions.length; i < ii; i++) { if (allOrNothing && isUndefined(values[i])) return; concat[expressionPositions[i]] = values[i]; } return concat.join(''); }; - var getValue = function (value) { + var getValue = function(value) { return trustedContext ? $sce.getTrusted(trustedContext, value) : $sce.valueOf(value); }; - var stringify = function (value) { + var stringify = function(value) { if (value == null) { // null || undefined return ''; } @@ -9988,7 +10234,7 @@ function $InterpolateProvider() { } return compute(values); - } catch(err) { + } catch (err) { var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); $exceptionHandler(newErr); @@ -9998,7 +10244,7 @@ function $InterpolateProvider() { // all of these properties are undocumented for now exp: text, //just for compatibility with regular watchers created via $watch expressions: expressions, - $$watchDelegate: function (scope, listener, objectEquality) { + $$watchDelegate: function(scope, listener, objectEquality) { var lastValue; return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { var currValue = compute(values); @@ -10018,8 +10264,9 @@ function $InterpolateProvider() { function parseStringifyInterceptor(value) { try { - return stringify(getValue(value)); - } catch(err) { + value = getValue(value); + return allOrNothing && !isDefined(value) ? value : stringify(value); + } catch (err) { var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); $exceptionHandler(newErr); @@ -10119,33 +10366,33 @@ function $IntervalProvider() { * // Don't start a new fight if we are already fighting * if ( angular.isDefined(stop) ) return; * - * stop = $interval(function() { - * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { - * $scope.blood_1 = $scope.blood_1 - 3; - * $scope.blood_2 = $scope.blood_2 - 4; - * } else { - * $scope.stopFight(); - * } - * }, 100); - * }; + * stop = $interval(function() { + * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { + * $scope.blood_1 = $scope.blood_1 - 3; + * $scope.blood_2 = $scope.blood_2 - 4; + * } else { + * $scope.stopFight(); + * } + * }, 100); + * }; * - * $scope.stopFight = function() { - * if (angular.isDefined(stop)) { - * $interval.cancel(stop); - * stop = undefined; - * } - * }; + * $scope.stopFight = function() { + * if (angular.isDefined(stop)) { + * $interval.cancel(stop); + * stop = undefined; + * } + * }; * - * $scope.resetFight = function() { - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * }; + * $scope.resetFight = function() { + * $scope.blood_1 = 100; + * $scope.blood_2 = 120; + * }; * - * $scope.$on('$destroy', function() { - * // Make sure that the interval is destroyed too - * $scope.stopFight(); - * }); - * }]) + * $scope.$on('$destroy', function() { + * // Make sure that the interval is destroyed too + * $scope.stopFight(); + * }); + * }]) * // Register the 'myCurrentTime' directive factory method. * // We inject $interval and dateFilter service since the factory method is DI. * .directive('myCurrentTime', ['$interval', 'dateFilter', @@ -10258,7 +10505,7 @@ function $IntervalProvider() { * * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ -function $LocaleProvider(){ +function $LocaleProvider() { this.$get = function() { return { id: 'en-us', @@ -10301,13 +10548,21 @@ function $LocaleProvider(){ SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), AMPMS: ['AM','PM'], medium: 'MMM d, y h:mm:ss a', - short: 'M/d/yy h:mm a', + 'short': 'M/d/yy h:mm a', fullDate: 'EEEE, MMMM d, y', longDate: 'MMMM d, y', mediumDate: 'MMM d, y', shortDate: 'M/d/yy', mediumTime: 'h:mm:ss a', - shortTime: 'h:mm a' + shortTime: 'h:mm a', + ERANAMES: [ + "Before Christ", + "Anno Domini" + ], + ERAS: [ + "BC", + "AD" + ] }, pluralCat: function(num) { @@ -10342,8 +10597,8 @@ function encodePath(path) { return segments.join('/'); } -function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { - var parsedUrl = urlResolve(absoluteUrl, appBase); +function parseAbsoluteUrl(absoluteUrl, locationObj) { + var parsedUrl = urlResolve(absoluteUrl); locationObj.$$protocol = parsedUrl.protocol; locationObj.$$host = parsedUrl.hostname; @@ -10351,12 +10606,12 @@ function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { } -function parseAppUrl(relativeUrl, locationObj, appBase) { +function parseAppUrl(relativeUrl, locationObj) { var prefixed = (relativeUrl.charAt(0) !== '/'); if (prefixed) { relativeUrl = '/' + relativeUrl; } - var match = urlResolve(relativeUrl, appBase); + var match = urlResolve(relativeUrl); locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname); locationObj.$$search = parseKeyValue(match.search); @@ -10388,6 +10643,10 @@ function stripHash(url) { return index == -1 ? url : url.substr(0, index); } +function trimEmptyHash(url) { + return url.replace(/(#.+)|#$/, '$1'); +} + function stripFile(url) { return url.substr(0, stripHash(url).lastIndexOf('/') + 1); @@ -10411,12 +10670,12 @@ function LocationHtml5Url(appBase, basePrefix) { this.$$html5 = true; basePrefix = basePrefix || ''; var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); + parseAbsoluteUrl(appBase, this); /** * Parse given html5 (regular) url string into properties - * @param {string} newAbsoluteUrl HTML5 url + * @param {string} url HTML5 url * @private */ this.$$parse = function(url) { @@ -10426,7 +10685,7 @@ function LocationHtml5Url(appBase, basePrefix) { appBaseNoFile); } - parseAppUrl(pathUrl, this, appBase); + parseAppUrl(pathUrl, this); if (!this.$$path) { this.$$path = '/'; @@ -10457,14 +10716,14 @@ function LocationHtml5Url(appBase, basePrefix) { var appUrl, prevAppUrl; var rewrittenUrl; - if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { + if ((appUrl = beginsWith(appBase, url)) !== undefined) { prevAppUrl = appUrl; - if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { + if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); } else { rewrittenUrl = appBase + prevAppUrl; } - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { + } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { rewrittenUrl = appBaseNoFile + appUrl; } else if (appBaseNoFile == url + '/') { rewrittenUrl = appBaseNoFile; @@ -10489,7 +10748,7 @@ function LocationHtml5Url(appBase, basePrefix) { function LocationHashbangUrl(appBase, hashPrefix) { var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); + parseAbsoluteUrl(appBase, this); /** @@ -10499,17 +10758,26 @@ function LocationHashbangUrl(appBase, hashPrefix) { */ this.$$parse = function(url) { var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); - var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' - ? beginsWith(hashPrefix, withoutBaseUrl) - : (this.$$html5) - ? withoutBaseUrl - : ''; + var withoutHashUrl; - if (!isString(withoutHashUrl)) { - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, - hashPrefix); + if (withoutBaseUrl.charAt(0) === '#') { + + // The rest of the url starts with a hash so we have + // got either a hashbang path or a plain hash fragment + withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); + if (isUndefined(withoutHashUrl)) { + // There was no hashbang prefix so we just have a hash fragment + withoutHashUrl = withoutBaseUrl; + } + + } else { + // There was no hashbang path nor hash fragment: + // If we are in HTML5 mode we use what is left as the path; + // Otherwise we ignore what is left + withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; } - parseAppUrl(withoutHashUrl, this, appBase); + + parseAppUrl(withoutHashUrl, this); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); @@ -10526,7 +10794,7 @@ function LocationHashbangUrl(appBase, hashPrefix) { * Inside of Angular, we're always using pathnames that * do not include drive names for routing. */ - function removeWindowsDriveName (path, url, base) { + function removeWindowsDriveName(path, url, base) { /* Matches paths for file protocol on windows, such as /C:/foo/bar, and captures only /foo/bar. @@ -10563,7 +10831,7 @@ function LocationHashbangUrl(appBase, hashPrefix) { }; this.$$parseLinkUrl = function(url, relHref) { - if(stripHash(appBase) == stripHash(url)) { + if (stripHash(appBase) == stripHash(url)) { this.$$parse(url); return true; } @@ -10598,11 +10866,11 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) { var rewrittenUrl; var appUrl; - if ( appBase == stripHash(url) ) { + if (appBase == stripHash(url)) { rewrittenUrl = url; - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { + } else if ((appUrl = beginsWith(appBaseNoFile, url))) { rewrittenUrl = appBase + hashPrefix + appUrl; - } else if ( appBaseNoFile === url + '/') { + } else if (appBaseNoFile === url + '/') { rewrittenUrl = appBaseNoFile; } if (rewrittenUrl) { @@ -10647,6 +10915,13 @@ var locationPrototype = { * Return full url representation with all segments encoded according to rules specified in * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var absUrl = $location.absUrl(); + * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" + * ``` + * * @return {string} full url */ absUrl: locationGetter('$$absUrl'), @@ -10662,6 +10937,13 @@ var locationPrototype = { * * Change path, search and hash, when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var url = $location.url(); + * // => "/some/path?foo=bar&baz=xoxo" + * ``` + * * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) * @return {string} url */ @@ -10670,8 +10952,8 @@ var locationPrototype = { return this.$$url; var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); + if (match[1] || url === '') this.path(decodeURIComponent(match[1])); + if (match[2] || match[1] || url === '') this.search(match[3] || ''); this.hash(match[5] || ''); return this; @@ -10686,6 +10968,13 @@ var locationPrototype = { * * Return protocol of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var protocol = $location.protocol(); + * // => "http" + * ``` + * * @return {string} protocol of current url */ protocol: locationGetter('$$protocol'), @@ -10699,6 +10988,21 @@ var locationPrototype = { * * Return host of current url. * + * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. + * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var host = $location.host(); + * // => "example.com" + * + * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo + * host = $location.host(); + * // => "example.com" + * host = location.host; + * // => "example.com:8080" + * ``` + * * @return {string} host of current url. */ host: locationGetter('$$host'), @@ -10712,6 +11016,13 @@ var locationPrototype = { * * Return port of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var port = $location.port(); + * // => 80 + * ``` + * * @return {Number} port */ port: locationGetter('$$port'), @@ -10730,6 +11041,13 @@ var locationPrototype = { * Note: Path should always begin with forward slash (/), this method will add the forward slash * if it is missing. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var path = $location.path(); + * // => "/some/path" + * ``` + * * @param {(string|number)=} path New path * @return {string} path */ @@ -10755,10 +11073,9 @@ var locationPrototype = { * var searchObject = $location.search(); * // => {foo: 'bar', baz: 'xoxo'} * - * * // set foo to 'yipee' * $location.search('foo', 'yipee'); - * // => $location + * // $location.search() => {foo: 'yipee', baz: 'xoxo'} * ``` * * @param {string|Object.|Object.>} search New search params - string or @@ -10828,6 +11145,13 @@ var locationPrototype = { * * Change hash fragment when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue + * var hash = $location.hash(); + * // => "hashValue" + * ``` + * * @param {(string|number)=} hash New hash fragment * @return {string} hash */ @@ -10849,7 +11173,7 @@ var locationPrototype = { } }; -forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) { +forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { Location.prototype = Object.create(locationPrototype); /** @@ -10941,7 +11265,7 @@ function locationGetterSetter(property, preprocess) { * @description * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ -function $LocationProvider(){ +function $LocationProvider() { var hashPrefix = '', html5Mode = { enabled: false, @@ -11047,8 +11371,8 @@ function $LocationProvider(){ * @param {string=} oldState History state object that was before it was changed. */ - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function( $rootScope, $browser, $sniffer, $rootElement) { + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', + function($rootScope, $browser, $sniffer, $rootElement, $window) { var $location, LocationMode, baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' @@ -11096,7 +11420,7 @@ function $LocationProvider(){ // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) // currently we open nice url link and redirect then - if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return; + if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; var elm = jqLite(event.target); @@ -11130,7 +11454,7 @@ function $LocationProvider(){ if ($location.absUrl() != $browser.url()) { $rootScope.$apply(); // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; + $window.angular['ff-684208-preventDefault'] = true; } } } @@ -11138,7 +11462,7 @@ function $LocationProvider(){ // rewrite hashbang url <> html5 url - if ($location.absUrl() != initialUrl) { + if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { $browser.url($location.absUrl(), true); } @@ -11149,11 +11473,19 @@ function $LocationProvider(){ $rootScope.$evalAsync(function() { var oldUrl = $location.absUrl(); var oldState = $location.$$state; + var defaultPrevented; $location.$$parse(newUrl); $location.$$state = newState; - if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - newState, oldState).defaultPrevented) { + + defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + newState, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; setBrowserUrlWithFallback(oldUrl, false, oldState); @@ -11167,23 +11499,31 @@ function $LocationProvider(){ // update browser $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); + var oldUrl = trimEmptyHash($browser.url()); + var newUrl = trimEmptyHash($location.absUrl()); var oldState = $browser.state(); var currentReplace = $location.$$replace; - var urlOrStateChanged = oldUrl !== $location.absUrl() || + var urlOrStateChanged = oldUrl !== newUrl || ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); if (initializing || urlOrStateChanged) { initializing = false; $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl, - $location.$$state, oldState).defaultPrevented) { + var newUrl = $location.absUrl(); + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + $location.$$state, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; } else { if (urlOrStateChanged) { - setBrowserUrlWithFallback($location.absUrl(), currentReplace, + setBrowserUrlWithFallback(newUrl, currentReplace, oldState === $location.$$state ? null : $location.$$state); } afterLocationChange(oldUrl, oldState); @@ -11238,6 +11578,7 @@ function $LocationProvider(){ + @@ -11249,7 +11590,7 @@ function $LocationProvider(){ * @description * Use the `$logProvider` to configure how the application logs messages */ -function $LogProvider(){ +function $LogProvider() { var debug = true, self = this; @@ -11269,7 +11610,7 @@ function $LogProvider(){ } }; - this.$get = ['$window', function($window){ + this.$get = ['$window', function($window) { return { /** * @ngdoc method @@ -11314,7 +11655,7 @@ function $LogProvider(){ * @description * Write a debug message */ - debug: (function () { + debug: (function() { var fn = consoleLog('debug'); return function() { @@ -11368,12 +11709,23 @@ function $LogProvider(){ }]; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + var $parseMinErr = minErr('$parse'); // Sandboxing Angular Expressions // ------------------------------ // Angular expressions are generally considered safe because these expressions only have direct -// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by +// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by // obtaining a reference to native JS functions such as the Function constructor. // // As an example, consider the following Angular expression: @@ -11382,7 +11734,7 @@ var $parseMinErr = minErr('$parse'); // // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits // against the expression language, but not to prevent exploits that were enabled by exposing -// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good +// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good // practice and therefore we are not even trying to protect against interaction with an object // explicitly exposed in this way. // @@ -11390,6 +11742,8 @@ var $parseMinErr = minErr('$parse'); // window or some DOM object that has a reference to window is published onto a Scope. // Similarly we prevent invocations of function known to be dangerous, as well as assignments to // native objects. +// +// See https://docs.angularjs.org/guide/security function ensureSafeMemberName(name, fullExpression) { @@ -11398,7 +11752,7 @@ function ensureSafeMemberName(name, fullExpression) { || name === "__proto__") { throw $parseMinErr('isecfld', 'Attempting to access a disallowed field in Angular expressions! ' - +'Expression: {0}', fullExpression); + + 'Expression: {0}', fullExpression); } return name; } @@ -11467,7 +11821,7 @@ CONSTANTS['this'].sharedGetter = true; //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter var OPERATORS = extend(createMap(), { - '+':function(self, locals, a,b){ + '+':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); if (isDefined(a)) { if (isDefined(b)) { @@ -11475,25 +11829,25 @@ var OPERATORS = extend(createMap(), { } return a; } - return isDefined(b)?b:undefined;}, - '-':function(self, locals, a,b){ + return isDefined(b) ? b : undefined;}, + '-':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); - return (isDefined(a)?a:0)-(isDefined(b)?b:0); + return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); }, - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, - '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, - '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, - '!':function(self, locals, a){return !a(self, locals);}, + '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, + '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, + '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, + '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, + '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, + '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, + '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, + '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, + '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, + '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, + '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, + '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, + '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, + '!':function(self, locals, a) {return !a(self, locals);}, //Tokenized as operators but parsed as assignment/filters '=':true, @@ -11508,54 +11862,41 @@ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"' /** * @constructor */ -var Lexer = function (options) { +var Lexer = function(options) { this.options = options; }; Lexer.prototype = { constructor: Lexer, - lex: function (text) { + lex: function(text) { this.text = text; this.index = 0; - this.ch = undefined; this.tokens = []; while (this.index < this.text.length) { - this.ch = this.text.charAt(this.index); - if (this.is('"\'')) { - this.readString(this.ch); - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + var ch = this.text.charAt(this.index); + if (ch === '"' || ch === "'") { + this.readString(ch); + } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { this.readNumber(); - } else if (this.isIdent(this.ch)) { + } else if (this.isIdent(ch)) { this.readIdent(); - } else if (this.is('(){}[].,;:?')) { - this.tokens.push({ - index: this.index, - text: this.ch - }); + } else if (this.is(ch, '(){}[].,;:?')) { + this.tokens.push({index: this.index, text: ch}); this.index++; - } else if (this.isWhitespace(this.ch)) { + } else if (this.isWhitespace(ch)) { this.index++; } else { - var ch2 = this.ch + this.peek(); + var ch2 = ch + this.peek(); var ch3 = ch2 + this.peek(2); - var fn = OPERATORS[this.ch]; - var fn2 = OPERATORS[ch2]; - var fn3 = OPERATORS[ch3]; - if (fn3) { - this.tokens.push({index: this.index, text: ch3, fn: fn3}); - this.index += 3; - } else if (fn2) { - this.tokens.push({index: this.index, text: ch2, fn: fn2}); - this.index += 2; - } else if (fn) { - this.tokens.push({ - index: this.index, - text: this.ch, - fn: fn - }); - this.index += 1; + var op1 = OPERATORS[ch]; + var op2 = OPERATORS[ch2]; + var op3 = OPERATORS[ch3]; + if (op1 || op2 || op3) { + var token = op3 ? ch3 : (op2 ? ch2 : ch); + this.tokens.push({index: this.index, text: token, operator: true}); + this.index += token.length; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } @@ -11564,8 +11905,8 @@ Lexer.prototype = { return this.tokens; }, - is: function(chars) { - return chars.indexOf(this.ch) !== -1; + is: function(ch, chars) { + return chars.indexOf(ch) !== -1; }, peek: function(i) { @@ -11574,7 +11915,7 @@ Lexer.prototype = { }, isNumber: function(ch) { - return ('0' <= ch && ch <= '9'); + return ('0' <= ch && ch <= '9') && typeof ch === "string"; }, isWhitespace: function(ch) { @@ -11627,79 +11968,28 @@ Lexer.prototype = { } this.index++; } - number = 1 * number; this.tokens.push({ index: start, text: number, constant: true, - fn: function() { return number; } + value: Number(number) }); }, readIdent: function() { - var expression = this.text; - - var ident = ''; var start = this.index; - - var lastDot, peekIndex, methodName, ch; - while (this.index < this.text.length) { - ch = this.text.charAt(this.index); - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { - if (ch === '.') lastDot = this.index; - ident += ch; - } else { + var ch = this.text.charAt(this.index); + if (!(this.isIdent(ch) || this.isNumber(ch))) { break; } this.index++; } - - //check if the identifier ends with . and if so move back one char - if (lastDot && ident[ident.length - 1] === '.') { - this.index--; - ident = ident.slice(0, -1); - lastDot = ident.lastIndexOf('.'); - if (lastDot === -1) { - lastDot = undefined; - } - } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = this.index; - while (peekIndex < this.text.length) { - ch = this.text.charAt(peekIndex); - if (ch === '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - this.index = peekIndex; - break; - } - if (this.isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - this.tokens.push({ index: start, - text: ident, - fn: CONSTANTS[ident] || getterFn(ident, this.options, expression) + text: this.text.slice(start, this.index), + identifier: true }); - - if (methodName) { - this.tokens.push({ - index: lastDot, - text: '.' - }); - this.tokens.push({ - index: lastDot + 1, - text: methodName - }); - } }, readString: function(quote) { @@ -11730,9 +12020,8 @@ Lexer.prototype = { this.tokens.push({ index: start, text: rawString, - string: string, constant: true, - fn: function() { return string; } + value: string }); return; } else { @@ -11752,13 +12041,13 @@ function isConstant(exp) { /** * @constructor */ -var Parser = function (lexer, $filter, options) { +var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; }; -Parser.ZERO = extend(function () { +Parser.ZERO = extend(function() { return 0; }, { sharedGetter: true, @@ -11768,7 +12057,7 @@ Parser.ZERO = extend(function () { Parser.prototype = { constructor: Parser, - parse: function (text) { + parse: function(text) { this.text = text; this.tokens = this.lexer.lex(text); @@ -11784,7 +12073,7 @@ Parser.prototype = { return value; }, - primary: function () { + primary: function() { var primary; if (this.expect('(')) { primary = this.filterChain(); @@ -11793,16 +12082,14 @@ Parser.prototype = { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); + } else if (this.peek().identifier && this.peek().text in CONSTANTS) { + primary = CONSTANTS[this.consume().text]; + } else if (this.peek().identifier) { + primary = this.identifier(); + } else if (this.peek().constant) { + primary = this.constant(); } else { - var token = this.expect(); - primary = token.fn; - if (!primary) { - this.throwError('not a primary expression', token); - } - if (token.constant) { - primary.constant = true; - primary.literal = true; - } + this.throwError('not a primary expression', this.peek()); } var next, context; @@ -11836,8 +12123,11 @@ Parser.prototype = { }, peek: function(e1, e2, e3, e4) { - if (this.tokens.length > 0) { - var token = this.tokens[0]; + return this.peekAhead(0, e1, e2, e3, e4); + }, + peekAhead: function(i, e1, e2, e3, e4) { + if (this.tokens.length > i) { + var token = this.tokens[i]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { @@ -11847,7 +12137,7 @@ Parser.prototype = { return false; }, - expect: function(e1, e2, e3, e4){ + expect: function(e1, e2, e3, e4) { var token = this.peek(e1, e2, e3, e4); if (token) { this.tokens.shift(); @@ -11856,13 +12146,20 @@ Parser.prototype = { return false; }, - consume: function(e1){ - if (!this.expect(e1)) { + consume: function(e1) { + if (this.tokens.length === 0) { + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + } + + var token = this.expect(e1); + if (!token) { this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } + return token; }, - unaryFn: function(fn, right) { + unaryFn: function(op, right) { + var fn = OPERATORS[op]; return extend(function $parseUnaryFn(self, locals) { return fn(self, locals, right); }, { @@ -11871,7 +12168,8 @@ Parser.prototype = { }); }, - binaryFn: function(left, fn, right, isBranching) { + binaryFn: function(left, op, right, isBranching) { + var fn = OPERATORS[op]; return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right); }, { @@ -11880,6 +12178,28 @@ Parser.prototype = { }); }, + identifier: function() { + var id = this.consume().text; + + //Continue reading each `.identifier` unless it is a method invocation + while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { + id += this.consume().text + this.consume().text; + } + + return getterFn(id, this.options, this.text); + }, + + constant: function() { + var value = this.consume().value; + + return extend(function $parseConstant() { + return value; + }, { + constant: true, + literal: true + }); + }, + statements: function() { var statements = []; while (true) { @@ -11911,8 +12231,7 @@ Parser.prototype = { }, filter: function(inputFn) { - var token = this.expect(); - var fn = this.$filter(token.text); + var fn = this.$filter(this.consume().text); var argsFn; var args; @@ -11975,17 +12294,14 @@ Parser.prototype = { var token; if ((token = this.expect('?'))) { middle = this.assignment(); - if ((token = this.expect(':'))) { + if (this.consume(':')) { var right = this.assignment(); - return extend(function $parseTernary(self, locals){ + return extend(function $parseTernary(self, locals) { return left(self, locals) ? middle(self, locals) : right(self, locals); }, { constant: left.constant && middle.constant && right.constant }); - - } else { - this.throwError('expected :', token); } } @@ -11996,7 +12312,7 @@ Parser.prototype = { var left = this.logicalAND(); var token; while ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND(), true); + left = this.binaryFn(left, token.text, this.logicalAND(), true); } return left; }, @@ -12004,8 +12320,8 @@ Parser.prototype = { logicalAND: function() { var left = this.equality(); var token; - if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND(), true); + while ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.text, this.equality(), true); } return left; }, @@ -12013,8 +12329,8 @@ Parser.prototype = { equality: function() { var left = this.relational(); var token; - if ((token = this.expect('==','!=','===','!=='))) { - left = this.binaryFn(left, token.fn, this.equality()); + while ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.text, this.relational()); } return left; }, @@ -12022,8 +12338,8 @@ Parser.prototype = { relational: function() { var left = this.additive(); var token; - if ((token = this.expect('<', '>', '<=', '>='))) { - left = this.binaryFn(left, token.fn, this.relational()); + while ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.text, this.additive()); } return left; }, @@ -12032,7 +12348,7 @@ Parser.prototype = { var left = this.multiplicative(); var token; while ((token = this.expect('+','-'))) { - left = this.binaryFn(left, token.fn, this.multiplicative()); + left = this.binaryFn(left, token.text, this.multiplicative()); } return left; }, @@ -12041,7 +12357,7 @@ Parser.prototype = { var left = this.unary(); var token; while ((token = this.expect('*','/','%'))) { - left = this.binaryFn(left, token.fn, this.unary()); + left = this.binaryFn(left, token.text, this.unary()); } return left; }, @@ -12051,26 +12367,25 @@ Parser.prototype = { if (this.expect('+')) { return this.primary(); } else if ((token = this.expect('-'))) { - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + return this.binaryFn(Parser.ZERO, token.text, this.unary()); } else if ((token = this.expect('!'))) { - return this.unaryFn(token.fn, this.unary()); + return this.unaryFn(token.text, this.unary()); } else { return this.primary(); } }, fieldAccess: function(object) { - var expression = this.text; - var field = this.expect().text; - var getter = getterFn(field, this.options, expression); + var getter = this.identifier(); return extend(function $parseFieldAccess(scope, locals, self) { - return getter(self || object(scope, locals)); + var o = self || object(scope, locals); + return (o == null) ? undefined : getter(o); }, { assign: function(scope, value, locals) { var o = object(scope, locals); - if (!o) object.assign(scope, o = {}); - return setter(o, field, value, expression); + if (!o) object.assign(scope, o = {}, locals); + return getter.assign(o, value); } }); }, @@ -12095,7 +12410,7 @@ Parser.prototype = { var key = ensureSafeMemberName(indexFn(self, locals), expression); // prevent overwriting of Function.constructor which would break ensureSafeObject check var o = ensureSafeObject(obj(self, locals), expression); - if (!o) obj.assign(self, o = {}); + if (!o) obj.assign(self, o = {}, locals); return o[key] = value; } }); @@ -12115,7 +12430,7 @@ Parser.prototype = { var args = argsFn.length ? [] : null; return function $parseFunctionCall(scope, locals) { - var context = contextGetter ? contextGetter(scope, locals) : scope; + var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; var fn = fnGetter(scope, locals, context) || noop; if (args) { @@ -12128,17 +12443,22 @@ Parser.prototype = { ensureSafeObject(context, expressionText); ensureSafeFunction(fn, expressionText); - // IE stupidity! (IE doesn't have apply for some native functions) + // IE doesn't have apply for some native functions var v = fn.apply ? fn.apply(context, args) : fn(args[0], args[1], args[2], args[3], args[4]); + if (args) { + // Free-up the memory (arguments of the last function call). + args.length = 0; + } + return ensureSafeObject(v, expressionText); - }; + }; }, // This is used with json array declaration - arrayDeclaration: function () { + arrayDeclaration: function() { var elementFns = []; if (this.peekToken().text !== ']') { do { @@ -12146,8 +12466,7 @@ Parser.prototype = { // Support trailing commas per ES5.1. break; } - var elementFn = this.expression(); - elementFns.push(elementFn); + elementFns.push(this.expression()); } while (this.expect(',')); } this.consume(']'); @@ -12165,7 +12484,7 @@ Parser.prototype = { }); }, - object: function () { + object: function() { var keys = [], valueFns = []; if (this.peekToken().text !== '}') { do { @@ -12173,11 +12492,16 @@ Parser.prototype = { // Support trailing commas per ES5.1. break; } - var token = this.expect(); - keys.push(token.string || token.text); + var token = this.consume(); + if (token.constant) { + keys.push(token.value); + } else if (token.identifier) { + keys.push(token.text); + } else { + this.throwError("invalid key", token); + } this.consume(':'); - var value = this.expression(); - valueFns.push(value); + valueFns.push(this.expression()); } while (this.expect(',')); } this.consume('}'); @@ -12201,18 +12525,19 @@ Parser.prototype = { // Parser helper functions ////////////////////////////////////////////////// -function setter(obj, path, setValue, fullExp) { +function setter(obj, locals, path, setValue, fullExp) { ensureSafeObject(obj, fullExp); + ensureSafeObject(locals, fullExp); var element = path.split('.'), key; for (var i = 0; element.length > 1; i++) { key = ensureSafeMemberName(element.shift(), fullExp); - var propertyObj = ensureSafeObject(obj[key], fullExp); + var propertyObj = (i === 0 && locals && locals[key]) || obj[key]; if (!propertyObj) { propertyObj = {}; obj[key] = propertyObj; } - obj = propertyObj; + obj = ensureSafeObject(propertyObj, fullExp); } key = ensureSafeMemberName(element.shift(), fullExp); ensureSafeObject(obj[key], fullExp); @@ -12220,64 +12545,85 @@ function setter(obj, path, setValue, fullExp) { return setValue; } -var getterFnCache = createMap(); +var getterFnCacheDefault = createMap(); +var getterFnCacheExpensive = createMap(); + +function isPossiblyDangerousMemberName(name) { + return name == 'constructor'; +} /** * Implementation of the "Black Hole" variant from: * - http://jsperf.com/angularjs-parse-getter/4 * - http://jsperf.com/path-evaluation-simplified/7 */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) { ensureSafeMemberName(key0, fullExp); ensureSafeMemberName(key1, fullExp); ensureSafeMemberName(key2, fullExp); ensureSafeMemberName(key3, fullExp); ensureSafeMemberName(key4, fullExp); + var eso = function(o) { + return ensureSafeObject(o, fullExp); + }; + var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity; + var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity; + var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity; + var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity; + var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity; return function cspSafeGetter(scope, locals) { var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; if (pathVal == null) return pathVal; - pathVal = pathVal[key0]; + pathVal = eso0(pathVal[key0]); if (!key1) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key1]; + pathVal = eso1(pathVal[key1]); if (!key2) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key2]; + pathVal = eso2(pathVal[key2]); if (!key3) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key3]; + pathVal = eso3(pathVal[key3]); if (!key4) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key4]; + pathVal = eso4(pathVal[key4]); return pathVal; }; } +function getterFnWithEnsureSafeObject(fn, fullExpression) { + return function(s, l) { + return fn(s, l, ensureSafeObject, fullExpression); + }; +} + function getterFn(path, options, fullExp) { + var expensiveChecks = options.expensiveChecks; + var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault); var fn = getterFnCache[path]; - if (fn) return fn; + var pathKeys = path.split('.'), pathKeysLength = pathKeys.length; // http://jsperf.com/angularjs-parse-getter/6 if (options.csp) { if (pathKeysLength < 6) { - fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks); } else { fn = function cspSafeGetter(scope, locals) { var i = 0, val; do { val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], - pathKeys[i++], fullExp)(scope, locals); + pathKeys[i++], fullExp, expensiveChecks)(scope, locals); locals = undefined; // clear after first iteration scope = val; @@ -12287,33 +12633,50 @@ function getterFn(path, options, fullExp) { } } else { var code = ''; + if (expensiveChecks) { + code += 's = eso(s, fe);\nl = eso(l, fe);\n'; + } + var needsEnsureSafeObject = expensiveChecks; forEach(pathKeys, function(key, index) { ensureSafeMemberName(key, fullExp); - code += 'if(s == null) return undefined;\n' + - 's='+ (index + var lookupJs = (index // we simply dereference 's' on any .dot notation ? 's' // but if we are first then we check locals first, and if so read it first - : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n'; + : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key; + if (expensiveChecks || isPossiblyDangerousMemberName(key)) { + lookupJs = 'eso(' + lookupJs + ', fe)'; + needsEnsureSafeObject = true; + } + code += 'if(s == null) return undefined;\n' + + 's=' + lookupJs + ';\n'; }); code += 'return s;'; /* jshint -W054 */ - var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals + var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject /* jshint +W054 */ evaledFnGetter.toString = valueFn(code); - + if (needsEnsureSafeObject) { + evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp); + } fn = evaledFnGetter; } fn.sharedGetter = true; - fn.assign = function(self, value) { - return setter(self, path, value, path); + fn.assign = function(self, value, locals) { + return setter(self, locals, path, value, path); }; getterFnCache[path] = fn; return fn; } +var objectValueOf = Object.prototype.valueOf; + +function getValueOf(value) { + return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); +} + /////////////////////////////////// /** @@ -12366,15 +12729,20 @@ function getterFn(path, options, fullExp) { * service. */ function $ParseProvider() { - var cache = createMap(); + var cacheDefault = createMap(); + var cacheExpensive = createMap(); - var $parseOptions = { - csp: false - }; this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { - $parseOptions.csp = $sniffer.csp; + var $parseOptions = { + csp: $sniffer.csp, + expensiveChecks: false + }, + $parseOptionsExpensive = { + csp: $sniffer.csp, + expensiveChecks: true + }; function wrapSharedExpression(exp) { var wrapped = exp; @@ -12391,13 +12759,14 @@ function $ParseProvider() { return wrapped; } - return function $parse(exp, interceptorFn) { + return function $parse(exp, interceptorFn, expensiveChecks) { var parsedExpression, oneTime, cacheKey; switch (typeof exp) { case 'string': cacheKey = exp = exp.trim(); + var cache = (expensiveChecks ? cacheExpensive : cacheDefault); parsedExpression = cache[cacheKey]; if (!parsedExpression) { @@ -12406,8 +12775,9 @@ function $ParseProvider() { exp = exp.substring(2); } - var lexer = new Lexer($parseOptions); - var parser = new Parser(lexer, $filter, $parseOptions); + var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; + var lexer = new Lexer(parseOptions); + var parser = new Parser(lexer, $filter, parseOptions); parsedExpression = parser.parse(exp); if (parsedExpression.constant) { @@ -12460,7 +12830,7 @@ function $ParseProvider() { // attempt to convert the value to a primitive type // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can // be cheaply dirty-checked - newValue = newValue.valueOf(); + newValue = getValueOf(newValue); if (typeof newValue === 'object') { // objects/arrays are not supported - deep-watching them would be too expensive @@ -12487,7 +12857,7 @@ function $ParseProvider() { var newInputValue = inputExpressions(scope); if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) { lastResult = parsedExpression(scope); - oldInputValue = newInputValue && newInputValue.valueOf(); + oldInputValue = newInputValue && getValueOf(newInputValue); } return lastResult; }, listener, objectEquality); @@ -12504,7 +12874,7 @@ function $ParseProvider() { for (var i = 0, ii = inputExpressions.length; i < ii; i++) { var newInputValue = inputExpressions[i](scope); if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { - oldInputValueOfValues[i] = newInputValue && newInputValue.valueOf(); + oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); } } @@ -12526,7 +12896,7 @@ function $ParseProvider() { listener.apply(this, arguments); } if (isDefined(value)) { - scope.$$postDigest(function () { + scope.$$postDigest(function() { if (isDefined(lastValue)) { unwatch(); } @@ -12545,15 +12915,15 @@ function $ParseProvider() { listener.call(this, value, old, scope); } if (isAllDefined(value)) { - scope.$$postDigest(function () { - if(isAllDefined(lastValue)) unwatch(); + scope.$$postDigest(function() { + if (isAllDefined(lastValue)) unwatch(); }); } }, objectEquality); function isAllDefined(value) { var allDefined = true; - forEach(value, function (val) { + forEach(value, function(val) { if (!isDefined(val)) allDefined = false; }); return allDefined; @@ -12574,8 +12944,16 @@ function $ParseProvider() { function addInterceptor(parsedExpression, interceptorFn) { if (!interceptorFn) return parsedExpression; + var watchDelegate = parsedExpression.$$watchDelegate; - var fn = function interceptedExpression(scope, locals) { + var regularWatch = + watchDelegate !== oneTimeLiteralWatchDelegate && + watchDelegate !== oneTimeWatchDelegate; + + var fn = regularWatch ? function regularInterceptedExpression(scope, locals) { + var value = parsedExpression(scope, locals); + return interceptorFn(value, scope, locals); + } : function oneTimeInterceptedExpression(scope, locals) { var value = parsedExpression(scope, locals); var result = interceptorFn(value, scope, locals); // we only return the interceptor's result if the @@ -12605,7 +12983,11 @@ function $ParseProvider() { * @requires $rootScope * * @description - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * A service that helps you run functions asynchronously, and use their return values (or exceptions) + * when they are done processing. + * + * This is an implementation of promises/deferred objects inspired by + * [Kris Kowal's Q](https://github.com/kriskowal/q). * * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred * implementations, and the other which resembles ES6 promises to some degree. @@ -12741,16 +13123,12 @@ function $ParseProvider() { * * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, * but to do so without modifying the final value. This is useful to release resources or do some * clean-up that needs to be done whether the promise was rejected or resolved. See the [full * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 and Android 2.x compatible. - * * # Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily @@ -12917,7 +13295,7 @@ function qFactory(nextTick, exceptionHandler) { } else { promise.reject(state.value); } - } catch(e) { + } catch (e) { promise.reject(e); exceptionHandler(e); } @@ -12946,8 +13324,7 @@ function qFactory(nextTick, exceptionHandler) { 'qcycle', "Expected promise to be resolved with value other than itself '{0}'", val)); - } - else { + } else { this.$$resolve(val); } @@ -12967,7 +13344,7 @@ function qFactory(nextTick, exceptionHandler) { this.promise.$$state.status = 1; scheduleProcessQueue(this.promise.$$state); } - } catch(e) { + } catch (e) { fns[1](e); exceptionHandler(e); } @@ -12995,7 +13372,7 @@ function qFactory(nextTick, exceptionHandler) { callback = callbacks[i][3]; try { result.notify(isFunction(callback) ? callback(progress) : progress); - } catch(e) { + } catch (e) { exceptionHandler(e); } } @@ -13060,7 +13437,7 @@ function qFactory(nextTick, exceptionHandler) { var callbackOutput = null; try { if (isFunction(callback)) callbackOutput = callback(); - } catch(e) { + } catch (e) { return makePromise(e, false); } if (isPromiseLike(callbackOutput)) { @@ -13168,19 +13545,17 @@ function qFactory(nextTick, exceptionHandler) { return $Q; } -function $$RAFProvider(){ //rAF +function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - $window.mozRequestAnimationFrame; + $window.webkitRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || - $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; var rafSupported = !!requestAnimationFrame; - var raf = rafSupported + var rafFn = rafSupported ? function(fn) { var id = requestAnimationFrame(fn); return function() { @@ -13194,9 +13569,47 @@ function $$RAFProvider(){ //rAF }; }; - raf.supported = rafSupported; + queueFn.supported = rafSupported; + + var cancelLastRAF; + var taskCount = 0; + var taskQueue = []; + return queueFn; + + function flush() { + for (var i = 0; i < taskQueue.length; i++) { + var task = taskQueue[i]; + if (task) { + taskQueue[i] = null; + task(); + } + } + taskCount = taskQueue.length = 0; + } + + function queueFn(asyncFn) { + var index = taskQueue.length; + + taskCount++; + taskQueue.push(asyncFn); + + if (index === 0) { + cancelLastRAF = rafFn(flush); + } + + return function cancelQueueFn() { + if (index >= 0) { + taskQueue[index] = null; + index = null; - return raf; + if (--taskCount === 0 && cancelLastRAF) { + cancelLastRAF(); + cancelLastRAF = null; + taskQueue.length = 0; + } + } + }; + } }]; } @@ -13267,7 +13680,7 @@ function $$RAFProvider(){ //rAF * They also provide an event emission/broadcast and subscription facility. See the * {@link guide/scope developer guide on scopes}. */ -function $RootScopeProvider(){ +function $RootScopeProvider() { var TTL = 10; var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; @@ -13280,8 +13693,25 @@ function $RootScopeProvider(){ return TTL; }; + function createChildScopeClass(parent) { + function ChildScope() { + this.$$watchers = this.$$nextSibling = + this.$$childHead = this.$$childTail = null; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$id = nextUid(); + this.$$ChildScope = null; + } + ChildScope.prototype = parent; + return ChildScope; + } + this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function( $injector, $exceptionHandler, $parse, $browser) { + function($injector, $exceptionHandler, $parse, $browser) { + + function destroyChildScope($event) { + $event.currentScope.$$destroyed = true; + } /** * @ngdoc type @@ -13305,7 +13735,6 @@ function $RootScopeProvider(){ var child = parent.$new(); parent.salutation = "Hello"; - child.name = "World"; expect(child.salutation).toEqual('Hello'); child.salutation = "Welcome"; @@ -13313,6 +13742,10 @@ function $RootScopeProvider(){ expect(parent.salutation).toEqual('Hello'); * ``` * + * When interacting with `Scope` in tests, additional helper methods are available on the + * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional + * details. + * * * @param {Object.=} providers Map of service factory which need to be * provided for the current scope. Defaults to {@link ng}. @@ -13402,15 +13835,7 @@ function $RootScopeProvider(){ // Only create a child scope class if somebody asks for one, // but cache it to allow the VM to optimize lookups. if (!this.$$ChildScope) { - this.$$ChildScope = function ChildScope() { - this.$$watchers = this.$$nextSibling = - this.$$childHead = this.$$childTail = null; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$id = nextUid(); - this.$$ChildScope = null; - }; - this.$$ChildScope.prototype = this; + this.$$ChildScope = createChildScopeClass(this); } child = new this.$$ChildScope(); } @@ -13428,13 +13853,9 @@ function $RootScopeProvider(){ // prototypically. In all other cases, this property needs to be set // when the parent scope is destroyed. // The listener needs to be added after the parent is set - if (isolate || parent != this) child.$on('$destroy', destroyChild); + if (isolate || parent != this) child.$on('$destroy', destroyChildScope); return child; - - function destroyChild() { - child.$$destroyed = true; - } }, /** @@ -13624,7 +14045,7 @@ function $RootScopeProvider(){ if (!watchExpressions.length) { // No expressions means we call the listener ASAP var shouldCall = true; - self.$evalAsync(function () { + self.$evalAsync(function() { if (shouldCall) listener(newValues, newValues, self); }); return function deregisterWatchGroup() { @@ -13641,7 +14062,7 @@ function $RootScopeProvider(){ }); } - forEach(watchExpressions, function (expr, i) { + forEach(watchExpressions, function(expr, i) { var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { newValues[i] = value; oldValues[i] = oldValue; @@ -13751,6 +14172,9 @@ function $RootScopeProvider(){ newValue = _value; var newLength, key, bothNaN, newItem, oldItem; + // If the new value is undefined, then return undefined as the watch may be a one-time watch + if (isUndefined(newValue)) return; + if (!isObject(newValue)) { // if primitive if (oldValue !== newValue) { oldValue = newValue; @@ -13813,7 +14237,7 @@ function $RootScopeProvider(){ if (oldLength > newLength) { // we used to have more keys, need to find them and destroy them. changeDetected++; - for(key in oldValue) { + for (key in oldValue) { if (!newValue.hasOwnProperty(key)) { oldLength--; delete oldValue[key]; @@ -13933,10 +14357,10 @@ function $RootScopeProvider(){ dirty = false; current = target; - while(asyncQueue.length) { + while (asyncQueue.length) { try { asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression); + asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); } catch (e) { $exceptionHandler(e); } @@ -13966,11 +14390,11 @@ function $RootScopeProvider(){ if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); + watchLog[logIdx].push({ + msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, + newVal: value, + oldVal: last + }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers @@ -13990,7 +14414,7 @@ function $RootScopeProvider(){ // this piece should be kept in sync with the traversal in $broadcast if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { + while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } @@ -13998,19 +14422,19 @@ function $RootScopeProvider(){ // `break traverseScopesLoop;` takes us to here - if((dirty || asyncQueue.length) && !(ttl--)) { + if ((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', - TTL, toJson(watchLog)); + TTL, watchLog); } } while (dirty || asyncQueue.length); clearPhase(); - while(postDigestQueue.length) { + while (postDigestQueue.length) { try { postDigestQueue.shift()(); } catch (e) { @@ -14151,8 +14575,9 @@ function $RootScopeProvider(){ * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. */ - $evalAsync: function(expr) { + $evalAsync: function(expr, locals) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush if (!$rootScope.$$phase && !asyncQueue.length) { @@ -14163,10 +14588,10 @@ function $RootScopeProvider(){ }); } - asyncQueue.push({scope: this, expression: expr}); + asyncQueue.push({scope: this, expression: expr, locals: locals}); }, - $$postDigest : function(fn) { + $$postDigest: function(fn) { postDigestQueue.push(fn); }, @@ -14238,7 +14663,7 @@ function $RootScopeProvider(){ * @kind function * * @description - * Schedule the invokation of $apply to occur at a later time. The actual time difference + * Schedule the invocation of $apply to occur at a later time. The actual time difference * varies across browsers, but is typically around ~10 milliseconds. * * This can be used to queue up multiple expressions which need to be evaluated in the same @@ -14303,8 +14728,11 @@ function $RootScopeProvider(){ var self = this; return function() { - namedListeners[namedListeners.indexOf(listener)] = null; - decrementListenerCount(self, 1, name); + var indexOfListener = namedListeners.indexOf(listener); + if (indexOfListener !== -1) { + namedListeners[indexOfListener] = null; + decrementListenerCount(self, 1, name); + } }; }, @@ -14351,7 +14779,7 @@ function $RootScopeProvider(){ do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; - for (i=0, length=namedListeners.length; i to the top of your HTML ' + @@ -15339,7 +15769,7 @@ function $SceProvider() { * @description * Returns a boolean indicating if SCE is enabled. */ - sce.isEnabled = function () { + sce.isEnabled = function() { return enabled; }; sce.trustAs = $sceDelegate.trustAs; @@ -15375,7 +15805,7 @@ function $SceProvider() { if (parsed.literal && parsed.constant) { return parsed; } else { - return $parse(expr, function (value) { + return $parse(expr, function(value) { return sce.getTrusted(type, value); }); } @@ -15628,15 +16058,15 @@ function $SceProvider() { getTrusted = sce.getTrusted, trustAs = sce.trustAs; - forEach(SCE_CONTEXTS, function (enumValue, name) { + forEach(SCE_CONTEXTS, function(enumValue, name) { var lName = lowercase(name); - sce[camelCase("parse_as_" + lName)] = function (expr) { + sce[camelCase("parse_as_" + lName)] = function(expr) { return parse(enumValue, expr); }; - sce[camelCase("get_trusted_" + lName)] = function (value) { + sce[camelCase("get_trusted_" + lName)] = function(value) { return getTrusted(enumValue, value); }; - sce[camelCase("trust_as_" + lName)] = function (value) { + sce[camelCase("trust_as_" + lName)] = function(value) { return trustAs(enumValue, value); }; }); @@ -15667,29 +16097,29 @@ function $SnifferProvider() { boxee = /Boxee/i.test(($window.navigator || {}).userAgent), document = $document[0] || {}, vendorPrefix, - vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, + vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, bodyStyle = document.body && document.body.style, transitions = false, animations = false, match; if (bodyStyle) { - for(var prop in bodyStyle) { - if(match = vendorRegex.exec(prop)) { + for (var prop in bodyStyle) { + if (match = vendorRegex.exec(prop)) { vendorPrefix = match[0]; vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); break; } } - if(!vendorPrefix) { + if (!vendorPrefix) { vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; } transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - if (android && (!transitions||!animations)) { + if (android && (!transitions || !animations)) { transitions = isString(document.body.style.webkitTransition); animations = isString(document.body.style.webkitAnimation); } @@ -15712,7 +16142,9 @@ function $SnifferProvider() { // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have // it. In particular the event is not fired when backspace or delete key are pressed or // when cut operation is performed. - if (event == 'input' && msie == 9) return false; + // IE10+ implements 'input' event but it erroneously fires under various situations, + // e.g. when placeholder changes, or a form is focused. + if (event === 'input' && msie <= 11) return false; if (isUndefined(eventSupport[event])) { var divElm = document.createElement('div'); @@ -15723,8 +16155,8 @@ function $SnifferProvider() { }, csp: csp(), vendorPrefix: vendorPrefix, - transitions : transitions, - animations : animations, + transitions: transitions, + animations: animations, android: android }; }]; @@ -15739,7 +16171,7 @@ var $compileMinErr = minErr('$compile'); * @description * The `$templateRequest` service 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 then a `$compile` error will be thrown (the exception can be thwarted + * 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). * * @param {string} tpl The HTTP request template URL @@ -15752,27 +16184,36 @@ var $compileMinErr = minErr('$compile'); function $TemplateRequestProvider() { this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { function handleRequestFn(tpl, ignoreRequestError) { - var self = handleRequestFn; - self.totalPendingRequests++; + handleRequestFn.totalPendingRequests++; - return $http.get(tpl, { cache : $templateCache }) - .then(function(response) { - var html = response.data; - if(!html || html.length === 0) { - return handleError(); - } + var transformResponse = $http.defaults && $http.defaults.transformResponse; + + if (isArray(transformResponse)) { + transformResponse = transformResponse.filter(function(transformer) { + return transformer !== defaultHttpResponseTransform; + }); + } else if (transformResponse === defaultHttpResponseTransform) { + transformResponse = null; + } + + var httpOptions = { + cache: $templateCache, + transformResponse: transformResponse + }; - self.totalPendingRequests--; - $templateCache.put(tpl, html); - return html; + return $http.get(tpl, httpOptions) + ['finally'](function() { + handleRequestFn.totalPendingRequests--; + }) + .then(function(response) { + return response.data; }, handleError); - function handleError() { - self.totalPendingRequests--; + function handleError(resp) { if (!ignoreRequestError) { throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl); } - return $q.reject(); + return $q.reject(resp); } } @@ -15815,7 +16256,7 @@ function $$TestabilityProvider() { if (dataBinding) { forEach(dataBinding, function(bindingName) { if (opt_exactMatch) { - var matcher = new RegExp('(^|\\s)' + expression + '(\\s|\\||$)'); + var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); if (matcher.test(bindingName)) { matches.push(binding); } @@ -15937,7 +16378,7 @@ function $TimeoutProvider() { timeoutId = $browser.defer(function() { try { deferred.resolve(fn()); - } catch(e) { + } catch (e) { deferred.reject(e); $exceptionHandler(e); } @@ -15988,7 +16429,7 @@ function $TimeoutProvider() { // exactly the behavior needed here. There is little value is mocking these out for this // service. var urlParsingNode = document.createElement("a"); -var originUrl = urlResolve(window.location.href, true); +var originUrl = urlResolve(window.location.href); /** @@ -16043,7 +16484,7 @@ var originUrl = urlResolve(window.location.href, true); * | pathname | The pathname, beginning with "/" * */ -function urlResolve(url, base) { +function urlResolve(url) { var href = url; if (msie) { @@ -16103,7 +16544,7 @@ function urlIsSameOrigin(requestUrl) { +
+ + + + + + + + + + + +
NamePhone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
+
+
+ + * + * The predicate and reverse parameters can be controlled dynamically through scope properties, + * as shown in the next example. * @example @@ -17291,42 +17796,38 @@ function limitToFilter(){ */ orderByFilter.$inject = ['$parse']; -function orderByFilter($parse){ +function orderByFilter($parse) { return function(array, sortPredicate, reverseOrder) { if (!(isArrayLike(array))) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; if (sortPredicate.length === 0) { sortPredicate = ['+']; } - sortPredicate = sortPredicate.map(function(predicate){ + sortPredicate = sortPredicate.map(function(predicate) { var descending = false, get = predicate || identity; if (isString(predicate)) { if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { descending = predicate.charAt(0) == '-'; predicate = predicate.substring(1); } - if ( predicate === '' ) { + if (predicate === '') { // Effectively no predicate was passed so we compare identity - return reverseComparator(function(a,b) { - return compare(a, b); - }, descending); + return reverseComparator(compare, descending); } get = $parse(predicate); if (get.constant) { var key = get(); - return reverseComparator(function(a,b) { + return reverseComparator(function(a, b) { return compare(a[key], b[key]); }, descending); } } - return reverseComparator(function(a,b){ + return reverseComparator(function(a, b) { return compare(get(a),get(b)); }, descending); }); - var arrayCopy = []; - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); + return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { + function comparator(o1, o2) { + for (var i = 0; i < sortPredicate.length; i++) { var comp = sortPredicate[i](o1, o2); if (comp !== 0) return comp; } @@ -17334,18 +17835,43 @@ function orderByFilter($parse){ } function reverseComparator(comp, descending) { return descending - ? function(a,b){return comp(b,a);} + ? function(a, b) {return comp(b,a);} : comp; } - function compare(v1, v2){ + + function isPrimitive(value) { + switch (typeof value) { + case 'number': /* falls through */ + case 'boolean': /* falls through */ + case 'string': + return true; + default: + return false; + } + } + + function objectToString(value) { + if (value === null) return 'null'; + if (typeof value.valueOf === 'function') { + value = value.valueOf(); + if (isPrimitive(value)) return value; + } + if (typeof value.toString === 'function') { + value = value.toString(); + if (isPrimitive(value)) return value; + } + return ''; + } + + function compare(v1, v2) { var t1 = typeof v1; var t2 = typeof v2; - if (t1 == t2) { - if (isDate(v1) && isDate(v2)) { - v1 = v1.valueOf(); - v2 = v2.valueOf(); - } - if (t1 == "string") { + if (t1 === t2 && t1 === "object") { + v1 = objectToString(v1); + v2 = objectToString(v2); + } + if (t1 === t2) { + if (t1 === "string") { v1 = v1.toLowerCase(); v2 = v2.toLowerCase(); } @@ -17386,10 +17912,13 @@ var htmlAnchorDirective = valueFn({ compile: function(element, attr) { if (!attr.href && !attr.xlinkHref && !attr.name) { return function(scope, element) { + // If the linked element is not an anchor tag anymore, do nothing + if (element[0].nodeName.toLowerCase() !== 'a') return; + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 'xlink:href' : 'href'; - element.on('click', function(event){ + element.on('click', function(event) { // if we have no href url, then don't navigate anywhere. if (!element.attr(href)) { event.preventDefault(); @@ -17411,9 +17940,8 @@ var htmlAnchorDirective = valueFn({ * make the link go to the wrong URL if the user clicks it before * Angular has a chance to replace the `{{hash}}` markup with its * value. Until Angular replaces the markup the link will be broken - * and will most likely return a 404 error. - * - * The `ngHref` directive solves this problem. + * and will most likely return a 404 error. The `ngHref` directive + * solves this problem. * * The wrong way to write it: * ```html @@ -17560,20 +18088,24 @@ var htmlAnchorDirective = valueFn({ * * @description * - * We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + * This directive sets the `disabled` attribute on the element if the + * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. + * + * A special directive is necessary because we cannot use interpolation inside the `disabled` + * attribute. The following example would make the button enabled on Chrome/Firefox + * but not on older IEs: + * * ```html - *
- * + * + *
+ * *
* ``` * - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as disabled. (Their presence means true and their absence means false.) + * This is because the HTML specification does not require browsers to preserve the values of + * boolean attributes such as `disabled` (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. - * The `ngDisabled` directive solves this problem for the `disabled` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. * * @example @@ -17592,7 +18124,7 @@ var htmlAnchorDirective = valueFn({ * * @element INPUT * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, - * then special attribute "disabled" will be set on the element + * then the `disabled` attribute will be set on the element */ @@ -17868,6 +18400,11 @@ function nullFormRenameControl(control, name) { * - `pattern` * - `required` * - `url` + * - `date` + * - `datetimelocal` + * - `time` + * - `week` + * - `month` * * @description * `FormController` keeps track of all its controls and nested forms as well as the state of them, @@ -17982,6 +18519,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { forEach(form.$error, function(value, name) { form.$setValidity(name, null, control); }); + forEach(form.$$success, function(value, name) { + form.$setValidity(name, null, control); + }); arrayRemove(controls, control); }; @@ -17999,23 +18539,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { addSetValidityMethod({ ctrl: this, $element: element, - set: function(object, property, control) { + set: function(object, property, controller) { var list = object[property]; if (!list) { - object[property] = [control]; + object[property] = [controller]; } else { - var index = list.indexOf(control); + var index = list.indexOf(controller); if (index === -1) { - list.push(control); + list.push(controller); } } }, - unset: function(object, property, control) { + unset: function(object, property, controller) { var list = object[property]; if (!list) { return; } - arrayRemove(list, control); + arrayRemove(list, controller); if (list.length === 0) { delete object[property]; } @@ -18056,7 +18596,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ - form.$setPristine = function () { + form.$setPristine = function() { $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); form.$dirty = false; form.$pristine = true; @@ -18079,7 +18619,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * Setting a form controls back to their untouched state is often useful when setting the form * back to its pristine state. */ - form.$setUntouched = function () { + form.$setUntouched = function() { forEach(controls, function(control) { control.$setUntouched(); }); @@ -18092,7 +18632,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * @description * Sets the form to its submitted state. */ - form.$setSubmitted = function () { + form.$setSubmitted = function() { $animate.addClass(element, SUBMITTED_CLASS); form.$submitted = true; parentForm.$setSubmitted(); @@ -18132,7 +18672,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * * # Alias: {@link ng.directive:ngForm `ngForm`} * - * In Angular forms can be nested. This means that the outer form is valid when all of the child + * In Angular, forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `
` elements, so * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to * `` but can be nested. This allows you to have nested forms, which is very useful when @@ -18231,11 +18771,11 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { userType: Required!
- userType = {{userType}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
+ userType = {{userType}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
@@ -18270,10 +18810,12 @@ var formDirectiveFactory = function(isNgForm) { name: 'form', restrict: isNgForm ? 'EAC' : 'E', controller: FormController, - compile: function ngFormCompile(formElement) { + compile: function ngFormCompile(formElement, attr) { // Setup initial state of the control formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); + var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); + return { pre: function ngFormPreLink(scope, formElement, attr, controller) { // if `action` attr is not present on the form, prevent the default action (submission) @@ -18290,9 +18832,7 @@ var formDirectiveFactory = function(isNgForm) { controller.$setSubmitted(); }); - event.preventDefault - ? event.preventDefault() - : event.returnValue = false; // IE + event.preventDefault(); }; addEventListenerFn(formElement[0], 'submit', handleFormSubmission); @@ -18306,23 +18846,21 @@ var formDirectiveFactory = function(isNgForm) { }); } - var parentFormCtrl = controller.$$parentForm, - alias = controller.$name; - - if (alias) { - setter(scope, alias, controller, alias); - attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) { - if (alias === newValue) return; - setter(scope, alias, undefined, alias); - alias = newValue; - setter(scope, alias, controller, alias); - parentFormCtrl.$$renameControl(controller, alias); + var parentFormCtrl = controller.$$parentForm; + + if (nameAttr) { + setter(scope, null, controller.$name, controller, controller.$name); + attr.$observe(nameAttr, function(newValue) { + if (controller.$name === newValue) return; + setter(scope, null, controller.$name, undefined, controller.$name); + parentFormCtrl.$$renameControl(controller, newValue); + setter(scope, null, controller.$name, controller, controller.$name); }); } formElement.on('$destroy', function() { parentFormCtrl.$removeControl(controller); - if (alias) { - setter(scope, alias, undefined, alias); + if (nameAttr) { + setter(scope, null, attr[nameAttr], undefined, controller.$name); } extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards }); @@ -18338,12 +18876,13 @@ var formDirectiveFactory = function(isNgForm) { var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); -/* global VALID_CLASS: true, - INVALID_CLASS: true, - PRISTINE_CLASS: true, - DIRTY_CLASS: true, - UNTOUCHED_CLASS: true, - TOUCHED_CLASS: true, +/* global VALID_CLASS: false, + INVALID_CLASS: false, + PRISTINE_CLASS: false, + DIRTY_CLASS: false, + UNTOUCHED_CLASS: false, + TOUCHED_CLASS: false, + $ngModelMinErr: false, */ // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 @@ -18356,9 +18895,6 @@ var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{ var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; -var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; - -var $ngModelMinErr = new minErr('ngModel'); var inputType = { @@ -18369,7 +18905,6 @@ var inputType = { * @description * Standard HTML text input with angular data binding, inherited by most of the `input` elements. * - * *NOTE* Not every feature offered is available for all input types. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. @@ -18380,10 +18915,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. @@ -18396,19 +18937,21 @@ var inputType = {
- Single word: + Single word: Required! Single word only! - text = {{text}}
+ text = {{example.text}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18416,9 +18959,9 @@ var inputType = {
- var text = element(by.binding('text')); + var text = element(by.binding('example.text')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); + var input = element(by.model('example.text')); it('should initialize to model', function() { expect(text.getText()).toContain('guest'); @@ -18453,7 +18996,10 @@ var inputType = { * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many * modern browsers do not yet support this input type, it is important to provide cues to users on the - * expected input format via a placeholder or label. The model must always be a Date object. + * expected input format via a placeholder or label. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. @@ -18477,18 +19023,20 @@ var inputType = {
Pick a date in 2013: - Required! Not a valid date! - value = {{value | date: "yyyy-MM-dd"}}
+ value = {{example.value | date: "yyyy-MM-dd"}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18496,9 +19044,9 @@ var inputType = {
- var value = element(by.binding('value | date: "yyyy-MM-dd"')); + var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls @@ -18536,12 +19084,15 @@ var inputType = { /** * @ngdoc input - * @name input[dateTimeLocal] + * @name input[datetime-local] * * @description * Input with datetime validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. The model must be a Date object. + * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. @@ -18565,18 +19116,20 @@ var inputType = {
Pick a date between in 2013: - Required! Not a valid date! - value = {{value | date: "yyyy-MM-ddTHH:mm:ss"}}
+ value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18584,9 +19137,9 @@ var inputType = {
- var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm:ss"')); + var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls @@ -18632,6 +19185,9 @@ var inputType = { * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * @@ -18654,18 +19210,20 @@ var inputType = {
Pick a between 8am and 5pm: - Required! Not a valid date! - value = {{value | date: "HH:mm:ss"}}
+ value = {{example.value | date: "HH:mm:ss"}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18673,9 +19231,9 @@ var inputType = {
- var value = element(by.binding('value | date: "HH:mm:ss"')); + var value = element(by.binding('example.value | date: "HH:mm:ss"')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls @@ -18718,7 +19276,10 @@ var inputType = { * @description * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object. + * week format (yyyy-W##), for example: `2013-W02`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. @@ -18742,18 +19303,20 @@ var inputType = {
Pick a date between in 2013: - Required! Not a valid date! - value = {{value | date: "yyyy-Www"}}
+ value = {{example.value | date: "yyyy-Www"}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18761,9 +19324,9 @@ var inputType = {
- var value = element(by.binding('value | date: "yyyy-Www"')); + var value = element(by.binding('example.value | date: "yyyy-Www"')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls @@ -18804,8 +19367,12 @@ var inputType = { * @description * Input with month validation and transformation. In browsers that do not yet support * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is - * not set to the first of the month, the first of that model's month is assumed. + * month format (yyyy-MM), for example: `2009-01`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * If the model is not set to the first of the month, the next view to model update will set it + * to the first of the month. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. @@ -18829,18 +19396,20 @@ var inputType = {
- Pick a month int 2013: - Required! Not a valid month! - value = {{value | date: "yyyy-MM"}}
+ value = {{example.value | date: "yyyy-MM"}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18848,9 +19417,9 @@ var inputType = {
- var value = element(by.binding('value | date: "yyyy-MM"')); + var value = element(by.binding('example.value | date: "yyyy-MM"')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls @@ -18894,6 +19463,12 @@ var inputType = { * Text input with number validation and transformation. Sets the `number` validation * error if not a valid number. * + *
+ * The model must always be of type `number` otherwise Angular will throw an error. + * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} + * error docs for more information and an example of how to convert your model if necessary. + *
+ * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. @@ -18905,10 +19480,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -18918,17 +19499,19 @@ var inputType = {
- Number: Required! Not valid number! - value = {{value}}
+ value = {{example.value}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -18936,9 +19519,9 @@ var inputType = {
- var value = element(by.binding('value')); + var value = element(by.binding('example.value')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); it('should initialize to model', function() { expect(value.getText()).toContain('12'); @@ -18972,6 +19555,12 @@ var inputType = { * Text input with URL validation. Sets the `url` validation error key if the content is not a * valid URL. * + *
+ * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex + * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify + * the built-in validators (see the {@link guide/forms Forms guide}) + *
+ * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. @@ -18981,10 +19570,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -18994,16 +19589,18 @@ var inputType = {
- URL: + URL: Required! Not valid url! - text = {{text}}
+ text = {{url.text}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -19012,9 +19609,9 @@ var inputType = {
- var text = element(by.binding('text')); + var text = element(by.binding('url.text')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); + var input = element(by.model('url.text')); it('should initialize to model', function() { expect(text.getText()).toContain('http://google.com'); @@ -19049,6 +19646,12 @@ var inputType = { * Text input with email validation. Sets the `email` validation error key if not a valid email * address. * + *
+ * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex + * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can + * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) + *
+ * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. @@ -19058,10 +19661,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -19071,16 +19680,18 @@ var inputType = {
- Email: + Email: Required! Not valid email! - text = {{text}}
+ text = {{email.text}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
@@ -19089,9 +19700,9 @@ var inputType = {
- var text = element(by.binding('text')); + var text = element(by.binding('email.text')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); + var input = element(by.model('email.text')); it('should initialize to model', function() { expect(text.getText()).toContain('me@example.com'); @@ -19138,7 +19749,9 @@ var inputType = {
- Red
- Green
- Blue
- color = {{color | json}}
+ Red
+ Green
+ Blue
+ color = {{color.name | json}}
Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
it('should change state', function() { - var color = element(by.binding('color')); + var color = element(by.binding('color.name')); expect(color.getText()).toContain('blue'); - element.all(by.model('color')).get(0).click(); + element.all(by.model('color.name')).get(0).click(); expect(color.getText()).toContain('red'); }); @@ -19189,28 +19802,30 @@ var inputType = {
- Value1:
- Value2:
+ Value2:
- value1 = {{value1}}
- value2 = {{value2}}
+ value1 = {{checkboxModel.value1}}
+ value2 = {{checkboxModel.value2}}
it('should change state', function() { - var value1 = element(by.binding('value1')); - var value2 = element(by.binding('value2')); + var value1 = element(by.binding('checkboxModel.value1')); + var value2 = element(by.binding('checkboxModel.value2')); expect(value1.getText()).toContain('true'); expect(value2.getText()).toContain('YES'); - element(by.model('value1')).click(); - element(by.model('value2')).click(); + element(by.model('checkboxModel.value1')).click(); + element(by.model('checkboxModel.value2')).click(); expect(value1.getText()).toContain('false'); expect(value2.getText()).toContain('NO'); @@ -19227,19 +19842,6 @@ var inputType = { 'file': noop }; -function testFlags(validity, flags) { - var i, flag; - if (flags) { - for (i=0; i= minVal; + return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; }; attr.$observe('min', function(val) { minVal = parseObservedDateValue(val); @@ -19478,18 +20075,18 @@ function createDateInputType(type, regexp, parseDate, format) { if (isDefined(attr.max) || attr.ngMax) { var maxVal; ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; + return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; }; attr.$observe('max', function(val) { maxVal = parseObservedDateValue(val); ctrl.$validate(); }); } - // Override the standard $isEmpty to detect invalid dates as well - ctrl.$isEmpty = function(value) { + + function isValidDate(value) { // Invalid Date: getTime() returns NaN - return !value || (value.getTime && value.getTime() !== value.getTime()); - }; + return value && !(value.getTime && value.getTime() !== value.getTime()); + } function parseObservedDateValue(val) { return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; @@ -19533,7 +20130,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { return value; }); - if (attr.min || attr.ngMin) { + if (isDefined(attr.min) || attr.ngMin) { var minVal; ctrl.$validators.min = function(value) { return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; @@ -19549,7 +20146,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { }); } - if (attr.max || attr.ngMax) { + if (isDefined(attr.max) || attr.ngMax) { var maxVal; ctrl.$validators.max = function(value) { return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; @@ -19573,7 +20170,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { stringBasedInputType(ctrl); ctrl.$$parserName = 'url'; - ctrl.$validators.url = function(value) { + ctrl.$validators.url = function(modelValue, viewValue) { + var value = modelValue || viewValue; return ctrl.$isEmpty(value) || URL_REGEXP.test(value); }; } @@ -19585,7 +20183,8 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { stringBasedInputType(ctrl); ctrl.$$parserName = 'email'; - ctrl.$validators.email = function(value) { + ctrl.$validators.email = function(modelValue, viewValue) { + var value = modelValue || viewValue; return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); }; } @@ -19639,9 +20238,11 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt element[0].checked = ctrl.$viewValue; }; - // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue + // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` + // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert + // it to a boolean. ctrl.$isEmpty = function(value) { - return value !== trueValue; + return value === false; }; ctrl.$formatters.push(function(value) { @@ -19673,7 +20274,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. @@ -19689,10 +20291,14 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @restrict E * * @description - * HTML input element control with angular data-binding. Input control follows HTML5 input types - * and polyfills the HTML5 validation behavior for older browsers. + * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, + * input state control, and validation. + * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. * - * *NOTE* Not every feature offered is available for all input types. + *
+ * **Note:** Not every feature offered is available for all input types. + * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. + *
* * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. @@ -19701,7 +20307,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. @@ -19815,3613 +20422,3628 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', }; }]; -var VALID_CLASS = 'ng-valid', - INVALID_CLASS = 'ng-invalid', - PRISTINE_CLASS = 'ng-pristine', - DIRTY_CLASS = 'ng-dirty', - UNTOUCHED_CLASS = 'ng-untouched', - TOUCHED_CLASS = 'ng-touched', - PENDING_CLASS = 'ng-pending'; -/** - * @ngdoc type - * @name ngModel.NgModelController - * - * @property {string} $viewValue Actual string value in the view. - * @property {*} $modelValue The value in the model, that the control is bound to. - * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. Each function is called, in turn, passing the value - through to the next. The last return value is used to populate the model. - Used to sanitize / convert the value as well as validation. For validation, - the parsers should update the validity state using - {@link ngModel.NgModelController#$setValidity $setValidity()}, - and return `undefined` for invalid values. +var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; +/** + * @ngdoc directive + * @name ngValue * - * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever - the model value changes. Each function is called, in turn, passing the value through to the - next. Used to format / convert values for display in the control and validation. - * ```js - * function formatter(value) { - * if (value) { - * return value.toUpperCase(); - * } - * } - * ngModel.$formatters.push(formatter); - * ``` - * - * @property {Object.} $validators A collection of validators that are applied - * whenever the model value changes. The key value within the object refers to the name of the - * validator while the function refers to the validation operation. The validation operation is - * provided with the model value as an argument and must return a true or false value depending - * on the response of that validation. - * - * ```js - * ngModel.$validators.validCharacters = function(modelValue, viewValue) { - * var value = modelValue || viewValue; - * return /[0-9]+/.test(value) && - * /[a-z]+/.test(value) && - * /[A-Z]+/.test(value) && - * /\W+/.test(value); - * }; - * ``` - * - * @property {Object.} $asyncValidators A collection of validations that are expected to - * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided - * is expected to return a promise when it is run during the model validation process. Once the promise - * is delivered then the validation status will be set to true when fulfilled and false when rejected. - * When the asynchronous validators are triggered, each of the validators will run in parallel and the model - * value will only be updated once all validators have been fulfilled. Also, keep in mind that all - * asynchronous validators will only run once all synchronous validators have passed. - * - * Please note that if $http is used then it is important that the server returns a success HTTP response code - * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. - * - * ```js - * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { - * var value = modelValue || viewValue; - * - * // Lookup user by username - * return $http.get('/api/users/' + value). - * then(function resolved() { - * //username exists, this means validation fails - * return $q.reject('exists'); - * }, function rejected() { - * //username does not exist, therefore this validation passes - * return true; - * }); - * }; - * ``` + * @description + * Binds the given expression to the value of `
- @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); + @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); .animate-show { - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; + line-height: 20px; + opacity: 1; + padding: 10px; + border: 1px solid black; + background: white; } .animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-remove.ng-hide-remove-active { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; + -webkit-transition: all linear 0.5s; + transition: all linear 0.5s; } .animate-show.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; + line-height: 0; + opacity: 0; + padding: 0 10px; } .check-element { - padding:10px; - border:1px solid black; - background:white; + padding: 10px; + border: 1px solid black; + background: white; } @@ -24274,13 +24981,13 @@ var ngShowDirective = ['$animate', function($animate) { restrict: 'A', multiElement: true, link: function(scope, element, attr) { - scope.$watch(attr.ngShow, function ngShowWatchAction(value){ + scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { - tempClasses : NG_HIDE_IN_PROGRESS_CLASS + tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } @@ -24324,17 +25031,17 @@ var ngShowDirective = ['$animate', function($animate) { * * ### Overriding `.ng-hide` * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` * class in CSS: * * ```css * .ng-hide { * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; + * display: block!important; + * position: absolute; + * top: -9999px; + * left: -9999px; * } * ``` * @@ -24351,7 +25058,7 @@ var ngShowDirective = ['$animate', function($animate) { * //a working example can be found at the bottom of this page * // * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition:0.5s linear all; + * transition: 0.5s linear all; * } * * .my-element.ng-hide-add { ... } @@ -24389,29 +25096,29 @@ var ngShowDirective = ['$animate', function($animate) { - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); + @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); .animate-hide { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; + -webkit-transition: all linear 0.5s; + transition: all linear 0.5s; + line-height: 20px; + opacity: 1; + padding: 10px; + border: 1px solid black; + background: white; } .animate-hide.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; + line-height: 0; + opacity: 0; + padding: 0 10px; } .check-element { - padding:10px; - border:1px solid black; - background:white; + padding: 10px; + border: 1px solid black; + background: white; } @@ -24435,11 +25142,11 @@ var ngHideDirective = ['$animate', function($animate) { restrict: 'A', multiElement: true, link: function(scope, element, attr) { - scope.$watch(attr.ngHide, function ngHideWatchAction(value){ + scope.$watch(attr.ngHide, function ngHideWatchAction(value) { // The comment inside of the ngShowDirective explains why we add and // remove a temporary class for the show/hide animation $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { - tempClasses : NG_HIDE_IN_PROGRESS_CLASS + tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } @@ -24544,7 +25251,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { * * @scope * @priority 1200 - * @param {*} ngSwitch|on expression to match against ng-switch-when. + * @param {*} ngSwitch|on expression to match against ng-switch-when. * On child elements add: * * * `ngSwitchWhen`: the case statement to match against. If match then this @@ -24561,7 +25268,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
- selection={{selection}} + selection={{selection}}
@@ -24740,7 +25447,7 @@ var ngSwitchDefaultDirective = ngDirective({ }]);
-
+

{{text}}
@@ -24818,7 +25525,6 @@ var scriptDirective = ['$templateCache', function($templateCache) { compile: function(element, attr) { if (attr.type == 'text/ng-template') { var templateUrl = attr.id, - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent text = element[0].text; $templateCache.put(templateUrl, text); @@ -24840,47 +25546,74 @@ var ngOptionsMinErr = minErr('ngOptions'); * * The `ngOptions` attribute can be used to dynamically generate a list of `