vendor/assets/javascripts/unstable/angular-scenario.js in angularjs-rails-1.2.19 vs vendor/assets/javascripts/unstable/angular-scenario.js in angularjs-rails-1.2.20

- old
+ new

@@ -9788,11 +9788,11 @@ } })( window ); /** - * @license AngularJS v1.3.0-beta.14 + * @license AngularJS v1.3.0-beta.15 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ (function(window, document){ var _jQuery = window.jQuery.noConflict(true); @@ -9858,11 +9858,11 @@ return arg; } return match; }); - message = message + '\nhttp://errors.angularjs.org/1.3.0-beta.14/' + + message = message + '\nhttp://errors.angularjs.org/1.3.0-beta.15/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + encodeURIComponent(stringify(arguments[i])); } @@ -9870,93 +9870,91 @@ return new Error(message); }; } /* We need to tell jshint what variables are being exported */ -/* global - -angular, - -msie, - -jqLite, - -jQuery, - -slice, - -push, - -toString, - -ngMinErr, - -angularModule, - -nodeName_, - -uid, - -REGEX_STRING_REGEXP, - -VALIDITY_STATE_PROPERTY, +/* global angular: true, + msie: true, + jqLite: true, + jQuery: true, + slice: true, + push: true, + toString: true, + ngMinErr: true, + angularModule: true, + nodeName_: true, + uid: true, + REGEX_STRING_REGEXP: true, + VALIDITY_STATE_PROPERTY: true, - -lowercase, - -uppercase, - -manualLowercase, - -manualUppercase, - -nodeName_, - -isArrayLike, - -forEach, - -sortedKeys, - -forEachSorted, - -reverseParams, - -nextUid, - -setHashKey, - -extend, - -int, - -inherit, - -noop, - -identity, - -valueFn, - -isUndefined, - -isDefined, - -isObject, - -isString, - -isNumber, - -isDate, - -isArray, - -isFunction, - -isRegExp, - -isWindow, - -isScope, - -isFile, - -isBlob, - -isBoolean, - -trim, - -isElement, - -makeMap, - -map, - -size, - -includes, - -indexOf, - -arrayRemove, - -isLeafNode, - -copy, - -shallowCopy, - -equals, - -csp, - -concat, - -sliceArgs, - -bind, - -toJsonReplacer, - -toJson, - -fromJson, - -startingTag, - -tryDecodeURIComponent, - -parseKeyValue, - -toKeyValue, - -encodeUriSegment, - -encodeUriQuery, - -angularInit, - -bootstrap, - -snake_case, - -bindJQuery, - -assertArg, - -assertArgFn, - -assertNotHasOwnProperty, - -getter, - -getBlockElements, - -hasOwnProperty, - + lowercase: true, + uppercase: true, + manualLowercase: true, + manualUppercase: true, + nodeName_: true, + isArrayLike: true, + forEach: true, + sortedKeys: true, + forEachSorted: true, + reverseParams: true, + nextUid: true, + setHashKey: true, + extend: true, + int: true, + inherit: true, + noop: true, + identity: true, + valueFn: true, + isUndefined: true, + isDefined: true, + isObject: true, + isString: true, + isNumber: true, + isDate: true, + isArray: true, + isFunction: true, + isRegExp: true, + isWindow: true, + isScope: true, + isFile: true, + isBlob: true, + isBoolean: true, + trim: true, + isElement: true, + makeMap: true, + map: true, + size: true, + includes: true, + indexOf: true, + arrayRemove: true, + isLeafNode: true, + copy: true, + shallowCopy: true, + equals: true, + csp: true, + concat: true, + sliceArgs: true, + bind: true, + toJsonReplacer: true, + toJson: true, + fromJson: true, + startingTag: true, + tryDecodeURIComponent: true, + parseKeyValue: true, + toKeyValue: true, + encodeUriSegment: true, + encodeUriQuery: true, + angularInit: true, + bootstrap: true, + snake_case: true, + bindJQuery: true, + assertArg: true, + assertArgFn: true, + assertNotHasOwnProperty: true, + getter: true, + getBlockElements: true, + hasOwnProperty: true, */ //////////////////////////////////// /** @@ -10590,13 +10588,13 @@ * @param {(Object|Array)=} destination Destination into which the source is copied. If * provided, must be of the same type as `source`. * @returns {*} The copy or updated `destination`, if `destination` was specified. * * @example - <example> + <example module="copyExample"> <file name="index.html"> - <div ng-controller="Controller"> + <div ng-controller="ExampleController"> <form novalidate class="simple-form"> Name: <input type="text" ng-model="user.name" /><br /> E-mail: <input type="email" ng-model="user.email" /><br /> Gender: <input type="radio" ng-model="user.gender" value="male" />male <input type="radio" ng-model="user.gender" value="female" />female<br /> @@ -10606,25 +10604,26 @@ <pre>form = {{user | json}}</pre> <pre>master = {{master | json}}</pre> </div> <script> - function Controller($scope) { - $scope.master= {}; + angular.module('copyExample') + .controller('ExampleController', ['$scope', function($scope) { + $scope.master= {}; - $scope.update = function(user) { - // Example with 1 argument - $scope.master= angular.copy(user); - }; + $scope.update = function(user) { + // Example with 1 argument + $scope.master= angular.copy(user); + }; - $scope.reset = function() { - // Example with 2 arguments - angular.copy($scope.master, $scope.user); - }; + $scope.reset = function() { + // Example with 2 arguments + angular.copy($scope.master, $scope.user); + }; - $scope.reset(); - } + $scope.reset(); + }]); </script> </file> </example> */ function copy(source, destination, stackSource, stackDest) { @@ -10960,11 +10959,11 @@ if ( keyValue ) { key_value = keyValue.split('='); key = tryDecodeURIComponent(key_value[0]); if ( isDefined(key) ) { var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; - if (!obj[key]) { + if (!hasOwnProperty.call(obj, key)) { obj[key] = val; } else if(isArray(obj[key])) { obj[key].push(val); } else { obj[key] = [obj[key],val]; @@ -11758,93 +11757,92 @@ }; }); } -/* global - angularModule: true, - version: true, +/* global angularModule: true, + version: true, - $LocaleProvider, - $CompileProvider, + $LocaleProvider, + $CompileProvider, - htmlAnchorDirective, - inputDirective, - inputDirective, - formDirective, - scriptDirective, - selectDirective, - styleDirective, - optionDirective, - ngBindDirective, - ngBindHtmlDirective, - ngBindTemplateDirective, - ngClassDirective, - ngClassEvenDirective, - ngClassOddDirective, - ngCspDirective, - ngCloakDirective, - ngControllerDirective, - ngFormDirective, - ngHideDirective, - ngIfDirective, - ngIncludeDirective, - ngIncludeFillContentDirective, - ngInitDirective, - ngNonBindableDirective, - ngPluralizeDirective, - ngRepeatDirective, - ngShowDirective, - ngStyleDirective, - ngSwitchDirective, - ngSwitchWhenDirective, - ngSwitchDefaultDirective, - ngOptionsDirective, - ngTranscludeDirective, - ngModelDirective, - ngListDirective, - ngChangeDirective, - patternDirective, - patternDirective, - requiredDirective, - requiredDirective, - minlengthDirective, - minlengthDirective, - maxlengthDirective, - maxlengthDirective, - ngValueDirective, - ngModelOptionsDirective, - ngAttributeAliasDirectives, - ngEventDirectives, + htmlAnchorDirective, + inputDirective, + inputDirective, + formDirective, + scriptDirective, + selectDirective, + styleDirective, + optionDirective, + ngBindDirective, + ngBindHtmlDirective, + ngBindTemplateDirective, + ngClassDirective, + ngClassEvenDirective, + ngClassOddDirective, + ngCspDirective, + ngCloakDirective, + ngControllerDirective, + ngFormDirective, + ngHideDirective, + ngIfDirective, + ngIncludeDirective, + ngIncludeFillContentDirective, + ngInitDirective, + ngNonBindableDirective, + ngPluralizeDirective, + ngRepeatDirective, + ngShowDirective, + ngStyleDirective, + ngSwitchDirective, + ngSwitchWhenDirective, + ngSwitchDefaultDirective, + ngOptionsDirective, + ngTranscludeDirective, + ngModelDirective, + ngListDirective, + ngChangeDirective, + patternDirective, + patternDirective, + requiredDirective, + requiredDirective, + minlengthDirective, + minlengthDirective, + maxlengthDirective, + maxlengthDirective, + ngValueDirective, + ngModelOptionsDirective, + ngAttributeAliasDirectives, + ngEventDirectives, - $AnchorScrollProvider, - $AnimateProvider, - $BrowserProvider, - $CacheFactoryProvider, - $ControllerProvider, - $DocumentProvider, - $ExceptionHandlerProvider, - $FilterProvider, - $InterpolateProvider, - $IntervalProvider, - $HttpProvider, - $HttpBackendProvider, - $LocationProvider, - $LogProvider, - $ParseProvider, - $RootScopeProvider, - $QProvider, - $$QProvider, - $$SanitizeUriProvider, - $SceProvider, - $SceDelegateProvider, - $SnifferProvider, - $TemplateCacheProvider, - $TimeoutProvider, - $$RAFProvider, - $$AsyncCallbackProvider, - $WindowProvider + $AnchorScrollProvider, + $AnimateProvider, + $BrowserProvider, + $CacheFactoryProvider, + $ControllerProvider, + $DocumentProvider, + $ExceptionHandlerProvider, + $FilterProvider, + $InterpolateProvider, + $IntervalProvider, + $HttpProvider, + $HttpBackendProvider, + $LocationProvider, + $LogProvider, + $ParseProvider, + $RootScopeProvider, + $QProvider, + $$QProvider, + $$SanitizeUriProvider, + $SceProvider, + $SceDelegateProvider, + $SnifferProvider, + $TemplateCacheProvider, + $TimeoutProvider, + $$RAFProvider, + $$AsyncCallbackProvider, + $WindowProvider */ /** * @ngdoc object @@ -11859,15 +11857,15 @@ * - `minor` – `{number}` – Minor version number, such as "9". * - `dot` – `{number}` – Dot version number, such as "18". * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.3.0-beta.14', // all of these placeholder strings will be replaced by grunt's + full: '1.3.0-beta.15', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, dot: 0, - codeName: 'harmonious-cacophonies' + codeName: 'unbelievable-advancement' }; function publishExternalAPI(angular){ extend(angular, { @@ -11995,17 +11993,15 @@ }); } ]); } -/* global - - -JQLitePrototype, - -addEventListenerFn, - -removeEventListenerFn, - -BOOLEAN_ATTR, - -ALIASED_ATTR +/* global JQLitePrototype: true, + addEventListenerFn: true, + removeEventListenerFn: true, + BOOLEAN_ATTR: true, + ALIASED_ATTR: true, */ ////////////////////////////////// //JQLite ////////////////////////////////// @@ -12240,16 +12236,20 @@ function jqLiteClone(element) { return element.cloneNode(true); } -function jqLiteDealoc(element){ - jqLiteRemoveData(element); - var childElement; - for ( var i = 0, children = element.children, l = (children && children.length) || 0; i < l; i++) { - childElement = children[i]; - jqLiteDealoc(childElement); +function jqLiteDealoc(element, onlyDescendants){ + if (!onlyDescendants) jqLiteRemoveData(element); + + if (element.childNodes && element.childNodes.length) { + // we use querySelectorAll because documentFragments don't have getElementsByTagName + var descendants = element.getElementsByTagName ? element.getElementsByTagName('*') : + element.querySelectorAll ? element.querySelectorAll('*') : []; + for (var i = 0, l = descendants.length; i < l; i++) { + jqLiteRemoveData(descendants[i]); + } } } function jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); @@ -12427,13 +12427,11 @@ element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); } } function jqLiteEmpty(element) { - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - jqLiteDealoc(childNodes[i]); - } + jqLiteDealoc(element, true); while (element.firstChild) { element.removeChild(element.firstChild); } } @@ -12627,13 +12625,11 @@ html: function(element, value) { if (isUndefined(value)) { return element.innerHTML; } - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - jqLiteDealoc(childNodes[i]); - } + jqLiteDealoc(element, true); element.innerHTML = value; }, empty: jqLiteEmpty }, function(fn, name){ @@ -12749,12 +12745,10 @@ // selector. ////////////////////////////////////////// forEach({ removeData: jqLiteRemoveData, - dealoc: jqLiteDealoc, - on: function onFn(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. if (!jqLiteAcceptsData(element)) { @@ -13902,28 +13896,30 @@ * * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. * * @example - <example> + <example module="anchorScrollExample"> <file name="index.html"> - <div id="scrollArea" ng-controller="ScrollCtrl"> + <div id="scrollArea" ng-controller="ScrollController"> <a ng-click="gotoBottom()">Go to bottom</a> <a id="bottom"></a> You're at the bottom! </div> </file> <file name="script.js"> - function ScrollCtrl($scope, $location, $anchorScroll) { - $scope.gotoBottom = function (){ - // set the location.hash to the id of - // the element you wish to scroll to. - $location.hash('bottom'); + angular.module('anchorScrollExample', []) + .controller('ScrollController', ['$scope', '$location', '$anchorScroll', + function ($scope, $location, $anchorScroll) { + $scope.gotoBottom = function() { + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); - // call $anchorScroll() - $anchorScroll(); - }; - } + // call $anchorScroll() + $anchorScroll(); + }; + }]); </file> <file name="style.css"> #scrollArea { height: 350px; overflow: auto; @@ -14113,10 +14109,11 @@ enter : function(element, parent, after, done) { after ? after.after(element) : parent.prepend(element); async(done); + return noop; }, /** * * @ngdoc method @@ -14129,10 +14126,11 @@ * removed from the DOM */ leave : function(element, done) { element.remove(); async(done); + return noop; }, /** * * @ngdoc method @@ -14152,11 +14150,11 @@ * element has been moved to its new position */ move : function(element, parent, after, done) { // Do not remove element before insert. Removing will cause data associated with the // element to be dropped. Insert will implicitly do the remove. - this.enter(element, parent, after, done); + return this.enter(element, parent, after, done); }, /** * * @ngdoc method @@ -14169,17 +14167,18 @@ * @param {string} className the CSS class which will be added to the element * @param {Function=} done the callback function (if provided) that will be fired after the * className value has been added to the element */ addClass : function(element, className, done) { - className = isString(className) ? - className : - isArray(className) ? className.join(' ') : ''; + className = !isString(className) + ? (isArray(className) ? className.join(' ') : '') + : className; forEach(element, function (element) { jqLiteAddClass(element, className); }); async(done); + return noop; }, /** * * @ngdoc method @@ -14199,10 +14198,11 @@ isArray(className) ? className.join(' ') : ''; forEach(element, function (element) { jqLiteRemoveClass(element, className); }); async(done); + return noop; }, /** * * @ngdoc method @@ -14221,10 +14221,11 @@ forEach(element, function (element) { jqLiteAddClass(element, add); jqLiteRemoveClass(element, remove); }); async(done); + return noop; }, enabled : noop }; }]; @@ -15180,11 +15181,11 @@ * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. * If no `attr` name is specified then the attribute name is assumed to be the same as the * local name. Given `<widget my-attr="count = count + value">` and widget definition of * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to * a function wrapper for the `count = count + value` expression. Often it's desirable to - * pass data from the isolated scope via an expression and to the parent scope, this can be + * pass data from the isolated scope via an expression to the parent scope, this can be * done by passing a map of local variable names and values into the expression wrapper fn. * For example, if the expression is `increment(amount)` then we can specify the amount value * by calling the `localFn` as `localFn({amount: 22})`. * * @@ -15420,14 +15421,14 @@ * <div class="alert alert-warning"> * **Note**: Typically directives are registered with `module.directive`. The example below is * to illustrate how `$compile` works. * </div> * - <example module="compile"> + <example module="compileExample"> <file name="index.html"> <script> - angular.module('compile', [], function($compileProvider) { + angular.module('compileExample', [], function($compileProvider) { // configure new 'compile' directive by passing a directive // factory function. The factory function injects the '$compile' $compileProvider.directive('compile', function($compile) { // directive factory creates a link function return function(scope, element, attrs) { @@ -15447,19 +15448,18 @@ // we don't get into infinite loop compiling ourselves $compile(element.contents())(scope); } ); }; - }) - }); - - function Ctrl($scope) { + }); + }) + .controller('GreeterController', ['$scope', function($scope) { $scope.name = 'Angular'; $scope.html = 'Hello {{name}}'; - } + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="GreeterController"> <input ng-model="name"> <br> <textarea ng-model="html"></textarea> <br> <div compile="html"></div> </div> </file> @@ -15883,18 +15883,11 @@ forEach(transcludeControllers, function(instance, name) { $linkNode.data('$' + name + 'Controller', instance); }); - // Attach scope only to non-text nodes. - for(var i = 0, ii = $linkNode.length; i<ii; i++) { - var node = $linkNode[i], - nodeType = node.nodeType; - if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { - $linkNode.eq(i).data('$scope', scope); - } - } + $linkNode.data('$scope', scope); if (cloneConnectFn) cloneConnectFn($linkNode, scope); if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); return $linkNode; }; @@ -17146,10 +17139,11 @@ * This provider allows controller registration via the * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { var controllers = {}, + globals = false, CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; /** * @ngdoc method @@ -17166,11 +17160,20 @@ } else { controllers[name] = constructor; } }; + /** + * @ngdoc method + * @name $controllerProvider#allowGlobals + * @description If called, allows `$controller` to find controller constructors on `window` + */ + this.allowGlobals = function() { + globals = true; + }; + this.$get = ['$injector', '$window', function($injector, $window) { /** * @ngdoc service * @name $controller @@ -17180,11 +17183,12 @@ * controller constructor function. Otherwise it's considered to be a string which is used * to retrieve the controller constructor using the following steps: * * * check if a controller with given name is registered via `$controllerProvider` * * check if evaluating the string on the current scope returns a constructor - * * check `window[constructor]` on the global `window` object + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (not recommended) * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * * @description @@ -17200,11 +17204,12 @@ match = expression.match(CNTRL_REG), constructor = match[1], identifier = match[3]; expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] - : getter(locals.$scope, constructor, true) || getter($window, constructor, true); + : getter(locals.$scope, constructor, true) || + (globals ? getter($window, constructor, true) : undefined); assertArgFn(expression, constructor, true); } instance = $injector.instantiate(expression, locals, constructor); @@ -17231,22 +17236,23 @@ * * @description * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. * * @example - <example> + <example module="documentExample"> <file name="index.html"> - <div ng-controller="MainCtrl"> + <div ng-controller="ExampleController"> <p>$document title: <b ng-bind="title"></b></p> <p>window.document title: <b ng-bind="windowTitle"></b></p> </div> </file> <file name="script.js"> - function MainCtrl($scope, $document) { - $scope.title = $document[0].title; - $scope.windowTitle = angular.element(window.document)[0].title; - } + angular.module('documentExample', []) + .controller('ExampleController', ['$scope', '$document', function($scope, $document) { + $scope.title = $document[0].title; + $scope.windowTitle = angular.element(window.document)[0].title; + }]); </file> </example> */ function $DocumentProvider(){ this.$get = ['$window', function(window){ @@ -17375,16 +17381,43 @@ function isSuccess(status) { return 200 <= status && status < 300; } +/** + * @ngdoc provider + * @name $httpProvider + * @description + * 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/, CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; + /** + * @ngdoc property + * @name $httpProvider#defaults + * @description + * + * Object containing default values for all {@link ng.$http $http} requests. + * + * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. + * Defaults value is `'XSRF-TOKEN'`. + * + * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the + * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. + * + * - **`defaults.headers`** - {Object} - Default headers for all $http requests. + * Refer to {@link ng.$http#setting-http-headers $http} for documentation on + * setting default headers. + * - **`defaults.headers.common`** + * - **`defaults.headers.post`** + * - **`defaults.headers.put`** + * - **`defaults.headers.patch`** + **/ var defaults = this.defaults = { // transform incoming response data transformResponse: [function(data) { if (isString(data)) { // strip json vulnerability protection prefix @@ -17798,13 +17831,13 @@ * @property {Array.<Object>} pendingRequests Array of config objects for currently pending * requests. This is primarily meant to be used for debugging purposes. * * * @example -<example> +<example module="httpExample"> <file name="index.html"> - <div ng-controller="FetchCtrl"> + <div ng-controller="FetchController"> <select ng-model="method"> <option>GET</option> <option>JSONP</option> </select> <input type="text" ng-model="url" size="80"/> @@ -17822,34 +17855,36 @@ <pre>http status code: {{status}}</pre> <pre>http response data: {{data}}</pre> </div> </file> <file name="script.js"> - function FetchCtrl($scope, $http, $templateCache) { - $scope.method = 'GET'; - $scope.url = 'http-hello.html'; + angular.module('httpExample', []) + .controller('FetchController', ['$scope', '$http', '$templateCache', + function($scope, $http, $templateCache) { + $scope.method = 'GET'; + $scope.url = 'http-hello.html'; - $scope.fetch = function() { - $scope.code = null; - $scope.response = null; + $scope.fetch = function() { + $scope.code = null; + $scope.response = null; - $http({method: $scope.method, url: $scope.url, cache: $templateCache}). - success(function(data, status) { - $scope.status = status; - $scope.data = data; - }). - error(function(data, status) { - $scope.data = data || "Request failed"; - $scope.status = status; - }); - }; + $http({method: $scope.method, url: $scope.url, cache: $templateCache}). + success(function(data, status) { + $scope.status = status; + $scope.data = data; + }). + error(function(data, status) { + $scope.data = data || "Request failed"; + $scope.status = status; + }); + }; - $scope.updateModel = function(method, url) { - $scope.method = method; - $scope.url = url; - }; - } + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + }]); </file> <file name="http-hello.html"> Hello, $http! </file> <file name="protractor.js" type="protractor"> @@ -17899,11 +17934,11 @@ var serverRequest = function(config) { headers = config.headers; var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); // strip content-type if data is undefined - if (isUndefined(config.data)) { + if (isUndefined(reqData)) { forEach(headers, function(value, header) { if (lowercase(header) === 'content-type') { delete headers[header]; } }); @@ -17968,14 +18003,10 @@ reqHeaders = extend({}, config.headers), defHeaderName, lowercaseDefHeaderName, reqHeaderName; defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); - // execute if header value is function - execHeaders(defHeaders); - execHeaders(reqHeaders); - // using for-in instead of forEach to avoid unecessary iteration after header has been found defaultHeadersIteration: for (defHeaderName in defHeaders) { lowercaseDefHeaderName = lowercase(defHeaderName); @@ -17986,10 +18017,12 @@ } reqHeaders[defHeaderName] = defHeaders[defHeaderName]; } + // execute if header value is a function for merged headers + execHeaders(reqHeaders); return reqHeaders; function execHeaders(headers) { var headerContent; @@ -18893,29 +18926,31 @@ * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. * @returns {promise} A promise which will be notified on each iteration. * * @example - * <example module="time"> - * <file name="index.html"> - * <script> - * function Ctrl2($scope,$interval) { - * $scope.format = 'M/d/yy h:mm:ss a'; - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; + * <example module="intervalExample"> + * <file name="index.html"> + * <script> + * angular.module('intervalExample', []) + * .controller('ExampleController', ['$scope', '$interval', + * function($scope, $interval) { + * $scope.format = 'M/d/yy h:mm:ss a'; + * $scope.blood_1 = 100; + * $scope.blood_2 = 120; * - * var stop; - * $scope.fight = function() { - * // Don't start a new fight if we are already fighting - * if ( angular.isDefined(stop) ) return; + * var stop; + * $scope.fight = function() { + * // 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; + * $scope.blood_1 = $scope.blood_1 - 3; + * $scope.blood_2 = $scope.blood_2 - 4; * } else { - * $scope.stopFight(); + * $scope.stopFight(); * } * }, 100); * }; * * $scope.stopFight = function() { @@ -18926,26 +18961,25 @@ * }; * * $scope.resetFight = function() { * $scope.blood_1 = 100; * $scope.blood_2 = 120; - * } + * }; * * $scope.$on('$destroy', function() { - * // Make sure that the interval is destroyed too + * // Make sure that the interval nis destroyed too * $scope.stopFight(); * }); - * } - * - * angular.module('time', []) - * // Register the 'myCurrentTime' directive factory method. - * // We inject $interval and dateFilter service since the factory method is DI. - * .directive('myCurrentTime', function($interval, dateFilter) { + * }) + * // Register the 'myCurrentTime' directive factory method. + * // We inject $interval and dateFilter service since the factory method is DI. + * .directive('myCurrentTime', ['$interval', 'dateFilter', + * function($interval, dateFilter) { * // return the directive link function. (compile function not needed) * return function(scope, element, attrs) { * var format, // date format - * stopTime; // so that we can cancel the time updates + * stopTime; // so that we can cancel the time updates * * // used to update the UI * function updateTime() { * element.text(dateFilter(new Date(), format)); * } @@ -18957,32 +18991,32 @@ * }); * * stopTime = $interval(updateTime, 1000); * * // listen on DOM destroy (removal) event, and cancel the next UI update - * // to prevent updating time ofter the DOM element was removed. + * // to prevent updating time after the DOM element was removed. * element.on('$destroy', function() { * $interval.cancel(stopTime); * }); * } * }); - * </script> + * </script> * - * <div> - * <div ng-controller="Ctrl2"> - * Date format: <input ng-model="format"> <hr/> - * Current time is: <span my-current-time="format"></span> - * <hr/> - * Blood 1 : <font color='red'>{{blood_1}}</font> - * Blood 2 : <font color='red'>{{blood_2}}</font> - * <button type="button" data-ng-click="fight()">Fight</button> - * <button type="button" data-ng-click="stopFight()">StopFight</button> - * <button type="button" data-ng-click="resetFight()">resetFight</button> - * </div> + * <div> + * <div ng-controller="ExampleController"> + * Date format: <input ng-model="format"> <hr/> + * Current time is: <span my-current-time="format"></span> + * <hr/> + * Blood 1 : <font color='red'>{{blood_1}}</font> + * Blood 2 : <font color='red'>{{blood_2}}</font> + * <button type="button" data-ng-click="fight()">Fight</button> + * <button type="button" data-ng-click="stopFight()">StopFight</button> + * <button type="button" data-ng-click="resetFight()">resetFight</button> * </div> + * </div> * - * </file> + * </file> * </example> */ function interval(fn, delay, count, invokeApply) { var setInterval = $window.setInterval, clearInterval = $window.clearInterval, @@ -19535,18 +19569,21 @@ * of `$location` to the specified value. * * If the argument is a hash object containing an array of values, these values will be encoded * as duplicate search parameters in the url. * - * @param {(string|Array<string>)=} paramValue If `search` is a string, then `paramValue` will - * override only a single search property. + * @param {(string|Array<string>|boolean)=} paramValue If `search` is a string, then `paramValue` + * will override only a single search property. * * If `paramValue` is an array, it will override the property of the `search` component of * `$location` specified via the first argument. * * If `paramValue` is `null`, the property specified via the first argument will be deleted. * + * If `paramValue` is `true`, the property specified via the first argument will be added with no + * value nor trailing equal sign. + * * @return {Object} If called with no arguments returns the parsed `search` object. If called with * one or more arguments returns `$location` object itself. */ search: function(search, paramValue) { switch (arguments.length) { @@ -19554,10 +19591,15 @@ return this.$$search; case 1: if (isString(search)) { this.$$search = parseKeyValue(search); } else if (isObject(search)) { + // remove object undefined or null properties + forEach(search, function(value, key) { + if (value == null) delete search[key]; + }); + this.$$search = search; } else { throw $locationMinErr('isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); } @@ -19875,19 +19917,20 @@ * * The default is to log `debug` messages. You can use * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. * * @example - <example> + <example module="logExample"> <file name="script.js"> - function LogCtrl($scope, $log) { - $scope.$log = $log; - $scope.message = 'Hello World!'; - } + angular.module('logExample', []) + .controller('LogController', ['$scope', '$log', function($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + }]); </file> <file name="index.html"> - <div ng-controller="LogCtrl"> + <div ng-controller="LogController"> <p>Reload this page with open console, enter text and hit the log button...</p> Message: <input type="text" ng-model="message"/> <button ng-click="$log.log(message)">log</button> <button ng-click="$log.warn(message)">warn</button> @@ -19907,11 +19950,11 @@ function $LogProvider(){ var debug = true, self = this; /** - * @ngdoc property + * @ngdoc method * @name $logProvider#debugEnabled * @description * @param {boolean=} flag enable or disable debug level messages * @returns {*} current value if used as getter or itself (chaining) if used as setter */ @@ -20896,30 +20939,10 @@ return pathVal; }; } -function simpleGetterFn1(key0, fullExp) { - ensureSafeMemberName(key0, fullExp); - - return function simpleGetterFn1(scope, locals) { - if (scope == null) return undefined; - return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; - }; -} - -function simpleGetterFn2(key0, key1, fullExp) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - - return function simpleGetterFn2(scope, locals) { - if (scope == null) return undefined; - scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; - return scope == null ? undefined : scope[key1]; - }; -} - function getterFn(path, options, fullExp) { // Check whether the cache has this getter already. // We can use hasOwnProperty directly on the cache because we ensure, // see below, that the cache never stores a path called 'hasOwnProperty' if (getterFnCache.hasOwnProperty(path)) { @@ -20928,17 +20951,12 @@ var pathKeys = path.split('.'), pathKeysLength = pathKeys.length, fn; - // When we have only 1 or 2 tokens, use optimized special case closures. // http://jsperf.com/angularjs-parse-getter/6 - if (pathKeysLength === 1) { - fn = simpleGetterFn1(pathKeys[0], fullExp); - } else if (pathKeysLength === 2) { - fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); - } else if (options.csp) { + if (options.csp) { if (pathKeysLength < 6) { fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); } else { fn = function(scope, locals) { var i = 0, val; @@ -23072,23 +23090,25 @@ * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc. * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. * * Here is what a secure configuration for this scenario might look like: * - * <pre class="prettyprint"> - * angular.module('myApp', []).config(function($sceDelegateProvider) { - * $sceDelegateProvider.resourceUrlWhitelist([ - * // Allow same origin resource loads. - * 'self', - * // Allow loading from our assets domain. Notice the difference between * and **. - * 'http://srv*.assets.example.com/**']); + * ``` + * angular.module('myApp', []).config(function($sceDelegateProvider) { + * $sceDelegateProvider.resourceUrlWhitelist([ + * // Allow same origin resource loads. + * 'self', + * // Allow loading from our assets domain. Notice the difference between * and **. + * 'http://srv*.assets.example.com/**' + * ]); * - * // The blacklist overrides the whitelist so the open redirect here is blocked. - * $sceDelegateProvider.resourceUrlBlacklist([ - * 'http://myapp.example.com/clickThru**']); - * }); - * </pre> + * // The blacklist overrides the whitelist so the open redirect here is blocked. + * $sceDelegateProvider.resourceUrlBlacklist([ + * 'http://myapp.example.com/clickThru**' + * ]); + * }); + * ``` */ function $SceDelegateProvider() { this.SCE_CONTEXTS = SCE_CONTEXTS; @@ -23379,14 +23399,14 @@ * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. * * Here's an example of a binding in a privileged context: * - * <pre class="prettyprint"> - * <input ng-model="userHtml"> - * <div ng-bind-html="userHtml"> - * </pre> + * ``` + * <input ng-model="userHtml"> + * <div ng-bind-html="userHtml"></div> + * ``` * * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE * disabled, this application allows the user to render arbitrary HTML into the DIV. * In a more realistic example, one may be rendering user comments, blog articles, etc. via * bindings. (HTML is just one example of a context where rendering user controlled input creates @@ -23422,19 +23442,19 @@ * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly * simplified): * - * <pre class="prettyprint"> - * var ngBindHtmlDirective = ['$sce', function($sce) { - * return function(scope, element, attr) { - * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { - * element.html(value || ''); - * }); - * }; - * }]; - * </pre> + * ``` + * var ngBindHtmlDirective = ['$sce', function($sce) { + * return function(scope, element, attr) { + * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { + * element.html(value || ''); + * }); + * }; + * }]; + * ``` * * ## Impact on loading templates * * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as * `templateUrl`'s specified by {@link guide/directive directives}. @@ -23534,90 +23554,89 @@ * * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. * * ## Show me an example using SCE. * - * @example -<example module="mySceApp" deps="angular-sanitize.js"> -<file name="index.html"> - <div ng-controller="myAppController as myCtrl"> - <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> - <b>User comments</b><br> - By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when - $sanitize is available. If $sanitize isn't available, this results in an error instead of an - exploit. - <div class="well"> - <div ng-repeat="userComment in myCtrl.userComments"> - <b>{{userComment.name}}</b>: - <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> - <br> - </div> - </div> - </div> -</file> - -<file name="script.js"> - var mySceApp = angular.module('mySceApp', ['ngSanitize']); - - mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { - var self = this; - $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - self.userComments = userComments; - }); - self.explicitlyTrustedHtml = $sce.trustAsHtml( - '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' + - 'sanitization.&quot;">Hover over this text.</span>'); - }); -</file> - -<file name="test_data.json"> -[ - { "name": "Alice", - "htmlComment": - "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" - }, - { "name": "Bob", - "htmlComment": "<i>Yes!</i> Am I the only other one?" - } -] -</file> - -<file name="protractor.js" type="protractor"> - describe('SCE doc demo', function() { - it('should sanitize untrusted values', function() { - expect(element(by.css('.htmlComment')).getInnerHtml()) - .toBe('<span>Is <i>anyone</i> reading this?</span>'); - }); - - it('should NOT sanitize explicitly trusted values', function() { - expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( - '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' + - 'sanitization.&quot;">Hover over this text.</span>'); - }); - }); -</file> -</example> + * <example module="mySceApp" deps="angular-sanitize.js"> + * <file name="index.html"> + * <div ng-controller="AppController as myCtrl"> + * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> + * <b>User comments</b><br> + * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + * $sanitize is available. If $sanitize isn't available, this results in an error instead of an + * exploit. + * <div class="well"> + * <div ng-repeat="userComment in myCtrl.userComments"> + * <b>{{userComment.name}}</b>: + * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> + * <br> + * </div> + * </div> + * </div> + * </file> * + * <file name="script.js"> + * angular.module('mySceApp', ['ngSanitize']) + * .controller('AppController', ['$http', '$templateCache', '$sce', + * function($http, $templateCache, $sce) { + * var self = this; + * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + * self.userComments = userComments; + * }); + * self.explicitlyTrustedHtml = $sce.trustAsHtml( + * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' + + * 'sanitization.&quot;">Hover over this text.</span>'); + * }]); + * </file> * + * <file name="test_data.json"> + * [ + * { "name": "Alice", + * "htmlComment": + * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" + * }, + * { "name": "Bob", + * "htmlComment": "<i>Yes!</i> Am I the only other one?" + * } + * ] + * </file> * + * <file name="protractor.js" type="protractor"> + * describe('SCE doc demo', function() { + * it('should sanitize untrusted values', function() { + * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) + * .toBe('<span>Is <i>anyone</i> reading this?</span>'); + * }); + * + * it('should NOT sanitize explicitly trusted values', function() { + * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( + * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' + + * 'sanitization.&quot;">Hover over this text.</span>'); + * }); + * }); + * </file> + * </example> + * + * + * * ## Can I disable SCE completely? * * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits * for little coding overhead. It will be much harder to take an SCE disabled application and * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE * for cases where you have a lot of existing code that was written before SCE was introduced and * you're migrating them a module at a time. * * That said, here's how you can completely disable SCE: * - * <pre class="prettyprint"> - * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { - * // Completely disable SCE. For demonstration purposes only! - * // Do not use in new projects. - * $sceProvider.enabled(false); - * }); - * </pre> + * ``` + * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { + * // Completely disable SCE. For demonstration purposes only! + * // Do not use in new projects. + * $sceProvider.enabled(false); + * }); + * ``` * */ /* jshint maxlen: 100 */ function $SceProvider() { @@ -24312,21 +24331,22 @@ * below, are evaluated with respect to the current scope. Therefore, there is * no risk of inadvertently coding in a dependency on a global value in such an * expression. * * @example - <example> + <example module="windowExample"> <file name="index.html"> <script> - function Ctrl($scope, $window) { - $scope.greeting = 'Hello, World!'; - $scope.doGreeting = function(greeting) { + angular.module('windowExample', []) + .controller('ExampleController', ['$scope', '$window', function ($scope, $window) { + $scope.greeting = 'Hello, World!'; + $scope.doGreeting = function(greeting) { $window.alert(greeting); - }; - } + }; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <input type="text" ng-model="greeting" /> <button ng-click="doGreeting(greeting)">ALERT</button> </div> </file> <file name="protractor.js" type="protractor"> @@ -24340,10 +24360,21 @@ */ function $WindowProvider(){ this.$get = valueFn(window); } +/* global currencyFilter: true, + dateFilter: true, + filterFilter: true, + jsonFilter: true, + limitToFilter: true, + lowercaseFilter: true, + numberFilter: true, + orderByFilter: true, + uppercaseFilter: true, + */ + /** * @ngdoc provider * @name $filterProvider * @description * @@ -24721,18 +24752,19 @@ * @param {string=} symbol Currency symbol or identifier to be displayed. * @returns {string} Formatted number. * * * @example - <example> + <example module="currencyExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.amount = 1234.56; - } + angular.module('currencyExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.amount = 1234.56; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <input type="number" ng-model="amount"> <br> default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> </div> </file> @@ -24780,18 +24812,19 @@ * If this is not provided then the fraction size is computed from the current locale's number * formatting pattern. In the case of the default locale, it will be 3. * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. * * @example - <example> + <example module="numberFilterExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.val = 1234.56789; - } + angular.module('numberFilterExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.val = 1234.56789; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> Enter number: <input ng-model='val'><br> Default formatting: <span id='number-default'>{{val | number}}</span><br> No fractions: <span>{{val | number:0}}</span><br> Negative number: <span>{{-val | number:4}}</span> </div> @@ -25241,21 +25274,22 @@ * are copied. The `limit` will be trimmed if it exceeds `array.length` * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array * had less than `limit` elements. * * @example - <example> + <example module="limitToExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.numbers = [1,2,3,4,5,6,7,8,9]; - $scope.letters = "abcdefghi"; - $scope.numLimit = 3; - $scope.letterLimit = 3; - } + angular.module('limitToExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.numbers = [1,2,3,4,5,6,7,8,9]; + $scope.letters = "abcdefghi"; + $scope.numLimit = 3; + $scope.letterLimit = 3; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> <p>Output letters: {{ letters | limitTo:letterLimit }}</p> </div> @@ -25363,24 +25397,25 @@ * * @param {boolean=} reverse Reverse the order of the array. * @returns {Array} Sorted copy of the source array. * * @example - <example> + <example module="orderByExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.friends = - [{name:'John', phone:'555-1212', age:10}, - {name:'Mary', phone:'555-9876', age:19}, - {name:'Mike', phone:'555-4321', age:21}, - {name:'Adam', phone:'555-5678', age:35}, - {name:'Julie', phone:'555-8765', age:29}] - $scope.predicate = '-age'; - } + angular.module('orderByExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.friends = + [{name:'John', phone:'555-1212', age:10}, + {name:'Mary', phone:'555-9876', age:19}, + {name:'Mike', phone:'555-4321', age:21}, + {name:'Adam', phone:'555-5678', age:35}, + {name:'Julie', phone:'555-8765', age:29}]; + $scope.predicate = '-age'; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> <hr/> [ <a href="" ng-click="predicate=''">unsorted</a> ] <table class="friend"> <tr> @@ -25404,11 +25439,11 @@ * desired parameters. * * Example: * * @example - <example> + <example module="orderByExample"> <file name="index.html"> <div ng-controller="Ctrl"> <table class="friend"> <tr> <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> @@ -25424,25 +25459,25 @@ </table> </div> </file> <file name="script.js"> - function Ctrl($scope, $filter) { - var orderBy = $filter('orderBy'); - $scope.friends = [ - { name: 'John', phone: '555-1212', age: 10 }, - { name: 'Mary', phone: '555-9876', age: 19 }, - { name: 'Mike', phone: '555-4321', age: 21 }, - { name: 'Adam', phone: '555-5678', age: 35 }, - { name: 'Julie', phone: '555-8765', age: 29 } - ]; - - $scope.order = function(predicate, reverse) { - $scope.friends = orderBy($scope.friends, predicate, reverse); - }; - $scope.order('-age',false); - } + angular.module('orderByExample', []) + .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { + var orderBy = $filter('orderBy'); + $scope.friends = [ + { name: 'John', phone: '555-1212', age: 10 }, + { name: 'Mary', phone: '555-9876', age: 19 }, + { name: 'Mike', phone: '555-4321', age: 21 }, + { name: 'Adam', phone: '555-5678', age: 35 }, + { name: 'Julie', phone: '555-8765', age: 29 } + ]; + $scope.order = function(predicate, reverse) { + $scope.friends = orderBy($scope.friends, predicate, reverse); + }; + $scope.order('-age',false); + }]); </file> </example> */ orderByFilter.$inject = ['$parse']; function orderByFilter($parse){ @@ -25624,11 +25659,11 @@ browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/123$/); }); - }, 1000, 'page should navigate to /123'); + }, 5000, 'page should navigate to /123'); }); xit('should execute ng-click but not reload when href empty string and name specified', function() { element(by.id('link-4')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('4'); @@ -25652,11 +25687,11 @@ // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/6$/); }); - }, 1000, 'page should navigate to /6'); + }, 5000, 'page should navigate to /6'); }); </file> </example> */ @@ -26053,10 +26088,27 @@ $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); } /** * @ngdoc method + * @name form.FormController#$rollbackViewValue + * + * @description + * Rollback all form controls pending updates to the `$modelValue`. + * + * Updates may be pending by a debounced event or because the input is waiting for a some future + * event defined in `ng-model-options`. This method is typically needed by the reset button of + * a form that uses `ng-model-options` to pend updates. + */ + form.$rollbackViewValue = function() { + forEach(controls, function(control) { + control.$rollbackViewValue(); + }); + }; + + /** + * @ngdoc method * @name form.FormController#$commitViewValue * * @description * Commit all form controls pending updates to the `$modelValue`. * @@ -26312,16 +26364,17 @@ * color:white; * } * </pre> * * @example - <example deps="angular-animate.js" animations="true" fixBase="true"> + <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.userType = 'guest'; - } + angular.module('formExample', []) + .controller('FormController', ['$scope', function($scope) { + $scope.userType = 'guest'; + }]); </script> <style> .my-form { -webkit-transition:all linear 0.5s; transition:all linear 0.5s; @@ -26329,11 +26382,11 @@ } .my-form.ng-invalid { background: red; } </style> - <form name="myForm" ng-controller="Ctrl" class="my-form"> + <form name="myForm" ng-controller="FormController" class="my-form"> userType: <input name="input" ng-model="userType" required> <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> <tt>userType = {{userType}}</tt><br> <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> @@ -26427,22 +26480,20 @@ }; var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); -/* global - - -VALID_CLASS, - -INVALID_CLASS, - -PRISTINE_CLASS, - -DIRTY_CLASS, - -UNTOUCHED_CLASS, - -TOUCHED_CLASS +/* global VALID_CLASS: true, + INVALID_CLASS: true, + PRISTINE_CLASS: true, + DIRTY_CLASS: true, + UNTOUCHED_CLASS: true, + TOUCHED_CLASS: true, */ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; -var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; +var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/; var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; @@ -26474,19 +26525,20 @@ * @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. * * @example - <example name="text-input-directive"> + <example name="text-input-directive" module="textInputExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.text = 'guest'; - $scope.word = /^\s*\w*\s*$/; - } + angular.module('textInputExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.text = 'guest'; + $scope.word = /^\s*\w*\s*$/; + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> Single word: <input type="text" name="input" ng-model="text" ng-pattern="word" required ng-trim="false"> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> <span class="error" ng-show="myForm.input.$error.pattern"> @@ -26549,18 +26601,19 @@ * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="date-input-directive"> + <example name="date-input-directive" module="dateInputExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value = new Date(2013, 9, 22); - } + angular.module('dateInputExample', []) + .controller('DateController', ['$scope', function($scope) { + $scope.value = new Date(2013, 9, 22); + }]); </script> - <form name="myForm" ng-controller="Ctrl as dateCtrl"> + <form name="myForm" ng-controller="DateController as dateCtrl"> Pick a date between in 2013: <input type="date" id="exampleInput" name="input" ng-model="value" placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> @@ -26633,18 +26686,19 @@ * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="datetimelocal-input-directive"> + <example name="datetimelocal-input-directive" module="dateExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value = new Date(2010, 11, 28, 14, 57); - } + angular.module('dateExample', []) + .controller('DateController', ['$scope', function($scope) { + $scope.value = new Date(2010, 11, 28, 14, 57); + }]); </script> - <form name="myForm" ng-controller="Ctrl as dateCtrl"> + <form name="myForm" ng-controller="DateController as dateCtrl"> Pick a date between in 2013: <input type="datetime-local" id="exampleInput" name="input" ng-model="value" placeholder="yyyy-MM-ddTHH:mm" min="2001-01-01T00:00" max="2013-12-31T00:00" required /> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> @@ -26718,18 +26772,19 @@ * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="time-input-directive"> + <example name="time-input-directive" module="timeExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value = new Date(0, 0, 1, 14, 57); - } + angular.module('timeExample', []) + .controller('DateController', ['$scope', function($scope) { + $scope.value = new Date(0, 0, 1, 14, 57); + }]); </script> - <form name="myForm" ng-controller="Ctrl as dateCtrl"> + <form name="myForm" ng-controller="DateController as dateCtrl"> Pick a between 8am and 5pm: <input type="time" id="exampleInput" name="input" ng-model="value" placeholder="HH:mm" min="08:00" max="17:00" required /> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> @@ -26802,18 +26857,19 @@ * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="week-input-directive"> + <example name="week-input-directive" module="weekExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value = new Date(2013, 0, 3); - } + angular.module('weekExample', []) + .controller('DateController', ['$scope', function($scope) { + $scope.value = new Date(2013, 0, 3); + }]); </script> - <form name="myForm" ng-controller="Ctrl as dateCtrl"> + <form name="myForm" ng-controller="DateController as dateCtrl"> Pick a date between in 2013: <input id="exampleInput" type="week" name="input" ng-model="value" placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required /> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> @@ -26885,18 +26941,19 @@ * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="month-input-directive"> + <example name="month-input-directive" module="monthExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value = new Date(2013, 9, 1); - } + angular.module('monthExample', []) + .controller('DateController', ['$scope', function($scope) { + $scope.value = new Date(2013, 9, 1); + }]); </script> - <form name="myForm" ng-controller="Ctrl as dateCtrl"> + <form name="myForm" ng-controller="DateController as dateCtrl"> Pick a month int 2013: <input id="exampleInput" type="month" name="input" ng-model="value" placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> @@ -26973,18 +27030,19 @@ * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="number-input-directive"> + <example name="number-input-directive" module="numberExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value = 12; - } + angular.module('numberExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.value = 12; + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> Number: <input type="number" name="input" ng-model="value" min="0" max="99" required> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> <span class="error" ng-show="myForm.input.$error.number"> @@ -27048,18 +27106,19 @@ * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="url-input-directive"> + <example name="url-input-directive" module="urlExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.text = 'http://google.com'; - } + angular.module('urlExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.text = 'http://google.com'; + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> URL: <input type="url" name="input" ng-model="text" required> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> <span class="error" ng-show="myForm.input.$error.url"> Not valid url!</span> @@ -27124,18 +27183,19 @@ * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="email-input-directive"> + <example name="email-input-directive" module="emailExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.text = 'me@example.com'; - } + angular.module('emailExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.text = 'me@example.com'; + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> Email: <input type="email" name="input" ng-model="text" required> <span class="error" ng-show="myForm.input.$error.required"> Required!</span> <span class="error" ng-show="myForm.input.$error.email"> Not valid email!</span> @@ -27190,22 +27250,23 @@ * interaction with the input element. * @param {string} ngValue Angular expression which sets the value to which the expression should * be set when selected. * * @example - <example name="radio-input-directive"> + <example name="radio-input-directive" module="radioExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.color = 'blue'; - $scope.specialValue = { - "id": "12345", - "value": "green" - }; - } + angular.module('radioExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.color = 'blue'; + $scope.specialValue = { + "id": "12345", + "value": "green" + }; + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> <input type="radio" ng-model="color" value="red"> Red <br/> <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> <input type="radio" ng-model="color" value="blue"> Blue <br/> <tt>color = {{color | json}}</tt><br/> </form> @@ -27234,28 +27295,29 @@ * @description * HTML checkbox. * * @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=} ngTrueValue The value to which the expression should be set when selected. - * @param {string=} ngFalseValue The value to which the expression should be set when not selected. + * @param {expression=} ngTrueValue The value to which the expression should be set when selected. + * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="checkbox-input-directive"> + <example name="checkbox-input-directive" module="checkboxExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.value1 = true; - $scope.value2 = 'YES' - } + angular.module('checkboxExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.value1 = true; + $scope.value2 = 'YES' + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> Value1: <input type="checkbox" ng-model="value1"> <br/> Value2: <input type="checkbox" ng-model="value2" - ng-true-value="YES" ng-false-value="NO"> <br/> + ng-true-value="'YES'" ng-false-value="'NO'"> <br/> <tt>value1 = {{value1}}</tt><br/> <tt>value2 = {{value2}}</tt><br/> </form> </file> <file name="protractor.js" type="protractor"> @@ -27610,16 +27672,26 @@ }; attr.$observe('value', ctrl.$render); } -function checkboxInputType(scope, element, attr, ctrl) { - var trueValue = attr.ngTrueValue, - falseValue = attr.ngFalseValue; +function parseConstantExpr($parse, context, name, expression, fallback) { + var parseFn; + if (isDefined(expression)) { + parseFn = $parse(expression); + if (!parseFn.constant) { + throw new minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' + + '`{1}`.', name, expression); + } + return parseFn(context); + } + return fallback; +} - if (!isString(trueValue)) trueValue = true; - if (!isString(falseValue)) falseValue = false; +function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { + var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); + var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); var listener = function(ev) { scope.$apply(function() { ctrl.$setViewValue(element[0].checked, ev && ev.type); }); @@ -27635,11 +27707,11 @@ ctrl.$isEmpty = function(value) { return value !== trueValue; }; ctrl.$formatters.push(function(value) { - return value === trueValue; + return equals(value, trueValue); }); ctrl.$parsers.push(function(value) { return value ? trueValue : falseValue; }); @@ -27697,18 +27769,19 @@ * patterns defined as scope expressions. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example - <example name="input-directive"> + <example name="input-directive" module="inputExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.user = {name: 'guest', last: 'visitor'}; - } + angular.module('inputExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.user = {name: 'guest', last: 'visitor'}; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <form name="myForm"> User name: <input type="text" name="userName" ng-model="user.name" required> <span class="error" ng-show="myForm.userName.$error.required"> Required!</span><br> Last name: <input type="text" name="lastName" ng-model="user.last" @@ -27783,18 +27856,19 @@ expect(formValid.getText()).toContain('false'); }); </file> </example> */ -var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sniffer, $filter) { +var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', + function($browser, $sniffer, $filter, $parse) { return { restrict: 'E', require: ['?ngModel'], link: function(scope, element, attr, ctrls) { if (ctrls[0]) { (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, - $browser, $filter); + $browser, $filter, $parse); } } }; }]; @@ -27984,10 +28058,22 @@ * @name ngModel.NgModelController#$render * * @description * Called when the view needs to be updated. It is expected that the user of the ng-model * directive will implement this method. + * + * The `$render()` method is invoked in the following situations: + * + * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last + * committed value then `$render()` is called to update the input control. + * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and + * the `$viewValue` are different to last time. + * + * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of + * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue` + * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be + * invoked if you only change a property on the objects. */ this.$render = noop; /** * @ngdoc method @@ -28149,11 +28235,11 @@ * * <example name="ng-model-cancel-update" module="cancel-update-example"> * <file name="app.js"> * angular.module('cancel-update-example', []) * - * .controller('CancelUpdateCtrl', function($scope) { + * .controller('CancelUpdateController', ['$scope', function($scope) { * $scope.resetWithCancel = function (e) { * if (e.keyCode == 27) { * $scope.myForm.myInput1.$rollbackViewValue(); * $scope.myValue = ''; * } @@ -28161,14 +28247,14 @@ * $scope.resetWithoutCancel = function (e) { * if (e.keyCode == 27) { * $scope.myValue = ''; * } * }; - * }); + * }]); * </file> * <file name="index.html"> - * <div ng-controller="CancelUpdateCtrl"> + * <div ng-controller="CancelUpdateController"> * <p>Try typing something in each input. See that the model only updates when you * blur off the input. * </p> * <p>Now see what happens if you start typing then press the Escape key</p> * @@ -28197,17 +28283,28 @@ * * @description * Runs each of the registered validations set on the $validators object. */ this.$validate = function() { - this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue); + // ignore $validate before model initialized + if (ctrl.$modelValue !== ctrl.$modelValue) { + return; + } + + var prev = ctrl.$modelValue; + ctrl.$$runValidators(ctrl.$$invalidModelValue || ctrl.$modelValue, ctrl.$viewValue); + if (prev !== ctrl.$modelValue) { + ctrl.$$writeModelToScope(); + } }; this.$$runValidators = function(modelValue, viewValue) { forEach(ctrl.$validators, function(fn, name) { ctrl.$setValidity(name, fn(modelValue, viewValue)); }); + ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; + ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; }; /** * @ngdoc method * @name ngModel.NgModelController#$commitViewValue @@ -28242,41 +28339,61 @@ modelValue = fn(modelValue); }); if (ctrl.$modelValue !== modelValue && (isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) { - ctrl.$$runValidators(modelValue, viewValue); - ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; - ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; + ctrl.$$writeModelToScope(); + } + }; + this.$$writeModelToScope = function() { + var getterSetter; + + if (ctrl.$options && ctrl.$options.getterSetter && + isFunction(getterSetter = ngModelGet($scope))) { + + getterSetter(ctrl.$modelValue); + } else { ngModelSet($scope, ctrl.$modelValue); - forEach(ctrl.$viewChangeListeners, function(listener) { - try { - listener(); - } catch(e) { - $exceptionHandler(e); - } - }); } + forEach(ctrl.$viewChangeListeners, function(listener) { + try { + listener(); + } catch(e) { + $exceptionHandler(e); + } + }); }; /** * @ngdoc method * @name ngModel.NgModelController#$setViewValue * * @description * Update the view value. * - * This method should be called when the view value changes, typically from within a DOM event handler. - * For example {@link ng.directive:input input} and - * {@link ng.directive:select select} directives call it. + * This method should be called when an input directive want to change the view value; typically, + * this is done from within a DOM event handler. * - * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, - * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to - * `$modelValue` and the **expression** specified in the `ng-model` attribute. + * For example {@link ng.directive:input input} calls it when the value of the input changes and + * {@link ng.directive:select select} calls it when an option is selected. * + * If the new `value` is an object (rather than a string or a number), we should make a copy of the + * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep + * watch of objects, it only looks for a change of identity. If you only change the property of + * the object then ngModel will not realise that the object has changed and will not invoke the + * `$parsers` and `$validators` pipelines. + * + * For this reason, you should not change properties of the copy once it has been passed to + * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly. + * + * When this method is called, the new `value` will be staged for committing through the `$parsers` + * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged + * value sent directly for processing, finally to be applied to `$modelValue` and then the + * **expression** specified in the `ng-model` attribute. + * * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. * * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` * and the `default` trigger is not listed, all those actions will remain pending until one of the * `updateOn` events is triggered on the DOM element. @@ -28323,10 +28440,14 @@ // model -> value $scope.$watch(function ngModelWatch() { var modelValue = ngModelGet($scope); + if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) { + modelValue = modelValue(); + } + // if scope model value and ngModel value are out of sync if (ctrl.$modelValue !== modelValue && (isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) { var formatters = ctrl.$formatters, @@ -28336,12 +28457,10 @@ while(idx--) { viewValue = formatters[idx](viewValue); } ctrl.$$runValidators(modelValue, viewValue); - ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; - ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; if (ctrl.$viewValue !== viewValue) { ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; ctrl.$render(); } @@ -28431,16 +28550,17 @@ * color:white; * } * </pre> * * @example - * <example deps="angular-animate.js" animations="true" fixBase="true"> + * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.val = '1'; - } + angular.module('inputExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.val = '1'; + }]); </script> <style> .my-input { -webkit-transition:all linear 0.5s; transition:all linear 0.5s; @@ -28451,15 +28571,64 @@ background: red; } </style> Update input to see transitions when valid/invalid. Integer is a valid value. - <form name="testForm" ng-controller="Ctrl"> + <form name="testForm" ng-controller="ExampleController"> <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> </form> </file> * </example> + * + * ## Binding to a getter/setter + * + * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a + * function that returns a representation of the model when called with zero arguments, and sets + * the internal state of a model when called with an argument. It's sometimes useful to use this + * for models that have an internal representation that's different than what the model exposes + * to the view. + * + * <div class="alert alert-success"> + * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more + * frequently than other parts of your code. + * </div> + * + * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that + * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to + * a `<form>`, which will enable this behavior for all `<input>`s within it. See + * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. + * + * The following example shows how to use `ngModel` with a getter/setter: + * + * @example + * <example name="ngModel-getter-setter" module="getterSetterExample"> + <file name="index.html"> + <div ng-controller="ExampleController"> + <form name="userForm"> + Name: + <input type="text" name="userName" + ng-model="user.name" + ng-model-options="{ getterSetter: true }" /> + </form> + <pre>user.name = <span ng-bind="user.name()"></span></pre> + </div> + </file> + <file name="app.js"> + angular.module('getterSetterExample', []) + .controller('ExampleController', ['$scope', function($scope) { + var _name = 'Brian'; + $scope.user = { + name: function (newName) { + if (angular.isDefined(newName)) { + _name = newName; + } + return _name; + } + }; + }]); + </file> + * </example> */ var ngModelDirective = function() { return { require: ['ngModel', '^?form', '^?ngModelOptions'], controller: NgModelController, @@ -28518,21 +28687,22 @@ * @element input * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change * in input value. * * @example - * <example name="ngChange-directive"> + * <example name="ngChange-directive" module="changeExample"> * <file name="index.html"> * <script> - * function Controller($scope) { - * $scope.counter = 0; - * $scope.change = function() { - * $scope.counter++; - * }; - * } + * angular.module('changeExample', []) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.counter = 0; + * $scope.change = function() { + * $scope.counter++; + * }; + * }]); * </script> - * <div ng-controller="Controller"> + * <div ng-controller="ExampleController"> * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> * <label for="ng-change-example2">Confirmed</label><br /> * <tt>debug = {{confirmed}}</tt><br/> * <tt>counter = {{counter}}</tt><br/> @@ -28667,18 +28837,19 @@ * @element input * @param {string=} ngList optional delimiter that should be used to split the value. If * specified in form `/something/` then the value will be converted into a regular expression. * * @example - <example name="ngList-directive"> + <example name="ngList-directive" module="listExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.names = ['igor', 'misko', 'vojta']; - } + angular.module('listExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.names = ['igor', 'misko', 'vojta']; + }]); </script> - <form name="myForm" ng-controller="Ctrl"> + <form name="myForm" ng-controller="ExampleController"> List: <input name="namesInput" ng-model="names" ng-list required> <span class="error" ng-show="myForm.namesInput.$error.required"> Required!</span> <br> <tt>names = {{names}}</tt><br/> @@ -28766,19 +28937,20 @@ * @element input * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute * of the `input` element * * @example - <example name="ngValue-directive"> + <example name="ngValue-directive" module="valueExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.names = ['pizza', 'unicorns', 'robots']; - $scope.my = { favorite: 'unicorns' }; - } + angular.module('valueExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.names = ['pizza', 'unicorns', 'robots']; + $scope.my = { favorite: 'unicorns' }; + }]); </script> - <form ng-controller="Ctrl"> + <form ng-controller="ExampleController"> <h2>Which is your favorite?</h2> <label ng-repeat="name in names" for="{{name}}"> {{name}} <input type="radio" ng-model="my.favorite" @@ -28850,20 +29022,22 @@ * matches the default events belonging of the control. * - `debounce`: integer value which contains the debounce model update value in milliseconds. A * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a * custom value for each event. For example: * `ngModelOptions="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` + * - `getterSetter`: boolean value which determines whether or not to treat functions bound to + `ngModel` as getters/setters. * * @example The following example shows how to override immediate updates. Changes on the inputs within the form will update the model only when the control loses focus (blur event). If `escape` key is pressed while the input field is focused, the value is reset to the value in the current model. - <example name="ngModelOptions-directive-blur"> + <example name="ngModelOptions-directive-blur" module="optionsExample"> <file name="index.html"> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <form name="userForm"> Name: <input type="text" name="userName" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" @@ -28874,19 +29048,20 @@ </form> <pre>user.name = <span ng-bind="user.name"></span></pre> </div> </file> <file name="app.js"> - function Ctrl($scope) { - $scope.user = { name: 'say', data: '' }; + angular.module('optionsExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.user = { name: 'say', data: '' }; - $scope.cancel = function (e) { - if (e.keyCode == 27) { - $scope.userForm.userName.$rollbackViewValue(); - } - }; - } + $scope.cancel = function (e) { + if (e.keyCode == 27) { + $scope.userForm.userName.$rollbackViewValue(); + } + }; + }]); </file> <file name="protractor.js" type="protractor"> var model = element(by.binding('user.name')); var input = element(by.model('user.name')); var other = element(by.model('user.data')); @@ -28911,13 +29086,13 @@ </example> This one shows how to debounce model changes. Model will be updated only 1 sec after last change. If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. - <example name="ngModelOptions-directive-debounce"> + <example name="ngModelOptions-directive-debounce" module="optionsExample"> <file name="index.html"> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <form name="userForm"> Name: <input type="text" name="userName" ng-model="user.name" ng-model-options="{ debounce: 1000 }" /> @@ -28925,15 +29100,43 @@ </form> <pre>user.name = <span ng-bind="user.name"></span></pre> </div> </file> <file name="app.js"> - function Ctrl($scope) { - $scope.user = { name: 'say' }; - } + angular.module('optionsExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.user = { name: 'say' }; + }]); </file> </example> + + This one shows how to bind to getter/setters: + + <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> + <file name="index.html"> + <div ng-controller="ExampleController"> + <form name="userForm"> + Name: + <input type="text" name="userName" + ng-model="user.name" + ng-model-options="{ getterSetter: true }" /> + </form> + <pre>user.name = <span ng-bind="user.name()"></span></pre> + </div> + </file> + <file name="app.js"> + angular.module('getterSetterExample', []) + .controller('ExampleController', ['$scope', function($scope) { + var _name = 'Brian'; + $scope.user = { + name: function (newName) { + return angular.isDefined(newName) ? (_name = newName) : _name; + } + }; + }]); + </file> + </example> */ var ngModelOptionsDirective = function() { return { controller: ['$scope', '$attrs', function($scope, $attrs) { var that = this; @@ -28977,18 +29180,19 @@ * @element ANY * @param {expression} ngBind {@link guide/expression Expression} to evaluate. * * @example * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. - <example> + <example module="bindExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.name = 'Whirled'; - } + angular.module('bindExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.name = 'Whirled'; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> Enter name: <input type="text" ng-model="name"><br> Hello <span ng-bind="name"></span>! </div> </file> <file name="protractor.js" type="protractor"> @@ -29035,19 +29239,20 @@ * @param {string} ngBindTemplate template of form * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. * * @example * Try it here: enter text in text box and watch the greeting change. - <example> + <example module="bindExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.salutation = 'Hello'; - $scope.name = 'World'; - } + angular.module('bindExample', []) + .controller('ExampleController', ['$scope', function ($scope) { + $scope.salutation = 'Hello'; + $scope.name = 'World'; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> Salutation: <input type="text" ng-model="salutation"><br> Name: <input type="text" ng-model="name"><br> <pre ng-bind-template="{{salutation}} {{name}}!"></pre> </div> </file> @@ -29101,24 +29306,24 @@ * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. * * @example Try it here: enter text in text box and watch the greeting change. - <example module="ngBindHtmlExample" deps="angular-sanitize.js"> + <example module="bindHtmlExample" deps="angular-sanitize.js"> <file name="index.html"> - <div ng-controller="ngBindHtmlCtrl"> + <div ng-controller="ExampleController"> <p ng-bind-html="myHTML"></p> </div> </file> <file name="script.js"> - angular.module('ngBindHtmlExample', ['ngSanitize']) - - .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { - $scope.myHTML = - 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'; - }]); + angular.module('bindHtmlExample', ['ngSanitize']) + .controller('ExampleController', ['$scope', function($scope) { + $scope.myHTML = + 'I am an <code>HTML</code>string with ' + + '<a href="#">links!</a> and other <em>stuff</em>'; + }]); </file> <file name="protractor.js" type="protractor"> it('should check ng-bind-html', function() { expect(element(by.binding('myHTML')).getText()).toBe( @@ -29617,11 +29822,11 @@ * * Since there is always a `.` in the bindings, you don't have to worry about prototypal * inheritance masking primitives. * * This example demonstrates the `controller as` syntax. * - * <example name="ngControllerAs"> + * <example name="ngControllerAs" module="controllerAsExample"> * <file name="index.html"> * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> * Name: <input type="text" ng-model="settings.name"/> * [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> * Contact: @@ -29638,10 +29843,13 @@ * <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> * </ul> * </div> * </file> * <file name="app.js"> + * angular.module('controllerAsExample', []) + * .controller('SettingsController1', SettingsController1); + * * function SettingsController1() { * this.name = "John Smith"; * this.contacts = [ * {type: 'phone', value: '408 555 1212'}, * {type: 'email', value: 'john.smith@example.org'} ]; @@ -29666,42 +29874,42 @@ * }; * </file> * <file name="protractor.js" type="protractor"> * it('should check controller as', function() { * var container = element(by.id('ctrl-as-exmpl')); - * expect(container.findElement(by.model('settings.name')) + * expect(container.element(by.model('settings.name')) * .getAttribute('value')).toBe('John Smith'); * * var firstRepeat = - * container.findElement(by.repeater('contact in settings.contacts').row(0)); + * container.element(by.repeater('contact in settings.contacts').row(0)); * var secondRepeat = - * container.findElement(by.repeater('contact in settings.contacts').row(1)); + * container.element(by.repeater('contact in settings.contacts').row(1)); * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) * .toBe('408 555 1212'); * - * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) * .toBe('john.smith@example.org'); * - * firstRepeat.findElement(by.linkText('clear')).click(); + * firstRepeat.element(by.linkText('clear')).click(); * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) * .toBe(''); * - * container.findElement(by.linkText('add')).click(); + * container.element(by.linkText('add')).click(); * - * expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) - * .findElement(by.model('contact.value')) + * expect(container.element(by.repeater('contact in settings.contacts').row(2)) + * .element(by.model('contact.value')) * .getAttribute('value')) * .toBe('yourname@example.org'); * }); * </file> * </example> * * This example demonstrates the "attach to `$scope`" style of controller. * - * <example name="ngController"> + * <example name="ngController" module="controllerExample"> * <file name="index.html"> * <div id="ctrl-exmpl" ng-controller="SettingsController2"> * Name: <input type="text" ng-model="name"/> * [ <a href="" ng-click="greet()">greet</a> ]<br/> * Contact: @@ -29718,10 +29926,13 @@ * <li>[ <a href="" ng-click="addContact()">add</a> ]</li> * </ul> * </div> * </file> * <file name="app.js"> + * angular.module('controllerExample', []) + * .controller('SettingsController2', ['$scope', SettingsController2]); + * * function SettingsController2($scope) { * $scope.name = "John Smith"; * $scope.contacts = [ * {type:'phone', value:'408 555 1212'}, * {type:'email', value:'john.smith@example.org'} ]; @@ -29747,32 +29958,32 @@ * </file> * <file name="protractor.js" type="protractor"> * it('should check controller', function() { * var container = element(by.id('ctrl-exmpl')); * - * expect(container.findElement(by.model('name')) + * expect(container.element(by.model('name')) * .getAttribute('value')).toBe('John Smith'); * * var firstRepeat = - * container.findElement(by.repeater('contact in contacts').row(0)); + * container.element(by.repeater('contact in contacts').row(0)); * var secondRepeat = - * container.findElement(by.repeater('contact in contacts').row(1)); + * container.element(by.repeater('contact in contacts').row(1)); * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) * .toBe('408 555 1212'); - * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) * .toBe('john.smith@example.org'); * - * firstRepeat.findElement(by.linkText('clear')).click(); + * firstRepeat.element(by.linkText('clear')).click(); * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) + * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) * .toBe(''); * - * container.findElement(by.linkText('add')).click(); + * container.element(by.linkText('add')).click(); * - * expect(container.findElement(by.repeater('contact in contacts').row(2)) - * .findElement(by.model('contact.value')) + * expect(container.element(by.repeater('contact in contacts').row(2)) + * .element(by.model('contact.value')) * .getAttribute('value')) * .toBe('yourname@example.org'); * }); * </file> *</example> @@ -30139,25 +30350,26 @@ * @priority 0 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. * ({@link guide/expression#-event- Event object is available as `$event`}) * * @example - <example> + <example module="submitExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.list = []; - $scope.text = 'hello'; - $scope.submit = function() { - if ($scope.text) { - $scope.list.push(this.text); - $scope.text = ''; - } - }; - } + angular.module('submitExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.list = []; + $scope.text = 'hello'; + $scope.submit = function() { + if ($scope.text) { + $scope.list.push(this.text); + $scope.text = ''; + } + }; + }]); </script> - <form ng-submit="submit()" ng-controller="Ctrl"> + <form ng-submit="submit()" ng-controller="ExampleController"> Enter text and hit enter: <input type="text" ng-model="text" name="text" /> <input type="submit" id="submit" value="Submit" /> <pre>list={{list}}</pre> </form> @@ -30165,11 +30377,11 @@ <file name="protractor.js" type="protractor"> it('should check ng-submit', function() { expect(element(by.binding('list')).getText()).toBe('list=[]'); element(by.css('#submit')).click(); expect(element(by.binding('list')).getText()).toContain('hello'); - expect(element(by.input('text')).getAttribute('value')).toBe(''); + expect(element(by.model('text')).getAttribute('value')).toBe(''); }); it('should ignore empty strings', function() { expect(element(by.binding('list')).getText()).toBe('list=[]'); element(by.css('#submit')).click(); element(by.css('#submit')).click(); @@ -30438,13 +30650,13 @@ * - If the attribute is not set, disable scrolling. * - If the attribute is set without value, enable scrolling. * - Otherwise enable scrolling only if the expression evaluates to truthy value. * * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> + <example module="includeExample" deps="angular-animate.js" animations="true"> <file name="index.html"> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <select ng-model="template" ng-options="t.name for t in templates"> <option value="">(blank)</option> </select> url of the template: <tt>{{template.url}}</tt> <hr/> @@ -30452,16 +30664,17 @@ <div class="slide-animate" ng-include="template.url"></div> </div> </div> </file> <file name="script.js"> - function Ctrl($scope) { - $scope.templates = - [ { name: 'template1.html', url: 'template1.html'}, - { name: 'template2.html', url: 'template2.html'} ]; - $scope.template = $scope.templates[0]; - } + angular.module('includeExample', ['ngAnimate']) + .controller('ExampleController', ['$scope', function($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'}, + { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + }]); </file> <file name="template1.html"> Content of template1.html </file> <file name="template2.html"> @@ -30520,21 +30733,21 @@ // Firefox can't handle using selects // See https://github.com/angular/protractor/issues/480 return; } templateSelect.click(); - templateSelect.element.all(by.css('option')).get(2).click(); + templateSelect.all(by.css('option')).get(2).click(); expect(includeElem.getText()).toMatch(/Content of template2.html/); }); it('should change to blank', function() { if (browser.params.browser == 'firefox') { // Firefox can't handle using selects return; } templateSelect.click(); - templateSelect.element.all(by.css('option')).get(0).click(); + templateSelect.all(by.css('option')).get(0).click(); expect(includeElem.isPresent()).toBe(false); }); </file> </example> */ @@ -30558,12 +30771,11 @@ */ /** * @ngdoc event - * @name ng.directive:ngInclude#$includeContentError - * @eventOf ng.directive:ngInclude + * @name ngInclude#$includeContentError * @eventType emit on the scope ngInclude was declared in * @description * Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299) */ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', @@ -30695,18 +30907,19 @@ * * @element ANY * @param {expression} ngInit {@link guide/expression Expression} to eval. * * @example - <example> + <example module="initExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.list = [['a', 'b'], ['c', 'd']]; - } + angular.module('initExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.list = [['a', 'b'], ['c', 'd']]; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> </div> </div> @@ -30855,20 +31068,21 @@ * @param {string|expression} count The variable to be bound to. * @param {string} when The mapping between plural category to its corresponding strings. * @param {number=} offset Offset to deduct from the total number. * * @example - <example> + <example module="pluralizeExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.person1 = 'Igor'; - $scope.person2 = 'Misko'; - $scope.personCount = 1; - } + angular.module('pluralizeExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.person1 = 'Igor'; + $scope.person2 = 'Misko'; + $scope.personCount = 1; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> Number of People:<input type="text" ng-model="personCount" value="1" /><br/> <!--- Example with simple pluralization rules for en locale ---> @@ -31796,13 +32010,13 @@ * are multiple default cases, all of them will be displayed when no other * case match. * * * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> + <example module="switchExample" deps="angular-animate.js" animations="true"> <file name="index.html"> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <select ng-model="selection" ng-options="item for item in items"> </select> <tt>selection={{selection}}</tt> <hr/> <div class="animate-switch-container" @@ -31812,14 +32026,15 @@ <div class="animate-switch" ng-switch-default>default</div> </div> </div> </file> <file name="script.js"> - function Ctrl($scope) { - $scope.items = ['settings', 'home', 'other']; - $scope.selection = $scope.items[0]; - } + angular.module('switchExample', ['ngAnimate']) + .controller('ExampleController', ['$scope', function($scope) { + $scope.items = ['settings', 'home', 'other']; + $scope.selection = $scope.items[0]; + }]); </file> <file name="animations.css"> .animate-switch-container { position:relative; background:white; @@ -31858,15 +32073,15 @@ it('should start in settings', function() { expect(switchElem.getText()).toMatch(/Settings Div/); }); it('should change to home', function() { - select.element.all(by.css('option')).get(1).click(); + select.all(by.css('option')).get(1).click(); expect(switchElem.getText()).toMatch(/Home Span/); }); it('should select default', function() { - select.element.all(by.css('option')).get(2).click(); + select.all(by.css('option')).get(2).click(); expect(switchElem.getText()).toMatch(/default/); }); </file> </example> */ @@ -31954,32 +32169,31 @@ * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. * * @element ANY * * @example - <example module="transclude"> + <example module="transcludeExample"> <file name="index.html"> <script> - function Ctrl($scope) { - $scope.title = 'Lorem Ipsum'; - $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; - } - - angular.module('transclude', []) + angular.module('transcludeExample', []) .directive('pane', function(){ return { restrict: 'E', transclude: true, scope: { title:'@' }, template: '<div style="border: 1px solid black;">' + '<div style="background-color: gray">{{title}}</div>' + '<div ng-transclude></div>' + '</div>' }; - }); + }) + .controller('ExampleController', ['$scope', function($scope) { + $scope.title = 'Lorem Ipsum'; + $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; + }]); </script> - <div ng-controller="Ctrl"> + <div ng-controller="ExampleController"> <input ng-model="title"><br> <textarea ng-model="text"></textarea> <br/> <pane title="{{title}}">{{text}}</pane> </div> </file> @@ -32134,25 +32348,26 @@ * * `trackexpr`: Used when working with an array of objects. The result of this expression will be * used to identify the objects in the array. The `trackexpr` will most likely refer to the * `value` variable (e.g. `value.propertyName`). * * @example - <example> + <example module="selectExample"> <file name="index.html"> <script> - function MyCntrl($scope) { - $scope.colors = [ - {name:'black', shade:'dark'}, - {name:'white', shade:'light'}, - {name:'red', shade:'dark'}, - {name:'blue', shade:'dark'}, - {name:'yellow', shade:'light'} - ]; - $scope.myColor = $scope.colors[2]; // red - } + angular.module('selectExample', []) + .controller('ExampleController', ['$scope', function($scope) { + $scope.colors = [ + {name:'black', shade:'dark'}, + {name:'white', shade:'light'}, + {name:'red', shade:'dark'}, + {name:'blue', shade:'dark'}, + {name:'yellow', shade:'light'} + ]; + $scope.myColor = $scope.colors[2]; // red + }]); </script> - <div ng-controller="MyCntrl"> + <div ng-controller="ExampleController"> <ul> <li ng-repeat="color in colors"> Name: <input ng-model="color.name"> [<a href ng-click="colors.splice($index, 1)">X</a>] </li> @@ -32185,11 +32400,11 @@ </div> </file> <file name="protractor.js" type="protractor"> it('should check ng-options', function() { expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); - element.all(by.select('myColor')).first().click(); + element.all(by.model('myColor')).first().click(); element.all(by.css('select[ng-model="myColor"] option')).first().click(); expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); element(by.css('.nullable select[ng-model="myColor"]')).click(); element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); @@ -32225,18 +32440,24 @@ nullOption = nullOption_; unknownOption = unknownOption_; }; - self.addOption = function(value) { + self.addOption = function(value, element) { assertNotHasOwnProperty(value, '"option value"'); optionsMap[value] = true; if (ngModelCtrl.$viewValue == value) { $element.val(value); if (unknownOption.parent()) unknownOption.remove(); } + // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 + // Adding an <option selected="selected"> element to a <select required="required"> should + // automatically select the new element + if (element[0].hasAttribute('selected')) { + element[0].selected = true; + } }; self.removeOption = function(value) { if (this.hasOption(value)) { @@ -32686,13 +32907,13 @@ scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { attr.$set('value', newVal); if (oldVal !== newVal) { selectCtrl.removeOption(oldVal); } - selectCtrl.addOption(newVal); + selectCtrl.addOption(newVal, element); }); } else { - selectCtrl.addOption(attr.value); + selectCtrl.addOption(attr.value, element); } element.on('$destroy', function() { selectCtrl.removeOption(attr.value); }); \ No newline at end of file