angular-rails-engine 1.2.5.0 → 1.2.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -1
  5. data/app/assets/javascripts/angular/angular-animate.js +461 -195
  6. data/app/assets/javascripts/angular/angular-animate.min.js +23 -18
  7. data/app/assets/javascripts/angular/angular-cookies.js +1 -1
  8. data/app/assets/javascripts/angular/angular-cookies.min.js +2 -1
  9. data/app/assets/javascripts/angular/angular-loader.js +2 -2
  10. data/app/assets/javascripts/angular/angular-loader.min.js +3 -2
  11. data/app/assets/javascripts/angular/angular-mocks.js +54 -34
  12. data/app/assets/javascripts/angular/angular-resource.js +36 -5
  13. data/app/assets/javascripts/angular/angular-resource.min.js +9 -8
  14. data/app/assets/javascripts/angular/angular-route.js +25 -15
  15. data/app/assets/javascripts/angular/angular-route.min.js +10 -9
  16. data/app/assets/javascripts/angular/angular-sanitize.js +35 -32
  17. data/app/assets/javascripts/angular/angular-sanitize.min.js +3 -2
  18. data/app/assets/javascripts/angular/angular-scenario.js +1472 -966
  19. data/app/assets/javascripts/angular/angular-touch.js +1 -1
  20. data/app/assets/javascripts/angular/angular-touch.min.js +2 -1
  21. data/app/assets/javascripts/angular/angular.js +1470 -965
  22. data/app/assets/javascripts/angular/angular.min.js +200 -196
  23. data/app/assets/stylesheets/angular/angular-csp.css +18 -0
  24. data/gem-public_cert.pem +11 -10
  25. data/lib/angular-rails-engine.rb +1 -1
  26. data/lib/angular-rails-engine/version.rb +1 -1
  27. metadata +14 -13
  28. metadata.gz.sig +0 -0
  29. data/app/assets/stylesheets/angular-csp.css +0 -24
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.2.5
2
+ * @license AngularJS v1.2.13
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*
2
- AngularJS v1.2.5
2
+ AngularJS v1.2.13
3
3
  (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  License: MIT
5
5
  */
@@ -10,3 +10,4 @@ a.touches&&a.touches.length?a.touches:[a],b=c[0].clientX,c=c[0].clientY;1>b&&1>c
10
10
  function(a){q=!0;s=a.target?a.target:a.srcElement;3==s.nodeType&&(s=s.parentNode);c.addClass(p);t=Date.now();a=a.touches&&a.touches.length?a.touches:[a];a=a[0].originalEvent||a[0];w=a.clientX;x=a.clientY});c.on("touchmove",function(a){f()});c.on("touchcancel",function(a){f()});c.on("touchend",function(a){var h=Date.now()-t,e=a.changedTouches&&a.changedTouches.length?a.changedTouches:a.touches&&a.touches.length?a.touches:[a],g=e[0].originalEvent||e[0],e=g.clientX,g=g.clientY,p=Math.sqrt(Math.pow(e-
11
11
  w,2)+Math.pow(g-x,2));q&&(750>h&&12>p)&&(k||(b[0].addEventListener("click",n,!0),b[0].addEventListener("touchstart",r,!0),k=[]),m=Date.now(),l(k,e,g),s&&s.blur(),v.isDefined(d.disabled)&&!1!==d.disabled||c.triggerHandler("click",[a]));f()});c.onclick=function(a){};c.on("click",function(b,c){a.$apply(function(){h(a,{$event:c||b})})});c.on("mousedown",function(a){c.addClass(p)});c.on("mousemove mouseup",function(a){c.removeClass(p)})}}]);t("ngSwipeLeft",-1,"swipeleft");t("ngSwipeRight",1,"swiperight")})(window,
12
12
  window.angular);
13
+ //# sourceMappingURL=angular-touch.min.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.2.5
2
+ * @license AngularJS v1.2.13
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -68,7 +68,7 @@ function minErr(module) {
68
68
  return match;
69
69
  });
70
70
 
71
- message = message + '\nhttp://errors.angularjs.org/1.2.5/' +
71
+ message = message + '\nhttp://errors.angularjs.org/1.2.13/' +
72
72
  (module ? module + '/' : '') + code;
73
73
  for (i = 2; i < arguments.length; i++) {
74
74
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -160,6 +160,7 @@ function minErr(module) {
160
160
  -assertNotHasOwnProperty,
161
161
  -getter,
162
162
  -getBlockElements,
163
+ -hasOwnProperty,
163
164
 
164
165
  */
165
166
 
@@ -175,7 +176,7 @@ function minErr(module) {
175
176
  * @returns {string} Lowercased string.
176
177
  */
177
178
  var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
178
-
179
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
179
180
 
180
181
  /**
181
182
  * @ngdoc function
@@ -271,7 +272,8 @@ function isArrayLike(obj) {
271
272
  * is the value of an object property or an array element and `key` is the object property key or
272
273
  * array element index. Specifying a `context` for the function is optional.
273
274
  *
274
- * Note: this function was previously known as `angular.foreach`.
275
+ * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
276
+ * using the `hasOwnProperty` method.
275
277
  *
276
278
  <pre>
277
279
  var values = {name: 'misko', gender: 'male'};
@@ -279,7 +281,7 @@ function isArrayLike(obj) {
279
281
  angular.forEach(values, function(value, key){
280
282
  this.push(key + ': ' + value);
281
283
  }, log);
282
- expect(log).toEqual(['name: misko', 'gender:male']);
284
+ expect(log).toEqual(['name: misko', 'gender: male']);
283
285
  </pre>
284
286
  *
285
287
  * @param {Object|Array} obj Object to iterate over.
@@ -292,7 +294,9 @@ function forEach(obj, iterator, context) {
292
294
  if (obj) {
293
295
  if (isFunction(obj)){
294
296
  for (key in obj) {
295
- if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
297
+ // Need to check if hasOwnProperty exists,
298
+ // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
299
+ if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
296
300
  iterator.call(context, obj[key], key);
297
301
  }
298
302
  }
@@ -848,7 +852,7 @@ function shallowCopy(src, dst) {
848
852
  for(var key in src) {
849
853
  // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
850
854
  // so we don't need to worry about using our custom hasOwnProperty here
851
- if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
855
+ if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
852
856
  dst[key] = src[key];
853
857
  }
854
858
  }
@@ -1036,7 +1040,9 @@ function fromJson(json) {
1036
1040
 
1037
1041
 
1038
1042
  function toBoolean(value) {
1039
- if (value && value.length !== 0) {
1043
+ if (typeof value === 'function') {
1044
+ value = true;
1045
+ } else if (value && value.length !== 0) {
1040
1046
  var v = lowercase("" + value);
1041
1047
  value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
1042
1048
  } else {
@@ -1205,6 +1211,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
1205
1211
  <file name="index.html">
1206
1212
  <div ng-controller="ngAppDemoController">
1207
1213
  I can add: {{a}} + {{b}} = {{ a+b }}
1214
+ </div>
1208
1215
  </file>
1209
1216
  <file name="script.js">
1210
1217
  angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
@@ -1829,11 +1836,11 @@ function setupModuleLoader(window) {
1829
1836
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
1830
1837
  */
1831
1838
  var version = {
1832
- full: '1.2.5', // all of these placeholder strings will be replaced by grunt's
1839
+ full: '1.2.13', // all of these placeholder strings will be replaced by grunt's
1833
1840
  major: 1, // package task
1834
1841
  minor: 2,
1835
- dot: 5,
1836
- codeName: 'singularity-expansion'
1842
+ dot: 13,
1843
+ codeName: 'romantic-transclusion'
1837
1844
  };
1838
1845
 
1839
1846
 
@@ -1995,7 +2002,7 @@ function publishExternalAPI(angular){
1995
2002
  * - [`after()`](http://api.jquery.com/after/)
1996
2003
  * - [`append()`](http://api.jquery.com/append/)
1997
2004
  * - [`attr()`](http://api.jquery.com/attr/)
1998
- * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2005
+ * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
1999
2006
  * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2000
2007
  * - [`clone()`](http://api.jquery.com/clone/)
2001
2008
  * - [`contents()`](http://api.jquery.com/contents/)
@@ -2009,6 +2016,7 @@ function publishExternalAPI(angular){
2009
2016
  * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2010
2017
  * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2011
2018
  * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
2019
+ * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2012
2020
  * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2013
2021
  * - [`prepend()`](http://api.jquery.com/prepend/)
2014
2022
  * - [`prop()`](http://api.jquery.com/prop/)
@@ -2021,7 +2029,7 @@ function publishExternalAPI(angular){
2021
2029
  * - [`text()`](http://api.jquery.com/text/)
2022
2030
  * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2023
2031
  * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2024
- * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces
2032
+ * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
2025
2033
  * - [`val()`](http://api.jquery.com/val/)
2026
2034
  * - [`wrap()`](http://api.jquery.com/wrap/)
2027
2035
  *
@@ -2061,6 +2069,14 @@ var jqCache = JQLite.cache = {},
2061
2069
  ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
2062
2070
  : function(element, type, fn) {element.detachEvent('on' + type, fn); });
2063
2071
 
2072
+ /*
2073
+ * !!! This is an undocumented "private" function !!!
2074
+ */
2075
+ var jqData = JQLite._data = function(node) {
2076
+ //jQuery always returns an object on cache miss
2077
+ return this.cache[node[this.expando]] || {};
2078
+ };
2079
+
2064
2080
  function jqNextId() { return ++jqId; }
2065
2081
 
2066
2082
 
@@ -2129,6 +2145,9 @@ function JQLite(element) {
2129
2145
  if (element instanceof JQLite) {
2130
2146
  return element;
2131
2147
  }
2148
+ if (isString(element)) {
2149
+ element = trim(element);
2150
+ }
2132
2151
  if (!(this instanceof JQLite)) {
2133
2152
  if (isString(element) && element.charAt(0) != '<') {
2134
2153
  throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
@@ -2600,7 +2619,10 @@ function createEventHandler(element, events) {
2600
2619
  return event.defaultPrevented || event.returnValue === false;
2601
2620
  };
2602
2621
 
2603
- forEach(events[type || event.type], function(fn) {
2622
+ // Copy event handlers in case event handlers array is modified during execution.
2623
+ var eventHandlersCopy = shallowCopy(events[type || event.type] || []);
2624
+
2625
+ forEach(eventHandlersCopy, function(fn) {
2604
2626
  fn.call(element, event);
2605
2627
  });
2606
2628
 
@@ -2696,6 +2718,19 @@ forEach({
2696
2718
 
2697
2719
  off: jqLiteOff,
2698
2720
 
2721
+ one: function(element, type, fn) {
2722
+ element = jqLite(element);
2723
+
2724
+ //add the listener twice so that when it is called
2725
+ //you can remove the original function and still be
2726
+ //able to call element.off(ev, fn) normally
2727
+ element.on(type, function onFn() {
2728
+ element.off(type, fn);
2729
+ element.off(type, onFn);
2730
+ });
2731
+ element.on(type, fn);
2732
+ },
2733
+
2699
2734
  replaceWith: function(element, replaceNode) {
2700
2735
  var index, parent = element.parentNode;
2701
2736
  jqLiteDealoc(element);
@@ -3258,11 +3293,9 @@ function annotate(fn) {
3258
3293
  * @param {(Object|function())} provider If the provider is:
3259
3294
  *
3260
3295
  * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
3261
- * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be
3262
- * created.
3263
- * - `Constructor`: a new instance of the provider will be created using
3264
- * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as
3265
- * `object`.
3296
+ * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
3297
+ * - `Constructor`: a new instance of the provider will be created using
3298
+ * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
3266
3299
  *
3267
3300
  * @returns {Object} registered provider instance
3268
3301
 
@@ -3378,7 +3411,7 @@ function annotate(fn) {
3378
3411
  * constructor function that will be used to instantiate the service instance.
3379
3412
  *
3380
3413
  * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service
3381
- * as a type/class. This is common when using {@link http://coffeescript.org CoffeeScript}.
3414
+ * as a type/class.
3382
3415
  *
3383
3416
  * @param {string} name The name of the instance.
3384
3417
  * @param {Function} constructor A class (constructor function) that will be instantiated.
@@ -3386,20 +3419,24 @@ function annotate(fn) {
3386
3419
  *
3387
3420
  * @example
3388
3421
  * Here is an example of registering a service using
3389
- * {@link AUTO.$provide#methods_service $provide.service(class)} that is defined as a CoffeeScript class.
3422
+ * {@link AUTO.$provide#methods_service $provide.service(class)}.
3390
3423
  * <pre>
3391
- * class Ping
3392
- * constructor: (@$http)->
3393
- * send: ()=>
3394
- * @$http.get('/ping')
3395
- *
3396
- * $provide.service('ping', ['$http', Ping])
3424
+ * var Ping = function($http) {
3425
+ * this.$http = $http;
3426
+ * };
3427
+ *
3428
+ * Ping.$inject = ['$http'];
3429
+ *
3430
+ * Ping.prototype.send = function() {
3431
+ * return this.$http.get('/ping');
3432
+ * };
3433
+ * $provide.service('ping', Ping);
3397
3434
  * </pre>
3398
3435
  * You would then inject and use this service like this:
3399
3436
  * <pre>
3400
- * someModule.controller 'Ctrl', ['ping', (ping)->
3401
- * ping.send()
3402
- * ]
3437
+ * someModule.controller('Ctrl', ['ping', function(ping) {
3438
+ * ping.send();
3439
+ * }]);
3403
3440
  * </pre>
3404
3441
  */
3405
3442
 
@@ -3491,7 +3528,7 @@ function annotate(fn) {
3491
3528
  * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
3492
3529
  * calls to {@link ng.$log#error $log.warn()}.
3493
3530
  * <pre>
3494
- * $provider.decorator('$log', ['$delegate', function($delegate) {
3531
+ * $provide.decorator('$log', ['$delegate', function($delegate) {
3495
3532
  * $delegate.warn = $delegate.error;
3496
3533
  * return $delegate;
3497
3534
  * }]);
@@ -3644,6 +3681,11 @@ function createInjector(modulesToLoad) {
3644
3681
  path.unshift(serviceName);
3645
3682
  cache[serviceName] = INSTANTIATING;
3646
3683
  return cache[serviceName] = factory(serviceName);
3684
+ } catch (err) {
3685
+ if (cache[serviceName] === INSTANTIATING) {
3686
+ delete cache[serviceName];
3687
+ }
3688
+ throw err;
3647
3689
  } finally {
3648
3690
  path.shift();
3649
3691
  }
@@ -3864,6 +3906,28 @@ var $AnimateProvider = ['$provide', function($provide) {
3864
3906
  $provide.factory(key, factory);
3865
3907
  };
3866
3908
 
3909
+ /**
3910
+ * @ngdoc function
3911
+ * @name ng.$animateProvider#classNameFilter
3912
+ * @methodOf ng.$animateProvider
3913
+ *
3914
+ * @description
3915
+ * Sets and/or returns the CSS class regular expression that is checked when performing
3916
+ * an animation. Upon bootstrap the classNameFilter value is not set at all and will
3917
+ * therefore enable $animate to attempt to perform an animation on any element.
3918
+ * When setting the classNameFilter value, animations will only be performed on elements
3919
+ * that successfully match the filter expression. This in turn can boost performance
3920
+ * for low-powered devices as well as applications containing a lot of structural operations.
3921
+ * @param {RegExp=} expression The className expression which will be checked against all animations
3922
+ * @return {RegExp} The current CSS className expression value. If null then there is no expression value
3923
+ */
3924
+ this.classNameFilter = function(expression) {
3925
+ if(arguments.length === 1) {
3926
+ this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
3927
+ }
3928
+ return this.$$classNameFilter;
3929
+ };
3930
+
3867
3931
  this.$get = ['$timeout', function($timeout) {
3868
3932
 
3869
3933
  /**
@@ -4003,6 +4067,29 @@ var $AnimateProvider = ['$provide', function($provide) {
4003
4067
  done && $timeout(done, 0, false);
4004
4068
  },
4005
4069
 
4070
+ /**
4071
+ *
4072
+ * @ngdoc function
4073
+ * @name ng.$animate#setClass
4074
+ * @methodOf ng.$animate
4075
+ * @function
4076
+ * @description Adds and/or removes the given CSS classes to and from the element.
4077
+ * Once complete, the done() callback will be fired (if provided).
4078
+ * @param {jQuery/jqLite element} element the element which will it's CSS classes changed
4079
+ * removed from it
4080
+ * @param {string} add the CSS classes which will be added to the element
4081
+ * @param {string} remove the CSS class which will be removed from the element
4082
+ * @param {function=} done the callback function (if provided) that will be fired after the
4083
+ * CSS classes have been set on the element
4084
+ */
4085
+ setClass : function(element, add, remove, done) {
4086
+ forEach(element, function (element) {
4087
+ jqLiteAddClass(element, add);
4088
+ jqLiteRemoveClass(element, remove);
4089
+ });
4090
+ done && $timeout(done, 0, false);
4091
+ },
4092
+
4006
4093
  enabled : noop
4007
4094
  };
4008
4095
  }];
@@ -4156,8 +4243,9 @@ function Browser(window, document, $log, $sniffer) {
4156
4243
  * @param {boolean=} replace Should new url replace current history record ?
4157
4244
  */
4158
4245
  self.url = function(url, replace) {
4159
- // Android Browser BFCache causes location reference to become stale.
4246
+ // Android Browser BFCache causes location, history reference to become stale.
4160
4247
  if (location !== window.location) location = window.location;
4248
+ if (history !== window.history) history = window.history;
4161
4249
 
4162
4250
  // setter
4163
4251
  if (url) {
@@ -4209,7 +4297,7 @@ function Browser(window, document, $log, $sniffer) {
4209
4297
  * @description
4210
4298
  * Register callback function that will be called, when url changes.
4211
4299
  *
4212
- * It's only called when the url is changed by outside of angular:
4300
+ * It's only called when the url is changed from outside of angular:
4213
4301
  * - user types different url into address bar
4214
4302
  * - user clicks on history (forward/back) button
4215
4303
  * - user clicks on a link
@@ -4251,7 +4339,7 @@ function Browser(window, document, $log, $sniffer) {
4251
4339
  /**
4252
4340
  * @name ng.$browser#baseHref
4253
4341
  * @methodOf ng.$browser
4254
- *
4342
+ *
4255
4343
  * @description
4256
4344
  * Returns current <base href>
4257
4345
  * (always relative - without domain)
@@ -4260,7 +4348,7 @@ function Browser(window, document, $log, $sniffer) {
4260
4348
  */
4261
4349
  self.baseHref = function() {
4262
4350
  var href = baseElement.attr('href');
4263
- return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
4351
+ return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
4264
4352
  };
4265
4353
 
4266
4354
  //////////////////////////////////////////////////////////////
@@ -4282,13 +4370,13 @@ function Browser(window, document, $log, $sniffer) {
4282
4370
  * It is not meant to be used directly, use the $cookie service instead.
4283
4371
  *
4284
4372
  * The return values vary depending on the arguments that the method was called with as follows:
4285
- *
4373
+ *
4286
4374
  * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
4287
4375
  * it
4288
4376
  * - cookies(name, value) -> set name to value, if value is undefined delete the cookie
4289
4377
  * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
4290
4378
  * way)
4291
- *
4379
+ *
4292
4380
  * @returns {Object} Hash of all cookies (if called without any parameter)
4293
4381
  */
4294
4382
  self.cookies = function(name, value) {
@@ -4666,7 +4754,7 @@ function $TemplateCacheProvider() {
4666
4754
  * @function
4667
4755
  *
4668
4756
  * @description
4669
- * Compiles a piece of HTML string or DOM into a template and produces a template function, which
4757
+ * Compiles an HTML string or DOM into a template and produces a template function, which
4670
4758
  * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
4671
4759
  *
4672
4760
  * The compilation is a process of walking the DOM tree and matching DOM elements to
@@ -5066,13 +5154,17 @@ function $TemplateCacheProvider() {
5066
5154
  <div compile="html"></div>
5067
5155
  </div>
5068
5156
  </doc:source>
5069
- <doc:scenario>
5157
+ <doc:protractor>
5070
5158
  it('should auto compile', function() {
5071
- expect(element('div[compile]').text()).toBe('Hello Angular');
5072
- input('html').enter('{{name}}!');
5073
- expect(element('div[compile]').text()).toBe('Angular!');
5159
+ var textarea = $('textarea');
5160
+ var output = $('div[compile]');
5161
+ // The initial state reads 'Hello Angular'.
5162
+ expect(output.getText()).toBe('Hello Angular');
5163
+ textarea.clear();
5164
+ textarea.sendKeys('{{name}}!');
5165
+ expect(output.getText()).toBe('Angular!');
5074
5166
  });
5075
- </doc:scenario>
5167
+ </doc:protractor>
5076
5168
  </doc:example>
5077
5169
 
5078
5170
  *
@@ -5111,14 +5203,14 @@ function $TemplateCacheProvider() {
5111
5203
  * example would not point to the clone, but rather to the original template that was cloned. In
5112
5204
  * this case, you can access the clone via the cloneAttachFn:
5113
5205
  * <pre>
5114
- * var templateHTML = angular.element('<p>{{total}}</p>'),
5206
+ * var templateElement = angular.element('<p>{{total}}</p>'),
5115
5207
  * scope = ....;
5116
5208
  *
5117
- * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
5209
+ * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
5118
5210
  * //attach the clone to DOM document at the right place
5119
5211
  * });
5120
5212
  *
5121
- * //now we have reference to the cloned DOM via `clone`
5213
+ * //now we have reference to the cloned DOM via `clonedElement`
5122
5214
  * </pre>
5123
5215
  *
5124
5216
  *
@@ -5140,7 +5232,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5140
5232
  var hasDirectives = {},
5141
5233
  Suffix = 'Directive',
5142
5234
  COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
5143
- CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
5235
+ CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
5236
+ TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|tbody)(\s+[^>]*)?>/i;
5144
5237
 
5145
5238
  // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
5146
5239
  // The assumption is that future DOM event attribute names will begin with
@@ -5327,8 +5420,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5327
5420
  * @param {string} oldClasses The former CSS className value
5328
5421
  */
5329
5422
  $updateClass : function(newClasses, oldClasses) {
5330
- this.$removeClass(tokenDifference(oldClasses, newClasses));
5331
- this.$addClass(tokenDifference(newClasses, oldClasses));
5423
+ var toAdd = tokenDifference(newClasses, oldClasses);
5424
+ var toRemove = tokenDifference(oldClasses, newClasses);
5425
+
5426
+ if(toAdd.length === 0) {
5427
+ $animate.removeClass(this.$$element, toRemove);
5428
+ } else if(toRemove.length === 0) {
5429
+ $animate.addClass(this.$$element, toAdd);
5430
+ } else {
5431
+ $animate.setClass(this.$$element, toAdd, toRemove);
5432
+ }
5332
5433
  },
5333
5434
 
5334
5435
  /**
@@ -5460,6 +5561,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5460
5561
  var compositeLinkFn =
5461
5562
  compileNodes($compileNodes, transcludeFn, $compileNodes,
5462
5563
  maxPriority, ignoreDirective, previousCompileContext);
5564
+ safeAddClass($compileNodes, 'ng-scope');
5463
5565
  return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
5464
5566
  assertArg(scope, 'scope');
5465
5567
  // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
@@ -5474,12 +5576,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5474
5576
 
5475
5577
  // Attach scope only to non-text nodes.
5476
5578
  for(var i = 0, ii = $linkNode.length; i<ii; i++) {
5477
- var node = $linkNode[i];
5478
- if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
5579
+ var node = $linkNode[i],
5580
+ nodeType = node.nodeType;
5581
+ if (nodeType === 1 /* element */ || nodeType === 9 /* document */) {
5479
5582
  $linkNode.eq(i).data('$scope', scope);
5480
5583
  }
5481
5584
  }
5482
- safeAddClass($linkNode, 'ng-scope');
5585
+
5483
5586
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
5484
5587
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
5485
5588
  return $linkNode;
@@ -5507,15 +5610,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5507
5610
  * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
5508
5611
  * the rootElement must be set the jqLite collection of the compile root. This is
5509
5612
  * needed so that the jqLite collection items can be replaced with widgets.
5510
- * @param {number=} max directive priority
5613
+ * @param {number=} maxPriority Max directive priority.
5511
5614
  * @returns {?function} A composite linking function of all of the matched directives or null.
5512
5615
  */
5513
5616
  function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
5514
5617
  previousCompileContext) {
5515
5618
  var linkFns = [],
5516
- nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
5619
+ attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound;
5517
5620
 
5518
- for(var i = 0; i < nodeList.length; i++) {
5621
+ for (var i = 0; i < nodeList.length; i++) {
5519
5622
  attrs = new Attributes();
5520
5623
 
5521
5624
  // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
@@ -5527,16 +5630,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5527
5630
  null, [], [], previousCompileContext)
5528
5631
  : null;
5529
5632
 
5633
+ if (nodeLinkFn && nodeLinkFn.scope) {
5634
+ safeAddClass(jqLite(nodeList[i]), 'ng-scope');
5635
+ }
5636
+
5530
5637
  childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
5531
- !nodeList[i].childNodes ||
5532
- !nodeList[i].childNodes.length)
5638
+ !(childNodes = nodeList[i].childNodes) ||
5639
+ !childNodes.length)
5533
5640
  ? null
5534
- : compileNodes(nodeList[i].childNodes,
5641
+ : compileNodes(childNodes,
5535
5642
  nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
5536
5643
 
5537
- linkFns.push(nodeLinkFn);
5538
- linkFns.push(childLinkFn);
5539
- linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
5644
+ linkFns.push(nodeLinkFn, childLinkFn);
5645
+ linkFnFound = linkFnFound || nodeLinkFn || childLinkFn;
5540
5646
  //use the previous context only for the first element in the virtual group
5541
5647
  previousCompileContext = null;
5542
5648
  }
@@ -5548,9 +5654,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5548
5654
  var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n;
5549
5655
 
5550
5656
  // copy nodeList so that linking doesn't break due to live list updates.
5551
- var stableNodeList = [];
5552
- for (i = 0, ii = nodeList.length; i < ii; i++) {
5553
- stableNodeList.push(nodeList[i]);
5657
+ var nodeListLength = nodeList.length,
5658
+ stableNodeList = new Array(nodeListLength);
5659
+ for (i = 0; i < nodeListLength; i++) {
5660
+ stableNodeList[i] = nodeList[i];
5554
5661
  }
5555
5662
 
5556
5663
  for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
@@ -5563,7 +5670,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5563
5670
  if (nodeLinkFn.scope) {
5564
5671
  childScope = scope.$new();
5565
5672
  $node.data('$scope', childScope);
5566
- safeAddClass($node, 'ng-scope');
5567
5673
  } else {
5568
5674
  childScope = scope;
5569
5675
  }
@@ -5646,9 +5752,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5646
5752
 
5647
5753
  nName = directiveNormalize(name.toLowerCase());
5648
5754
  attrsMap[nName] = name;
5649
- attrs[nName] = value = trim((msie && name == 'href')
5650
- ? decodeURIComponent(node.getAttribute(name, 2))
5651
- : attr.value);
5755
+ attrs[nName] = value = trim(attr.value);
5652
5756
  if (getBooleanAttrName(node, nName)) {
5653
5757
  attrs[nName] = true; // presence means true
5654
5758
  }
@@ -5777,7 +5881,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5777
5881
  templateDirective = previousCompileContext.templateDirective,
5778
5882
  nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
5779
5883
  hasTranscludeDirective = false,
5780
- hasElementTranscludeDirective = false,
5884
+ hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
5781
5885
  $compileNode = templateAttrs.$$element = jqLite(compileNode),
5782
5886
  directive,
5783
5887
  directiveName,
@@ -5831,7 +5935,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5831
5935
  hasTranscludeDirective = true;
5832
5936
 
5833
5937
  // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
5834
- // This option should only be used by directives that know how to how to safely handle element transclusion,
5938
+ // This option should only be used by directives that know how to safely handle element transclusion,
5835
5939
  // where the transcluded nodes are added or replaced after linking.
5836
5940
  if (!directive.$$tlb) {
5837
5941
  assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
@@ -5878,9 +5982,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5878
5982
 
5879
5983
  if (directive.replace) {
5880
5984
  replaceDirective = directive;
5881
- $template = jqLite('<div>' +
5882
- trim(directiveValue) +
5883
- '</div>').contents();
5985
+ $template = directiveTemplateContents(directiveValue);
5884
5986
  compileNode = $template[0];
5885
5987
 
5886
5988
  if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -5951,6 +6053,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
5951
6053
 
5952
6054
  nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
5953
6055
  nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
6056
+ previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
5954
6057
 
5955
6058
  // might be normal or delayed nodeLinkFn depending on if templateUrl is present
5956
6059
  return nodeLinkFn;
@@ -6278,6 +6381,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6278
6381
  }
6279
6382
 
6280
6383
 
6384
+ function directiveTemplateContents(template) {
6385
+ var type;
6386
+ template = trim(template);
6387
+ if ((type = TABLE_CONTENT_REGEXP.exec(template))) {
6388
+ type = type[1].toLowerCase();
6389
+ var table = jqLite('<table>' + template + '</table>'),
6390
+ tbody = table.children('tbody'),
6391
+ leaf = /(td|th)/.test(type) && table.find('tr');
6392
+ if (tbody.length && type !== 'tbody') {
6393
+ table = tbody;
6394
+ }
6395
+ if (leaf && leaf.length) {
6396
+ table = leaf;
6397
+ }
6398
+ return table.contents();
6399
+ }
6400
+ return jqLite('<div>' +
6401
+ template +
6402
+ '</div>').contents();
6403
+ }
6404
+
6405
+
6281
6406
  function compileTemplateUrl(directives, $compileNode, tAttrs,
6282
6407
  $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
6283
6408
  var linkQueue = [],
@@ -6302,7 +6427,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6302
6427
  content = denormalizeTemplate(content);
6303
6428
 
6304
6429
  if (origAsyncDirective.replace) {
6305
- $template = jqLite('<div>' + trim(content) + '</div>').contents();
6430
+ $template = directiveTemplateContents(content);
6306
6431
  compileNode = $template[0];
6307
6432
 
6308
6433
  if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -6346,9 +6471,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
6346
6471
  linkNode = $compileNode[0];
6347
6472
 
6348
6473
  if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
6349
- // it was cloned therefore we have to clone as well.
6350
- linkNode = jqLiteClone(compileNode);
6474
+ var oldClasses = beforeTemplateLinkNode.className;
6475
+
6476
+ if (!(previousCompileContext.hasElementTranscludeDirective &&
6477
+ origAsyncDirective.replace)) {
6478
+ // it was cloned therefore we have to clone as well.
6479
+ linkNode = jqLiteClone(compileNode);
6480
+ }
6481
+
6351
6482
  replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
6483
+
6484
+ // Copy in CSS classes from original node
6485
+ safeAddClass(jqLite(linkNode), oldClasses);
6352
6486
  }
6353
6487
  if (afterTemplateNodeLinkFn.transclude) {
6354
6488
  childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
@@ -6596,7 +6730,7 @@ function directiveNormalize(name) {
6596
6730
  *
6597
6731
  *
6598
6732
  * @param {string} name Normalized element attribute name of the property to modify. The name is
6599
- * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
6733
+ * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
6600
6734
  * property to the original name.
6601
6735
  * @param {string} value Value to set the attribute to. The value can be an interpolated string.
6602
6736
  */
@@ -6734,8 +6868,7 @@ function $ControllerProvider() {
6734
6868
  * @requires $window
6735
6869
  *
6736
6870
  * @description
6737
- * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
6738
- * element.
6871
+ * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
6739
6872
  */
6740
6873
  function $DocumentProvider(){
6741
6874
  this.$get = ['$window', function(window){
@@ -6894,9 +7027,9 @@ function $HttpProvider() {
6894
7027
  common: {
6895
7028
  'Accept': 'application/json, text/plain, */*'
6896
7029
  },
6897
- post: CONTENT_TYPE_APPLICATION_JSON,
6898
- put: CONTENT_TYPE_APPLICATION_JSON,
6899
- patch: CONTENT_TYPE_APPLICATION_JSON
7030
+ post: copy(CONTENT_TYPE_APPLICATION_JSON),
7031
+ put: copy(CONTENT_TYPE_APPLICATION_JSON),
7032
+ patch: copy(CONTENT_TYPE_APPLICATION_JSON)
6900
7033
  },
6901
7034
 
6902
7035
  xsrfCookieName: 'XSRF-TOKEN',
@@ -7005,32 +7138,15 @@ function $HttpProvider() {
7005
7138
  * will result in the success callback being called. Note that if the response is a redirect,
7006
7139
  * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
7007
7140
  * called for such responses.
7008
- *
7009
- * # Calling $http from outside AngularJS
7010
- * The `$http` service will not actually send the request until the next `$digest()` is
7011
- * executed. Normally this is not an issue, since almost all the time your call to `$http` will
7012
- * be from within a `$apply()` block.
7013
- * If you are calling `$http` from outside Angular, then you should wrap it in a call to
7014
- * `$apply` to cause a $digest to occur and also to handle errors in the block correctly.
7015
- *
7016
- * ```
7017
- * $scope.$apply(function() {
7018
- * $http(...);
7019
- * });
7020
- * ```
7021
7141
  *
7022
7142
  * # Writing Unit Tests that use $http
7023
- * When unit testing you are mostly responsible for scheduling the `$digest` cycle. If you do
7024
- * not trigger a `$digest` before calling `$httpBackend.flush()` then the request will not have
7025
- * been made and `$httpBackend.expect(...)` expectations will fail. The solution is to run the
7026
- * code that calls the `$http()` method inside a $apply block as explained in the previous
7027
- * section.
7143
+ * When unit testing (using {@link api/ngMock ngMock}), it is necessary to call
7144
+ * {@link api/ngMock.$httpBackend#methods_flush $httpBackend.flush()} to flush each pending
7145
+ * request using trained responses.
7028
7146
  *
7029
7147
  * ```
7030
7148
  * $httpBackend.expectGET(...);
7031
- * $scope.$apply(function() {
7032
- * $http.get(...);
7033
- * });
7149
+ * $http.get(...);
7034
7150
  * $httpBackend.flush();
7035
7151
  * ```
7036
7152
  *
@@ -7074,7 +7190,15 @@ function $HttpProvider() {
7074
7190
  * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
7075
7191
  *
7076
7192
  * The defaults can also be set at runtime via the `$http.defaults` object in the same
7077
- * fashion. In addition, you can supply a `headers` property in the config object passed when
7193
+ * fashion. For example:
7194
+ *
7195
+ * ```
7196
+ * module.run(function($http) {
7197
+ * $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w'
7198
+ * });
7199
+ * ```
7200
+ *
7201
+ * In addition, you can supply a `headers` property in the config object passed when
7078
7202
  * calling `$http(config)`, which overrides the defaults without changing them globally.
7079
7203
  *
7080
7204
  *
@@ -7098,7 +7222,9 @@ function $HttpProvider() {
7098
7222
  * properties. These properties are by default an array of transform functions, which allows you
7099
7223
  * to `push` or `unshift` a new transformation function into the transformation chain. You can
7100
7224
  * also decide to completely override any default transformations by assigning your
7101
- * transformation functions to these properties directly without the array wrapper.
7225
+ * transformation functions to these properties directly without the array wrapper. These defaults
7226
+ * are again available on the $http factory at run-time, which may be useful if you have run-time
7227
+ * services you wish to be involved in your transformations.
7102
7228
  *
7103
7229
  * Similarly, to locally override the request/response transforms, augment the
7104
7230
  * `transformRequest` and/or `transformResponse` properties of the configuration object passed
@@ -7192,19 +7318,20 @@ function $HttpProvider() {
7192
7318
  * return responseOrNewPromise
7193
7319
  * }
7194
7320
  * return $q.reject(rejection);
7195
- * };
7196
- * }
7321
+ * }
7322
+ * };
7197
7323
  * });
7198
7324
  *
7199
7325
  * $httpProvider.interceptors.push('myHttpInterceptor');
7200
7326
  *
7201
7327
  *
7202
- * // register the interceptor via an anonymous factory
7328
+ * // alternatively, register the interceptor via an anonymous factory
7203
7329
  * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
7204
7330
  * return {
7205
7331
  * 'request': function(config) {
7206
7332
  * // same as above
7207
7333
  * },
7334
+ *
7208
7335
  * 'response': function(response) {
7209
7336
  * // same as above
7210
7337
  * }
@@ -7311,7 +7438,8 @@ function $HttpProvider() {
7311
7438
  * for added security.
7312
7439
  *
7313
7440
  * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
7314
- * properties of either $httpProvider.defaults, or the per-request config object.
7441
+ * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
7442
+ * or the per-request config object.
7315
7443
  *
7316
7444
  *
7317
7445
  * @param {object} config Object describing the request to be made and how it should be
@@ -7375,14 +7503,14 @@ function $HttpProvider() {
7375
7503
  <option>JSONP</option>
7376
7504
  </select>
7377
7505
  <input type="text" ng-model="url" size="80"/>
7378
- <button ng-click="fetch()">fetch</button><br>
7379
- <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
7380
- <button
7506
+ <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
7507
+ <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
7508
+ <button id="samplejsonpbtn"
7381
7509
  ng-click="updateModel('JSONP',
7382
7510
  'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
7383
7511
  Sample JSONP
7384
7512
  </button>
7385
- <button
7513
+ <button id="invalidjsonpbtn"
7386
7514
  ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
7387
7515
  Invalid JSONP
7388
7516
  </button>
@@ -7419,27 +7547,34 @@ function $HttpProvider() {
7419
7547
  <file name="http-hello.html">
7420
7548
  Hello, $http!
7421
7549
  </file>
7422
- <file name="scenario.js">
7550
+ <file name="protractorTest.js">
7551
+ var status = element(by.binding('status'));
7552
+ var data = element(by.binding('data'));
7553
+ var fetchBtn = element(by.id('fetchbtn'));
7554
+ var sampleGetBtn = element(by.id('samplegetbtn'));
7555
+ var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
7556
+ var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
7557
+
7423
7558
  it('should make an xhr GET request', function() {
7424
- element(':button:contains("Sample GET")').click();
7425
- element(':button:contains("fetch")').click();
7426
- expect(binding('status')).toBe('200');
7427
- expect(binding('data')).toMatch(/Hello, \$http!/);
7559
+ sampleGetBtn.click();
7560
+ fetchBtn.click();
7561
+ expect(status.getText()).toMatch('200');
7562
+ expect(data.getText()).toMatch(/Hello, \$http!/)
7428
7563
  });
7429
7564
 
7430
7565
  it('should make a JSONP request to angularjs.org', function() {
7431
- element(':button:contains("Sample JSONP")').click();
7432
- element(':button:contains("fetch")').click();
7433
- expect(binding('status')).toBe('200');
7434
- expect(binding('data')).toMatch(/Super Hero!/);
7566
+ sampleJsonpBtn.click();
7567
+ fetchBtn.click();
7568
+ expect(status.getText()).toMatch('200');
7569
+ expect(data.getText()).toMatch(/Super Hero!/);
7435
7570
  });
7436
7571
 
7437
7572
  it('should make JSONP request to invalid URL and invoke the error handler',
7438
7573
  function() {
7439
- element(':button:contains("Invalid JSONP")').click();
7440
- element(':button:contains("fetch")').click();
7441
- expect(binding('status')).toBe('0');
7442
- expect(binding('data')).toBe('Request failed');
7574
+ invalidJsonpBtn.click();
7575
+ fetchBtn.click();
7576
+ expect(status.getText()).toMatch('0');
7577
+ expect(data.getText()).toMatch('Request failed');
7443
7578
  });
7444
7579
  </file>
7445
7580
  </example>
@@ -7820,14 +7955,19 @@ function $HttpProvider() {
7820
7955
  }];
7821
7956
  }
7822
7957
 
7823
- var XHR = window.XMLHttpRequest || function() {
7824
- /* global ActiveXObject */
7825
- try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
7826
- try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
7827
- try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
7828
- throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
7829
- };
7958
+ function createXhr(method) {
7959
+ //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
7960
+ //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
7961
+ //if it is available
7962
+ if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
7963
+ !window.XMLHttpRequest)) {
7964
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
7965
+ } else if (window.XMLHttpRequest) {
7966
+ return new window.XMLHttpRequest();
7967
+ }
7830
7968
 
7969
+ throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
7970
+ }
7831
7971
 
7832
7972
  /**
7833
7973
  * @ngdoc object
@@ -7848,11 +7988,11 @@ var XHR = window.XMLHttpRequest || function() {
7848
7988
  */
7849
7989
  function $HttpBackendProvider() {
7850
7990
  this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
7851
- return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, $document[0]);
7991
+ return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]);
7852
7992
  }];
7853
7993
  }
7854
7994
 
7855
- function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument) {
7995
+ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
7856
7996
  var ABORTED = -1;
7857
7997
 
7858
7998
  // TODO(vojta): fix the signature
@@ -7874,10 +8014,12 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
7874
8014
  } else {
7875
8015
  completeRequest(callback, status || -2);
7876
8016
  }
7877
- delete callbacks[callbackId];
8017
+ callbacks[callbackId] = angular.noop;
7878
8018
  });
7879
8019
  } else {
7880
- var xhr = new XHR();
8020
+
8021
+ var xhr = createXhr(method);
8022
+
7881
8023
  xhr.open(method, url, true);
7882
8024
  forEach(headers, function(value, key) {
7883
8025
  if (isDefined(value)) {
@@ -7889,17 +8031,25 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
7889
8031
  // response is in the cache. the promise api will ensure that to the app code the api is
7890
8032
  // always async
7891
8033
  xhr.onreadystatechange = function() {
7892
- if (xhr.readyState == 4) {
8034
+ // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
8035
+ // xhrs that are resolved while the app is in the background (see #5426).
8036
+ // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
8037
+ // continuing
8038
+ //
8039
+ // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
8040
+ // Safari respectively.
8041
+ if (xhr && xhr.readyState == 4) {
7893
8042
  var responseHeaders = null,
7894
8043
  response = null;
7895
8044
 
7896
8045
  if(status !== ABORTED) {
7897
8046
  responseHeaders = xhr.getAllResponseHeaders();
7898
- response = xhr.responseType ? xhr.response : xhr.responseText;
8047
+
8048
+ // responseText is the old-school way of retrieving response (supported by IE8 & 9)
8049
+ // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
8050
+ response = ('response' in xhr) ? xhr.response : xhr.responseText;
7899
8051
  }
7900
8052
 
7901
- // responseText is the old-school way of retrieving response (supported by IE8 & 9)
7902
- // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
7903
8053
  completeRequest(callback,
7904
8054
  status || xhr.status,
7905
8055
  response,
@@ -7912,7 +8062,20 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
7912
8062
  }
7913
8063
 
7914
8064
  if (responseType) {
7915
- xhr.responseType = responseType;
8065
+ try {
8066
+ xhr.responseType = responseType;
8067
+ } catch (e) {
8068
+ // WebKit added support for the json responseType value on 09/03/2013
8069
+ // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
8070
+ // known to throw when setting the value "json" as the response type. Other older
8071
+ // browsers implementing the responseType
8072
+ //
8073
+ // The json response type can be ignored if not supported, because JSON payloads are
8074
+ // parsed on the client-side regardless.
8075
+ if (responseType !== 'json') {
8076
+ throw e;
8077
+ }
8078
+ }
7916
8079
  }
7917
8080
 
7918
8081
  xhr.send(post || null);
@@ -7932,14 +8095,14 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
7932
8095
  }
7933
8096
 
7934
8097
  function completeRequest(callback, status, response, headersString) {
7935
- var protocol = urlResolve(url).protocol;
7936
-
7937
8098
  // cancel timeout and subsequent timeout promise resolution
7938
8099
  timeoutId && $browserDefer.cancel(timeoutId);
7939
8100
  jsonpDone = xhr = null;
7940
8101
 
7941
- // fix status code for file protocol (it's always 0)
7942
- status = (protocol == 'file' && status === 0) ? (response ? 200 : 404) : status;
8102
+ // fix status code when it is 0 (0 status is undocumented).
8103
+ // Occurs when accessing file resources.
8104
+ // On Android 4.1 stock browser it occurs while retrieving files from application cache.
8105
+ status = (status === 0) ? (response ? 200 : 404) : status;
7943
8106
 
7944
8107
  // normalize IE bug (http://bugs.jquery.com/ticket/1450)
7945
8108
  status = status == 1223 ? 204 : status;
@@ -8011,11 +8174,11 @@ var $interpolateMinErr = minErr('$interpolate');
8011
8174
  //demo.label//
8012
8175
  </div>
8013
8176
  </doc:source>
8014
- <doc:scenario>
8015
- it('should interpolate binding with custom symbols', function() {
8016
- expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.');
8017
- });
8018
- </doc:scenario>
8177
+ <doc:protractor>
8178
+ it('should interpolate binding with custom symbols', function() {
8179
+ expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
8180
+ });
8181
+ </doc:protractor>
8019
8182
  </doc:example>
8020
8183
  */
8021
8184
  function $InterpolateProvider() {
@@ -8207,7 +8370,7 @@ function $InterpolateProvider() {
8207
8370
  * @description
8208
8371
  * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
8209
8372
  *
8210
- * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
8373
+ * Use {@link ng.$interpolateProvider#methods_endSymbol $interpolateProvider#endSymbol} to change
8211
8374
  * the symbol.
8212
8375
  *
8213
8376
  * @returns {string} start symbol.
@@ -8244,6 +8407,14 @@ function $IntervalProvider() {
8244
8407
  * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
8245
8408
  * time.
8246
8409
  *
8410
+ * <div class="alert alert-warning">
8411
+ * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
8412
+ * with them. In particular they are not automatically destroyed when a controller's scope or a
8413
+ * directive's element are destroyed.
8414
+ * You should take this into consideration and make sure to always cancel the interval at the
8415
+ * appropriate moment. See the example below for more details on how and when to do this.
8416
+ * </div>
8417
+ *
8247
8418
  * @param {function()} fn A function that should be called repeatedly.
8248
8419
  * @param {number} delay Number of milliseconds between each function call.
8249
8420
  * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
@@ -8251,6 +8422,95 @@ function $IntervalProvider() {
8251
8422
  * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
8252
8423
  * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block.
8253
8424
  * @returns {promise} A promise which will be notified on each iteration.
8425
+ *
8426
+ * @example
8427
+ <doc:example module="time">
8428
+ <doc:source>
8429
+ <script>
8430
+ function Ctrl2($scope,$interval) {
8431
+ $scope.format = 'M/d/yy h:mm:ss a';
8432
+ $scope.blood_1 = 100;
8433
+ $scope.blood_2 = 120;
8434
+
8435
+ var stop;
8436
+ $scope.fight = function() {
8437
+ // Don't start a new fight if we are already fighting
8438
+ if ( angular.isDefined(stop) ) return;
8439
+
8440
+ stop = $interval(function() {
8441
+ if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
8442
+ $scope.blood_1 = $scope.blood_1 - 3;
8443
+ $scope.blood_2 = $scope.blood_2 - 4;
8444
+ } else {
8445
+ $scope.stopFight();
8446
+ }
8447
+ }, 100);
8448
+ };
8449
+
8450
+ $scope.stopFight = function() {
8451
+ if (angular.isDefined(stop)) {
8452
+ $interval.cancel(stop);
8453
+ stop = undefined;
8454
+ }
8455
+ };
8456
+
8457
+ $scope.resetFight = function() {
8458
+ $scope.blood_1 = 100;
8459
+ $scope.blood_2 = 120;
8460
+ }
8461
+
8462
+ $scope.$on('$destroy', function() {
8463
+ // Make sure that the interval is destroyed too
8464
+ $scope.stopFight();
8465
+ });
8466
+ }
8467
+
8468
+ angular.module('time', [])
8469
+ // Register the 'myCurrentTime' directive factory method.
8470
+ // We inject $interval and dateFilter service since the factory method is DI.
8471
+ .directive('myCurrentTime', function($interval, dateFilter) {
8472
+ // return the directive link function. (compile function not needed)
8473
+ return function(scope, element, attrs) {
8474
+ var format, // date format
8475
+ stopTime; // so that we can cancel the time updates
8476
+
8477
+ // used to update the UI
8478
+ function updateTime() {
8479
+ element.text(dateFilter(new Date(), format));
8480
+ }
8481
+
8482
+ // watch the expression, and update the UI on change.
8483
+ scope.$watch(attrs.myCurrentTime, function(value) {
8484
+ format = value;
8485
+ updateTime();
8486
+ });
8487
+
8488
+ stopTime = $interval(updateTime, 1000);
8489
+
8490
+ // listen on DOM destroy (removal) event, and cancel the next UI update
8491
+ // to prevent updating time ofter the DOM element was removed.
8492
+ element.bind('$destroy', function() {
8493
+ $interval.cancel(stopTime);
8494
+ });
8495
+ }
8496
+ });
8497
+ </script>
8498
+
8499
+ <div>
8500
+ <div ng-controller="Ctrl2">
8501
+ Date format: <input ng-model="format"> <hr/>
8502
+ Current time is: <span my-current-time="format"></span>
8503
+ <hr/>
8504
+ Blood 1 : <font color='red'>{{blood_1}}</font>
8505
+ Blood 2 : <font color='red'>{{blood_2}}</font>
8506
+ <button type="button" data-ng-click="fight()">Fight</button>
8507
+ <button type="button" data-ng-click="stopFight()">StopFight</button>
8508
+ <button type="button" data-ng-click="resetFight()">resetFight</button>
8509
+ </div>
8510
+ </div>
8511
+
8512
+ </doc:source>
8513
+ </doc:example>
8254
8514
  */
8255
8515
  function interval(fn, delay, count, invokeApply) {
8256
8516
  var setInterval = $window.setInterval,
@@ -8259,8 +8519,8 @@ function $IntervalProvider() {
8259
8519
  promise = deferred.promise,
8260
8520
  iteration = 0,
8261
8521
  skipApply = (isDefined(invokeApply) && !invokeApply);
8262
-
8263
- count = isDefined(count) ? count : 0,
8522
+
8523
+ count = isDefined(count) ? count : 0;
8264
8524
 
8265
8525
  promise.then(null, null, fn);
8266
8526
 
@@ -8954,9 +9214,9 @@ function $LocationProvider(){
8954
9214
  * @eventType broadcast on root scope
8955
9215
  * @description
8956
9216
  * Broadcasted before a URL will change. This change can be prevented by calling
8957
- * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
9217
+ * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#methods_$on} for more
8958
9218
  * details about event object. Upon successful change
8959
- * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
9219
+ * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
8960
9220
  *
8961
9221
  * @param {Object} angularEvent Synthetic event object.
8962
9222
  * @param {string} newUrl New URL
@@ -9009,6 +9269,13 @@ function $LocationProvider(){
9009
9269
  }
9010
9270
 
9011
9271
  var absHref = elm.prop('href');
9272
+
9273
+ if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
9274
+ // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
9275
+ // an animation.
9276
+ absHref = urlResolve(absHref.animVal).href;
9277
+ }
9278
+
9012
9279
  var rewrittenUrl = $location.$$rewrite(absHref);
9013
9280
 
9014
9281
  if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
@@ -9032,16 +9299,17 @@ function $LocationProvider(){
9032
9299
  // update $location when $browser url changes
9033
9300
  $browser.onUrlChange(function(newUrl) {
9034
9301
  if ($location.absUrl() != newUrl) {
9035
- if ($rootScope.$broadcast('$locationChangeStart', newUrl,
9036
- $location.absUrl()).defaultPrevented) {
9037
- $browser.url($location.absUrl());
9038
- return;
9039
- }
9040
9302
  $rootScope.$evalAsync(function() {
9041
9303
  var oldUrl = $location.absUrl();
9042
9304
 
9043
9305
  $location.$$parse(newUrl);
9044
- afterLocationChange(oldUrl);
9306
+ if ($rootScope.$broadcast('$locationChangeStart', newUrl,
9307
+ oldUrl).defaultPrevented) {
9308
+ $location.$$parse(oldUrl);
9309
+ $browser.url(oldUrl);
9310
+ } else {
9311
+ afterLocationChange(oldUrl);
9312
+ }
9045
9313
  });
9046
9314
  if (!$rootScope.$$phase) $rootScope.$digest();
9047
9315
  }
@@ -9129,7 +9397,7 @@ function $LogProvider(){
9129
9397
  * @name ng.$logProvider#debugEnabled
9130
9398
  * @methodOf ng.$logProvider
9131
9399
  * @description
9132
- * @param {string=} flag enable or disable debug level messages
9400
+ * @param {boolean=} flag enable or disable debug level messages
9133
9401
  * @returns {*} current value if used as getter or itself (chaining) if used as setter
9134
9402
  */
9135
9403
  this.debugEnabled = function(flag) {
@@ -9217,9 +9485,16 @@ function $LogProvider(){
9217
9485
 
9218
9486
  function consoleLog(type) {
9219
9487
  var console = $window.console || {},
9220
- logFn = console[type] || console.log || noop;
9488
+ logFn = console[type] || console.log || noop,
9489
+ hasApply = false;
9490
+
9491
+ // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
9492
+ // The reason behind this is that console.log has type "object" in IE8...
9493
+ try {
9494
+ hasApply = !! logFn.apply;
9495
+ } catch (e) {}
9221
9496
 
9222
- if (logFn.apply) {
9497
+ if (hasApply) {
9223
9498
  return function() {
9224
9499
  var args = [];
9225
9500
  forEach(arguments, function(arg) {
@@ -9945,7 +10220,7 @@ Parser.prototype = {
9945
10220
  var getter = getterFn(field, this.options, this.text);
9946
10221
 
9947
10222
  return extend(function(scope, locals, self) {
9948
- return getter(self || object(scope, locals), locals);
10223
+ return getter(self || object(scope, locals));
9949
10224
  }, {
9950
10225
  assign: function(scope, value, locals) {
9951
10226
  return setter(object(scope, locals), field, value, parser.text, parser.options);
@@ -10129,19 +10404,23 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10129
10404
  ? function cspSafeGetter(scope, locals) {
10130
10405
  var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
10131
10406
 
10132
- if (pathVal === null || pathVal === undefined) return pathVal;
10407
+ if (pathVal == null) return pathVal;
10133
10408
  pathVal = pathVal[key0];
10134
10409
 
10135
- if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
10410
+ if (!key1) return pathVal;
10411
+ if (pathVal == null) return undefined;
10136
10412
  pathVal = pathVal[key1];
10137
10413
 
10138
- if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
10414
+ if (!key2) return pathVal;
10415
+ if (pathVal == null) return undefined;
10139
10416
  pathVal = pathVal[key2];
10140
10417
 
10141
- if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
10418
+ if (!key3) return pathVal;
10419
+ if (pathVal == null) return undefined;
10142
10420
  pathVal = pathVal[key3];
10143
10421
 
10144
- if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
10422
+ if (!key4) return pathVal;
10423
+ if (pathVal == null) return undefined;
10145
10424
  pathVal = pathVal[key4];
10146
10425
 
10147
10426
  return pathVal;
@@ -10150,7 +10429,7 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10150
10429
  var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
10151
10430
  promise;
10152
10431
 
10153
- if (pathVal === null || pathVal === undefined) return pathVal;
10432
+ if (pathVal == null) return pathVal;
10154
10433
 
10155
10434
  pathVal = pathVal[key0];
10156
10435
  if (pathVal && pathVal.then) {
@@ -10162,8 +10441,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10162
10441
  }
10163
10442
  pathVal = pathVal.$$v;
10164
10443
  }
10165
- if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
10166
10444
 
10445
+ if (!key1) return pathVal;
10446
+ if (pathVal == null) return undefined;
10167
10447
  pathVal = pathVal[key1];
10168
10448
  if (pathVal && pathVal.then) {
10169
10449
  promiseWarning(fullExp);
@@ -10174,8 +10454,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10174
10454
  }
10175
10455
  pathVal = pathVal.$$v;
10176
10456
  }
10177
- if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
10178
10457
 
10458
+ if (!key2) return pathVal;
10459
+ if (pathVal == null) return undefined;
10179
10460
  pathVal = pathVal[key2];
10180
10461
  if (pathVal && pathVal.then) {
10181
10462
  promiseWarning(fullExp);
@@ -10186,8 +10467,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10186
10467
  }
10187
10468
  pathVal = pathVal.$$v;
10188
10469
  }
10189
- if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
10190
10470
 
10471
+ if (!key3) return pathVal;
10472
+ if (pathVal == null) return undefined;
10191
10473
  pathVal = pathVal[key3];
10192
10474
  if (pathVal && pathVal.then) {
10193
10475
  promiseWarning(fullExp);
@@ -10198,8 +10480,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10198
10480
  }
10199
10481
  pathVal = pathVal.$$v;
10200
10482
  }
10201
- if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
10202
10483
 
10484
+ if (!key4) return pathVal;
10485
+ if (pathVal == null) return undefined;
10203
10486
  pathVal = pathVal[key4];
10204
10487
  if (pathVal && pathVal.then) {
10205
10488
  promiseWarning(fullExp);
@@ -10214,6 +10497,26 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
10214
10497
  };
10215
10498
  }
10216
10499
 
10500
+ function simpleGetterFn1(key0, fullExp) {
10501
+ ensureSafeMemberName(key0, fullExp);
10502
+
10503
+ return function simpleGetterFn1(scope, locals) {
10504
+ if (scope == null) return undefined;
10505
+ return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
10506
+ };
10507
+ }
10508
+
10509
+ function simpleGetterFn2(key0, key1, fullExp) {
10510
+ ensureSafeMemberName(key0, fullExp);
10511
+ ensureSafeMemberName(key1, fullExp);
10512
+
10513
+ return function simpleGetterFn2(scope, locals) {
10514
+ if (scope == null) return undefined;
10515
+ scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
10516
+ return scope == null ? undefined : scope[key1];
10517
+ };
10518
+ }
10519
+
10217
10520
  function getterFn(path, options, fullExp) {
10218
10521
  // Check whether the cache has this getter already.
10219
10522
  // We can use hasOwnProperty directly on the cache because we ensure,
@@ -10226,7 +10529,13 @@ function getterFn(path, options, fullExp) {
10226
10529
  pathKeysLength = pathKeys.length,
10227
10530
  fn;
10228
10531
 
10229
- if (options.csp) {
10532
+ // When we have only 1 or 2 tokens, use optimized special case closures.
10533
+ // http://jsperf.com/angularjs-parse-getter/6
10534
+ if (!options.unwrapPromises && pathKeysLength === 1) {
10535
+ fn = simpleGetterFn1(pathKeys[0], fullExp);
10536
+ } else if (!options.unwrapPromises && pathKeysLength === 2) {
10537
+ fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp);
10538
+ } else if (options.csp) {
10230
10539
  if (pathKeysLength < 6) {
10231
10540
  fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp,
10232
10541
  options);
@@ -10244,11 +10553,10 @@ function getterFn(path, options, fullExp) {
10244
10553
  };
10245
10554
  }
10246
10555
  } else {
10247
- var code = 'var l, fn, p;\n';
10556
+ var code = 'var p;\n';
10248
10557
  forEach(pathKeys, function(key, index) {
10249
10558
  ensureSafeMemberName(key, fullExp);
10250
- code += 'if(s === null || s === undefined) return s;\n' +
10251
- 'l=s;\n' +
10559
+ code += 'if(s == null) return undefined;\n' +
10252
10560
  's='+ (index
10253
10561
  // we simply dereference 's' on any .dot notation
10254
10562
  ? 's'
@@ -10271,10 +10579,10 @@ function getterFn(path, options, fullExp) {
10271
10579
  /* jshint -W054 */
10272
10580
  var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
10273
10581
  /* jshint +W054 */
10274
- evaledFnGetter.toString = function() { return code; };
10275
- fn = function(scope, locals) {
10582
+ evaledFnGetter.toString = valueFn(code);
10583
+ fn = options.unwrapPromises ? function(scope, locals) {
10276
10584
  return evaledFnGetter(scope, locals, promiseWarning);
10277
- };
10585
+ } : evaledFnGetter;
10278
10586
  }
10279
10587
 
10280
10588
  // Only cache the value if it's not going to mess up the cache object
@@ -10488,9 +10796,9 @@ function $ParseProvider() {
10488
10796
  * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
10489
10797
  *
10490
10798
  * <pre>
10491
- * // for the purpose of this example let's assume that variables `$q` and `scope` are
10492
- * // available in the current lexical scope (they could have been injected or passed in).
10493
- *
10799
+ * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet`
10800
+ * // are available in the current lexical scope (they could have been injected or passed in).
10801
+ *
10494
10802
  * function asyncGreet(name) {
10495
10803
  * var deferred = $q.defer();
10496
10804
  *
@@ -10545,7 +10853,7 @@ function $ParseProvider() {
10545
10853
  * constructed via `$q.reject`, the promise will be rejected instead.
10546
10854
  * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
10547
10855
  * resolving it with a rejection constructed via `$q.reject`.
10548
- * - `notify(value)` - provides updates on the status of the promises execution. This may be called
10856
+ * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
10549
10857
  * multiple times before the promise is either resolved or rejected.
10550
10858
  *
10551
10859
  * **Properties**
@@ -10695,7 +11003,7 @@ function qFactory(nextTick, exceptionHandler) {
10695
11003
 
10696
11004
 
10697
11005
  reject: function(reason) {
10698
- deferred.resolve(reject(reason));
11006
+ deferred.resolve(createInternalRejectedPromise(reason));
10699
11007
  },
10700
11008
 
10701
11009
 
@@ -10852,6 +11160,12 @@ function qFactory(nextTick, exceptionHandler) {
10852
11160
  * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
10853
11161
  */
10854
11162
  var reject = function(reason) {
11163
+ var result = defer();
11164
+ result.reject(reason);
11165
+ return result.promise;
11166
+ };
11167
+
11168
+ var createInternalRejectedPromise = function(reason) {
10855
11169
  return {
10856
11170
  then: function(callback, errback) {
10857
11171
  var result = defer();
@@ -11119,6 +11433,7 @@ function $RootScopeProvider(){
11119
11433
  this.$$asyncQueue = [];
11120
11434
  this.$$postDigestQueue = [];
11121
11435
  this.$$listeners = {};
11436
+ this.$$listenerCount = {};
11122
11437
  this.$$isolateBindings = {};
11123
11438
  }
11124
11439
 
@@ -11171,13 +11486,14 @@ function $RootScopeProvider(){
11171
11486
  } else {
11172
11487
  ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
11173
11488
  // the name it does not become random set of chars. This will then show up as class
11174
- // name in the debugger.
11489
+ // name in the web inspector.
11175
11490
  ChildScope.prototype = this;
11176
11491
  child = new ChildScope();
11177
11492
  child.$id = nextUid();
11178
11493
  }
11179
11494
  child['this'] = child;
11180
11495
  child.$$listeners = {};
11496
+ child.$$listenerCount = {};
11181
11497
  child.$parent = this;
11182
11498
  child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
11183
11499
  child.$$prevSibling = this.$$childTail;
@@ -11271,7 +11587,7 @@ function $RootScopeProvider(){
11271
11587
  // No digest has been run so the counter will be zero
11272
11588
  expect(scope.foodCounter).toEqual(0);
11273
11589
 
11274
- // Run the digest but since food has not changed cout will still be zero
11590
+ // Run the digest but since food has not changed count will still be zero
11275
11591
  scope.$digest();
11276
11592
  expect(scope.foodCounter).toEqual(0);
11277
11593
 
@@ -11337,6 +11653,7 @@ function $RootScopeProvider(){
11337
11653
 
11338
11654
  return function() {
11339
11655
  arrayRemove(array, watcher);
11656
+ lastDirtyWatch = null;
11340
11657
  };
11341
11658
  },
11342
11659
 
@@ -11615,7 +11932,7 @@ function $RootScopeProvider(){
11615
11932
 
11616
11933
  // `break traverseScopesLoop;` takes us to here
11617
11934
 
11618
- if(dirty && !(ttl--)) {
11935
+ if((dirty || asyncQueue.length) && !(ttl--)) {
11619
11936
  clearPhase();
11620
11937
  throw $rootScopeMinErr('infdig',
11621
11938
  '{0} $digest() iterations reached. Aborting!\n' +
@@ -11682,6 +11999,8 @@ function $RootScopeProvider(){
11682
11999
  this.$$destroyed = true;
11683
12000
  if (this === $rootScope) return;
11684
12001
 
12002
+ forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
12003
+
11685
12004
  if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
11686
12005
  if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
11687
12006
  if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
@@ -11871,8 +12190,18 @@ function $RootScopeProvider(){
11871
12190
  }
11872
12191
  namedListeners.push(listener);
11873
12192
 
12193
+ var current = this;
12194
+ do {
12195
+ if (!current.$$listenerCount[name]) {
12196
+ current.$$listenerCount[name] = 0;
12197
+ }
12198
+ current.$$listenerCount[name]++;
12199
+ } while ((current = current.$parent));
12200
+
12201
+ var self = this;
11874
12202
  return function() {
11875
12203
  namedListeners[indexOf(namedListeners, listener)] = null;
12204
+ decrementListenerCount(self, 1, name);
11876
12205
  };
11877
12206
  },
11878
12207
 
@@ -11897,7 +12226,7 @@ function $RootScopeProvider(){
11897
12226
  * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
11898
12227
  *
11899
12228
  * @param {string} name Event name to emit.
11900
- * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
12229
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
11901
12230
  * @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}).
11902
12231
  */
11903
12232
  $emit: function(name, args) {
@@ -11965,7 +12294,7 @@ function $RootScopeProvider(){
11965
12294
  * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
11966
12295
  *
11967
12296
  * @param {string} name Event name to broadcast.
11968
- * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
12297
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
11969
12298
  * @return {Object} Event object, see {@link ng.$rootScope.Scope#methods_$on}
11970
12299
  */
11971
12300
  $broadcast: function(name, args) {
@@ -11984,8 +12313,7 @@ function $RootScopeProvider(){
11984
12313
  listeners, i, length;
11985
12314
 
11986
12315
  //down while you can, then up and next sibling or up and next sibling until back at root
11987
- do {
11988
- current = next;
12316
+ while ((current = next)) {
11989
12317
  event.currentScope = current;
11990
12318
  listeners = current.$$listeners[name] || [];
11991
12319
  for (i=0, length = listeners.length; i<length; i++) {
@@ -12007,12 +12335,14 @@ function $RootScopeProvider(){
12007
12335
  // Insanity Warning: scope depth-first traversal
12008
12336
  // yes, this code is a bit crazy, but it works and we have tests to prove it!
12009
12337
  // this piece should be kept in sync with the traversal in $digest
12010
- if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
12338
+ // (though it differs due to having the extra check for $$listenerCount)
12339
+ if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
12340
+ (current !== target && current.$$nextSibling)))) {
12011
12341
  while(current !== target && !(next = current.$$nextSibling)) {
12012
12342
  current = current.$parent;
12013
12343
  }
12014
12344
  }
12015
- } while ((current = next));
12345
+ }
12016
12346
 
12017
12347
  return event;
12018
12348
  }
@@ -12041,6 +12371,16 @@ function $RootScopeProvider(){
12041
12371
  return fn;
12042
12372
  }
12043
12373
 
12374
+ function decrementListenerCount(current, count, name) {
12375
+ do {
12376
+ current.$$listenerCount[name] -= count;
12377
+
12378
+ if (current.$$listenerCount[name] === 0) {
12379
+ delete current.$$listenerCount[name];
12380
+ }
12381
+ } while ((current = current.$parent));
12382
+ }
12383
+
12044
12384
  /**
12045
12385
  * function used as an initial value for watchers.
12046
12386
  * because it's unique we can easily tell it apart from other values
@@ -12397,7 +12737,7 @@ function $SceDelegateProvider() {
12397
12737
  *
12398
12738
  * @description
12399
12739
  * Returns an object that is trusted by angular for use in specified strict
12400
- * contextual escaping contexts (such as ng-html-bind-unsafe, ng-include, any src
12740
+ * contextual escaping contexts (such as ng-bind-html, ng-include, any src
12401
12741
  * attribute interpolation, any dom event binding attribute interpolation
12402
12742
  * such as for onclick, etc.) that uses the provided value.
12403
12743
  * See {@link ng.$sce $sce} for enabling strict contextual escaping.
@@ -12443,7 +12783,7 @@ function $SceDelegateProvider() {
12443
12783
  *
12444
12784
  * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}
12445
12785
  * call or anything else.
12446
- * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs
12786
+ * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#methods_trustAs
12447
12787
  * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
12448
12788
  * `value` unchanged.
12449
12789
  */
@@ -12624,8 +12964,8 @@ function $SceDelegateProvider() {
12624
12964
  * It's important to remember that SCE only applies to interpolation expressions.
12625
12965
  *
12626
12966
  * If your expressions are constant literals, they're automatically trusted and you don't need to
12627
- * call `$sce.trustAs` on them. (e.g.
12628
- * `<div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div>`) just works.
12967
+ * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
12968
+ * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
12629
12969
  *
12630
12970
  * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
12631
12971
  * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here.
@@ -12685,7 +13025,7 @@ function $SceDelegateProvider() {
12685
13025
  * matched against the **entire** *normalized / absolute URL* of the resource being tested
12686
13026
  * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
12687
13027
  * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
12688
- * - If you are generating your Javascript from some other templating engine (not
13028
+ * - If you are generating your JavaScript from some other templating engine (not
12689
13029
  * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
12690
13030
  * remember to escape your regular expression (and be aware that you might need more than
12691
13031
  * one level of escaping depending on your templating engine and the way you interpolated
@@ -12702,7 +13042,7 @@ function $SceDelegateProvider() {
12702
13042
  * ## Show me an example using SCE.
12703
13043
  *
12704
13044
  * @example
12705
- <example module="mySceApp">
13045
+ <example module="mySceApp" deps="angular-sanitize.js">
12706
13046
  <file name="index.html">
12707
13047
  <div ng-controller="myAppController as myCtrl">
12708
13048
  <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
@@ -12746,13 +13086,15 @@ function $SceDelegateProvider() {
12746
13086
  ]
12747
13087
  </file>
12748
13088
 
12749
- <file name="scenario.js">
13089
+ <file name="protractorTest.js">
12750
13090
  describe('SCE doc demo', function() {
12751
13091
  it('should sanitize untrusted values', function() {
12752
- expect(element('.htmlComment').html()).toBe('<span>Is <i>anyone</i> reading this?</span>');
13092
+ expect(element(by.css('.htmlComment')).getInnerHtml())
13093
+ .toBe('<span>Is <i>anyone</i> reading this?</span>');
12753
13094
  });
13095
+
12754
13096
  it('should NOT sanitize explicitly trusted values', function() {
12755
- expect(element('#explicitlyTrustedHtml').html()).toBe(
13097
+ expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
12756
13098
  '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
12757
13099
  'sanitization.&quot;">Hover over this text.</span>');
12758
13100
  });
@@ -12927,8 +13269,8 @@ function $SceProvider() {
12927
13269
  *
12928
13270
  * @description
12929
13271
  * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such,
12930
- * returns an objectthat is trusted by angular for use in specified strict contextual
12931
- * escaping contexts (such as ng-html-bind-unsafe, ng-include, any src attribute
13272
+ * returns an object that is trusted by angular for use in specified strict contextual
13273
+ * escaping contexts (such as ng-bind-html, ng-include, any src attribute
12932
13274
  * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
12933
13275
  * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
12934
13276
  * escaping.
@@ -13259,7 +13601,7 @@ function $SnifferProvider() {
13259
13601
  // http://code.google.com/p/android/issues/detail?id=17471
13260
13602
  // https://github.com/angular/angular.js/issues/904
13261
13603
 
13262
- // older webit browser (533.9) on Boxee box has exactly the same problem as Android has
13604
+ // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
13263
13605
  // so let's not use the history API also
13264
13606
  // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
13265
13607
  // jshint -W018
@@ -13285,6 +13627,7 @@ function $SnifferProvider() {
13285
13627
  vendorPrefix: vendorPrefix,
13286
13628
  transitions : transitions,
13287
13629
  animations : animations,
13630
+ android: android,
13288
13631
  msie : msie,
13289
13632
  msieDocumentMode: documentMode
13290
13633
  };
@@ -13322,113 +13665,26 @@ function $TimeoutProvider() {
13322
13665
  * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
13323
13666
  * promise will be resolved with is the return value of the `fn` function.
13324
13667
  *
13325
- * @example
13326
- <doc:example module="time">
13327
- <doc:source>
13328
- <script>
13329
- function Ctrl2($scope,$timeout) {
13330
- $scope.format = 'M/d/yy h:mm:ss a';
13331
- $scope.blood_1 = 100;
13332
- $scope.blood_2 = 120;
13333
-
13334
- var stop;
13335
- $scope.fight = function() {
13336
- stop = $timeout(function() {
13337
- if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
13338
- $scope.blood_1 = $scope.blood_1 - 3;
13339
- $scope.blood_2 = $scope.blood_2 - 4;
13340
- $scope.fight();
13341
- } else {
13342
- $timeout.cancel(stop);
13343
- }
13344
- }, 100);
13345
- };
13668
+ */
13669
+ function timeout(fn, delay, invokeApply) {
13670
+ var deferred = $q.defer(),
13671
+ promise = deferred.promise,
13672
+ skipApply = (isDefined(invokeApply) && !invokeApply),
13673
+ timeoutId;
13346
13674
 
13347
- $scope.stopFight = function() {
13348
- $timeout.cancel(stop);
13349
- };
13675
+ timeoutId = $browser.defer(function() {
13676
+ try {
13677
+ deferred.resolve(fn());
13678
+ } catch(e) {
13679
+ deferred.reject(e);
13680
+ $exceptionHandler(e);
13681
+ }
13682
+ finally {
13683
+ delete deferreds[promise.$$timeoutId];
13684
+ }
13350
13685
 
13351
- $scope.resetFight = function() {
13352
- $scope.blood_1 = 100;
13353
- $scope.blood_2 = 120;
13354
- }
13355
- }
13356
-
13357
- angular.module('time', [])
13358
- // Register the 'myCurrentTime' directive factory method.
13359
- // We inject $timeout and dateFilter service since the factory method is DI.
13360
- .directive('myCurrentTime', function($timeout, dateFilter) {
13361
- // return the directive link function. (compile function not needed)
13362
- return function(scope, element, attrs) {
13363
- var format, // date format
13364
- timeoutId; // timeoutId, so that we can cancel the time updates
13365
-
13366
- // used to update the UI
13367
- function updateTime() {
13368
- element.text(dateFilter(new Date(), format));
13369
- }
13370
-
13371
- // watch the expression, and update the UI on change.
13372
- scope.$watch(attrs.myCurrentTime, function(value) {
13373
- format = value;
13374
- updateTime();
13375
- });
13376
-
13377
- // schedule update in one second
13378
- function updateLater() {
13379
- // save the timeoutId for canceling
13380
- timeoutId = $timeout(function() {
13381
- updateTime(); // update DOM
13382
- updateLater(); // schedule another update
13383
- }, 1000);
13384
- }
13385
-
13386
- // listen on DOM destroy (removal) event, and cancel the next UI update
13387
- // to prevent updating time ofter the DOM element was removed.
13388
- element.bind('$destroy', function() {
13389
- $timeout.cancel(timeoutId);
13390
- });
13391
-
13392
- updateLater(); // kick off the UI update process.
13393
- }
13394
- });
13395
- </script>
13396
-
13397
- <div>
13398
- <div ng-controller="Ctrl2">
13399
- Date format: <input ng-model="format"> <hr/>
13400
- Current time is: <span my-current-time="format"></span>
13401
- <hr/>
13402
- Blood 1 : <font color='red'>{{blood_1}}</font>
13403
- Blood 2 : <font color='red'>{{blood_2}}</font>
13404
- <button type="button" data-ng-click="fight()">Fight</button>
13405
- <button type="button" data-ng-click="stopFight()">StopFight</button>
13406
- <button type="button" data-ng-click="resetFight()">resetFight</button>
13407
- </div>
13408
- </div>
13409
-
13410
- </doc:source>
13411
- </doc:example>
13412
- */
13413
- function timeout(fn, delay, invokeApply) {
13414
- var deferred = $q.defer(),
13415
- promise = deferred.promise,
13416
- skipApply = (isDefined(invokeApply) && !invokeApply),
13417
- timeoutId;
13418
-
13419
- timeoutId = $browser.defer(function() {
13420
- try {
13421
- deferred.resolve(fn());
13422
- } catch(e) {
13423
- deferred.reject(e);
13424
- $exceptionHandler(e);
13425
- }
13426
- finally {
13427
- delete deferreds[promise.$$timeoutId];
13428
- }
13429
-
13430
- if (!skipApply) $rootScope.$apply();
13431
- }, delay);
13686
+ if (!skipApply) $rootScope.$apply();
13687
+ }, delay);
13432
13688
 
13433
13689
  promise.$$timeoutId = timeoutId;
13434
13690
  deferreds[timeoutId] = deferred;
@@ -13597,13 +13853,13 @@ function urlIsSameOrigin(requestUrl) {
13597
13853
  <button ng-click="doGreeting(greeting)">ALERT</button>
13598
13854
  </div>
13599
13855
  </doc:source>
13600
- <doc:scenario>
13856
+ <doc:protractor>
13601
13857
  it('should display the greeting in the input box', function() {
13602
- input('greeting').enter('Hello, E2E Tests');
13858
+ element(by.model('greeting')).sendKeys('Hello, E2E Tests');
13603
13859
  // If we click the button it will block the test runner
13604
13860
  // element(':button').click();
13605
13861
  });
13606
- </doc:scenario>
13862
+ </doc:protractor>
13607
13863
  </doc:example>
13608
13864
  */
13609
13865
  function $WindowProvider(){
@@ -13756,8 +14012,8 @@ function $FilterProvider($provide) {
13756
14012
  *
13757
14013
  * Can be one of:
13758
14014
  *
13759
- * - `string`: Predicate that results in a substring match using the value of `expression`
13760
- * string. All strings or objects with string properties in `array` that contain this string
14015
+ * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
14016
+ * the contents of the `array`. All strings or objects with string properties in `array` that contain this string
13761
14017
  * will be returned. The predicate can be negated by prefixing the string with `!`.
13762
14018
  *
13763
14019
  * - `Object`: A pattern object can be used to filter specific properties on objects contained
@@ -13767,21 +14023,21 @@ function $FilterProvider($provide) {
13767
14023
  * property of the object. That's equivalent to the simple substring match with a `string`
13768
14024
  * as described above.
13769
14025
  *
13770
- * - `function`: A predicate function can be used to write arbitrary filters. The function is
14026
+ * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is
13771
14027
  * called for each element of `array`. The final result is an array of those elements that
13772
14028
  * the predicate returned true for.
13773
14029
  *
13774
- * @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
14030
+ * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
13775
14031
  * determining if the expected value (from the filter expression) and actual value (from
13776
14032
  * the object in the array) should be considered a match.
13777
14033
  *
13778
14034
  * Can be one of:
13779
14035
  *
13780
- * - `function(expected, actual)`:
14036
+ * - `function(actual, expected)`:
13781
14037
  * The function will be given the object value and the predicate value to compare and
13782
14038
  * should return true if the item should be included in filtered result.
13783
14039
  *
13784
- * - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`.
14040
+ * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
13785
14041
  * this is essentially strict comparison of expected and actual.
13786
14042
  *
13787
14043
  * - `false|undefined`: A short hand for a function which will look for a substring match in case
@@ -13812,35 +14068,47 @@ function $FilterProvider($provide) {
13812
14068
  Equality <input type="checkbox" ng-model="strict"><br>
13813
14069
  <table id="searchObjResults">
13814
14070
  <tr><th>Name</th><th>Phone</th></tr>
13815
- <tr ng-repeat="friend in friends | filter:search:strict">
13816
- <td>{{friend.name}}</td>
13817
- <td>{{friend.phone}}</td>
14071
+ <tr ng-repeat="friendObj in friends | filter:search:strict">
14072
+ <td>{{friendObj.name}}</td>
14073
+ <td>{{friendObj.phone}}</td>
13818
14074
  </tr>
13819
14075
  </table>
13820
14076
  </doc:source>
13821
- <doc:scenario>
13822
- it('should search across all fields when filtering with a string', function() {
13823
- input('searchText').enter('m');
13824
- expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
13825
- toEqual(['Mary', 'Mike', 'Adam']);
14077
+ <doc:protractor>
14078
+ var expectFriendNames = function(expectedNames, key) {
14079
+ element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
14080
+ arr.forEach(function(wd, i) {
14081
+ expect(wd.getText()).toMatch(expectedNames[i]);
14082
+ });
14083
+ });
14084
+ };
13826
14085
 
13827
- input('searchText').enter('76');
13828
- expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
13829
- toEqual(['John', 'Julie']);
14086
+ it('should search across all fields when filtering with a string', function() {
14087
+ var searchText = element(by.model('searchText'));
14088
+ searchText.clear();
14089
+ searchText.sendKeys('m');
14090
+ expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
14091
+
14092
+ searchText.clear();
14093
+ searchText.sendKeys('76');
14094
+ expectFriendNames(['John', 'Julie'], 'friend');
13830
14095
  });
13831
14096
 
13832
14097
  it('should search in specific fields when filtering with a predicate object', function() {
13833
- input('search.$').enter('i');
13834
- expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
13835
- toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
14098
+ var searchAny = element(by.model('search.$'));
14099
+ searchAny.clear();
14100
+ searchAny.sendKeys('i');
14101
+ expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
13836
14102
  });
13837
14103
  it('should use a equal comparison when comparator is true', function() {
13838
- input('search.name').enter('Julie');
13839
- input('strict').check();
13840
- expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
13841
- toEqual(['Julie']);
14104
+ var searchName = element(by.model('search.name'));
14105
+ var strict = element(by.model('strict'));
14106
+ searchName.clear();
14107
+ searchName.sendKeys('Julie');
14108
+ strict.click();
14109
+ expectFriendNames(['Julie'], 'friendObj');
13842
14110
  });
13843
- </doc:scenario>
14111
+ </doc:protractor>
13844
14112
  </doc:example>
13845
14113
  */
13846
14114
  function filterFilter() {
@@ -13866,6 +14134,15 @@ function filterFilter() {
13866
14134
  };
13867
14135
  } else {
13868
14136
  comparator = function(obj, text) {
14137
+ if (obj && text && typeof obj === 'object' && typeof text === 'object') {
14138
+ for (var objKey in obj) {
14139
+ if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
14140
+ comparator(obj[objKey], text[objKey])) {
14141
+ return true;
14142
+ }
14143
+ }
14144
+ return false;
14145
+ }
13869
14146
  text = (''+text).toLowerCase();
13870
14147
  return (''+obj).toLowerCase().indexOf(text) > -1;
13871
14148
  };
@@ -13915,23 +14192,12 @@ function filterFilter() {
13915
14192
  case "object":
13916
14193
  // jshint +W086
13917
14194
  for (var key in expression) {
13918
- if (key == '$') {
13919
- (function() {
13920
- if (!expression[key]) return;
13921
- var path = key;
13922
- predicates.push(function(value) {
13923
- return search(value, expression[path]);
13924
- });
13925
- })();
13926
- } else {
13927
- (function() {
13928
- if (typeof(expression[key]) == 'undefined') { return; }
13929
- var path = key;
13930
- predicates.push(function(value) {
13931
- return search(getter(value,path), expression[path]);
13932
- });
13933
- })();
13934
- }
14195
+ (function(path) {
14196
+ if (typeof expression[path] == 'undefined') return;
14197
+ predicates.push(function(value) {
14198
+ return search(path == '$' ? value : (value && value[path]), expression[path]);
14199
+ });
14200
+ })(key);
13935
14201
  }
13936
14202
  break;
13937
14203
  case 'function':
@@ -13975,21 +14241,27 @@ function filterFilter() {
13975
14241
  </script>
13976
14242
  <div ng-controller="Ctrl">
13977
14243
  <input type="number" ng-model="amount"> <br>
13978
- default currency symbol ($): {{amount | currency}}<br>
13979
- custom currency identifier (USD$): {{amount | currency:"USD$"}}
14244
+ default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
14245
+ custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span>
13980
14246
  </div>
13981
14247
  </doc:source>
13982
- <doc:scenario>
14248
+ <doc:protractor>
13983
14249
  it('should init with 1234.56', function() {
13984
- expect(binding('amount | currency')).toBe('$1,234.56');
13985
- expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
14250
+ expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
14251
+ expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56');
13986
14252
  });
13987
14253
  it('should update', function() {
13988
- input('amount').enter('-1234');
13989
- expect(binding('amount | currency')).toBe('($1,234.00)');
13990
- expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
14254
+ if (browser.params.browser == 'safari') {
14255
+ // Safari does not understand the minus key. See
14256
+ // https://github.com/angular/protractor/issues/481
14257
+ return;
14258
+ }
14259
+ element(by.model('amount')).clear();
14260
+ element(by.model('amount')).sendKeys('-1234');
14261
+ expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
14262
+ expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
13991
14263
  });
13992
- </doc:scenario>
14264
+ </doc:protractor>
13993
14265
  </doc:example>
13994
14266
  */
13995
14267
  currencyFilter.$inject = ['$locale'];
@@ -14028,25 +14300,26 @@ function currencyFilter($locale) {
14028
14300
  </script>
14029
14301
  <div ng-controller="Ctrl">
14030
14302
  Enter number: <input ng-model='val'><br>
14031
- Default formatting: {{val | number}}<br>
14032
- No fractions: {{val | number:0}}<br>
14033
- Negative number: {{-val | number:4}}
14303
+ Default formatting: <span id='number-default'>{{val | number}}</span><br>
14304
+ No fractions: <span>{{val | number:0}}</span><br>
14305
+ Negative number: <span>{{-val | number:4}}</span>
14034
14306
  </div>
14035
14307
  </doc:source>
14036
- <doc:scenario>
14308
+ <doc:protractor>
14037
14309
  it('should format numbers', function() {
14038
- expect(binding('val | number')).toBe('1,234.568');
14039
- expect(binding('val | number:0')).toBe('1,235');
14040
- expect(binding('-val | number:4')).toBe('-1,234.5679');
14310
+ expect(element(by.id('number-default')).getText()).toBe('1,234.568');
14311
+ expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
14312
+ expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
14041
14313
  });
14042
14314
 
14043
14315
  it('should update', function() {
14044
- input('val').enter('3374.333');
14045
- expect(binding('val | number')).toBe('3,374.333');
14046
- expect(binding('val | number:0')).toBe('3,374');
14047
- expect(binding('-val | number:4')).toBe('-3,374.3330');
14048
- });
14049
- </doc:scenario>
14316
+ element(by.model('val')).clear();
14317
+ element(by.model('val')).sendKeys('3374.333');
14318
+ expect(element(by.id('number-default')).getText()).toBe('3,374.333');
14319
+ expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
14320
+ expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
14321
+ });
14322
+ </doc:protractor>
14050
14323
  </doc:example>
14051
14324
  */
14052
14325
 
@@ -14276,22 +14549,22 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
14276
14549
  <doc:example>
14277
14550
  <doc:source>
14278
14551
  <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
14279
- {{1288323623006 | date:'medium'}}<br>
14552
+ <span>{{1288323623006 | date:'medium'}}</span><br>
14280
14553
  <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
14281
- {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
14554
+ <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
14282
14555
  <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
14283
- {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
14556
+ <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
14284
14557
  </doc:source>
14285
- <doc:scenario>
14558
+ <doc:protractor>
14286
14559
  it('should format date', function() {
14287
- expect(binding("1288323623006 | date:'medium'")).
14560
+ expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
14288
14561
  toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
14289
- expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
14562
+ expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
14290
14563
  toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
14291
- expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
14564
+ expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
14292
14565
  toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
14293
14566
  });
14294
- </doc:scenario>
14567
+ </doc:protractor>
14295
14568
  </doc:example>
14296
14569
  */
14297
14570
  dateFilter.$inject = ['$locale'];
@@ -14390,11 +14663,11 @@ function dateFilter($locale) {
14390
14663
  <doc:source>
14391
14664
  <pre>{{ {'name':'value'} | json }}</pre>
14392
14665
  </doc:source>
14393
- <doc:scenario>
14666
+ <doc:protractor>
14394
14667
  it('should jsonify filtered objects', function() {
14395
- expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
14668
+ expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
14396
14669
  });
14397
- </doc:scenario>
14670
+ </doc:protractor>
14398
14671
  </doc:example>
14399
14672
  *
14400
14673
  */
@@ -14462,28 +14735,37 @@ var uppercaseFilter = valueFn(uppercase);
14462
14735
  <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
14463
14736
  </div>
14464
14737
  </doc:source>
14465
- <doc:scenario>
14738
+ <doc:protractor>
14739
+ var numLimitInput = element(by.model('numLimit'));
14740
+ var letterLimitInput = element(by.model('letterLimit'));
14741
+ var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
14742
+ var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
14743
+
14466
14744
  it('should limit the number array to first three items', function() {
14467
- expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
14468
- expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
14469
- expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
14470
- expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
14745
+ expect(numLimitInput.getAttribute('value')).toBe('3');
14746
+ expect(letterLimitInput.getAttribute('value')).toBe('3');
14747
+ expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
14748
+ expect(limitedLetters.getText()).toEqual('Output letters: abc');
14471
14749
  });
14472
14750
 
14473
14751
  it('should update the output when -3 is entered', function() {
14474
- input('numLimit').enter(-3);
14475
- input('letterLimit').enter(-3);
14476
- expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
14477
- expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
14752
+ numLimitInput.clear();
14753
+ numLimitInput.sendKeys('-3');
14754
+ letterLimitInput.clear();
14755
+ letterLimitInput.sendKeys('-3');
14756
+ expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
14757
+ expect(limitedLetters.getText()).toEqual('Output letters: ghi');
14478
14758
  });
14479
14759
 
14480
14760
  it('should not exceed the maximum size of input array', function() {
14481
- input('numLimit').enter(100);
14482
- input('letterLimit').enter(100);
14483
- expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
14484
- expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
14761
+ numLimitInput.clear();
14762
+ numLimitInput.sendKeys('100');
14763
+ letterLimitInput.clear();
14764
+ letterLimitInput.sendKeys('100');
14765
+ expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
14766
+ expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
14485
14767
  });
14486
- </doc:scenario>
14768
+ </doc:protractor>
14487
14769
  </doc:example>
14488
14770
  */
14489
14771
  function limitToFilter(){
@@ -14584,29 +14866,6 @@ function limitToFilter(){
14584
14866
  </table>
14585
14867
  </div>
14586
14868
  </doc:source>
14587
- <doc:scenario>
14588
- it('should be reverse ordered by aged', function() {
14589
- expect(binding('predicate')).toBe('-age');
14590
- expect(repeater('table.friend', 'friend in friends').column('friend.age')).
14591
- toEqual(['35', '29', '21', '19', '10']);
14592
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
14593
- toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
14594
- });
14595
-
14596
- it('should reorder the table when user selects different predicate', function() {
14597
- element('.doc-example-live a:contains("Name")').click();
14598
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
14599
- toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
14600
- expect(repeater('table.friend', 'friend in friends').column('friend.age')).
14601
- toEqual(['35', '10', '29', '19', '21']);
14602
-
14603
- element('.doc-example-live a:contains("Phone")').click();
14604
- expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
14605
- toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
14606
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
14607
- toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
14608
- });
14609
- </doc:scenario>
14610
14869
  </doc:example>
14611
14870
  */
14612
14871
  orderByFilter.$inject = ['$parse'];
@@ -14703,11 +14962,14 @@ var htmlAnchorDirective = valueFn({
14703
14962
  element.append(document.createComment('IE fix'));
14704
14963
  }
14705
14964
 
14706
- if (!attr.href && !attr.name) {
14965
+ if (!attr.href && !attr.xlinkHref && !attr.name) {
14707
14966
  return function(scope, element) {
14967
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
14968
+ var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
14969
+ 'xlink:href' : 'href';
14708
14970
  element.on('click', function(event){
14709
14971
  // if we have no href url, then don't navigate anywhere.
14710
- if (!element.attr('href')) {
14972
+ if (!element.attr(href)) {
14711
14973
  event.preventDefault();
14712
14974
  }
14713
14975
  });
@@ -14720,6 +14982,7 @@ var htmlAnchorDirective = valueFn({
14720
14982
  * @ngdoc directive
14721
14983
  * @name ng.directive:ngHref
14722
14984
  * @restrict A
14985
+ * @priority 99
14723
14986
  *
14724
14987
  * @description
14725
14988
  * Using Angular markup like `{{hash}}` in an href attribute will
@@ -14756,46 +15019,55 @@ var htmlAnchorDirective = valueFn({
14756
15019
  <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
14757
15020
  <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
14758
15021
  </doc:source>
14759
- <doc:scenario>
15022
+ <doc:protractor>
14760
15023
  it('should execute ng-click but not reload when href without value', function() {
14761
- element('#link-1').click();
14762
- expect(input('value').val()).toEqual('1');
14763
- expect(element('#link-1').attr('href')).toBe("");
15024
+ element(by.id('link-1')).click();
15025
+ expect(element(by.model('value')).getAttribute('value')).toEqual('1');
15026
+ expect(element(by.id('link-1')).getAttribute('href')).toBe('');
14764
15027
  });
14765
15028
 
14766
15029
  it('should execute ng-click but not reload when href empty string', function() {
14767
- element('#link-2').click();
14768
- expect(input('value').val()).toEqual('2');
14769
- expect(element('#link-2').attr('href')).toBe("");
15030
+ element(by.id('link-2')).click();
15031
+ expect(element(by.model('value')).getAttribute('value')).toEqual('2');
15032
+ expect(element(by.id('link-2')).getAttribute('href')).toBe('');
14770
15033
  });
14771
15034
 
14772
15035
  it('should execute ng-click and change url when ng-href specified', function() {
14773
- expect(element('#link-3').attr('href')).toBe("/123");
15036
+ expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
15037
+
15038
+ element(by.id('link-3')).click();
14774
15039
 
14775
- element('#link-3').click();
14776
- expect(browser().window().path()).toEqual('/123');
15040
+ // At this point, we navigate away from an Angular page, so we need
15041
+ // to use browser.driver to get the base webdriver.
15042
+
15043
+ browser.wait(function() {
15044
+ return browser.driver.getCurrentUrl().then(function(url) {
15045
+ return url.match(/\/123$/);
15046
+ });
15047
+ }, 1000, 'page should navigate to /123');
14777
15048
  });
14778
15049
 
14779
15050
  it('should execute ng-click but not reload when href empty string and name specified', function() {
14780
- element('#link-4').click();
14781
- expect(input('value').val()).toEqual('4');
14782
- expect(element('#link-4').attr('href')).toBe('');
15051
+ element(by.id('link-4')).click();
15052
+ expect(element(by.model('value')).getAttribute('value')).toEqual('4');
15053
+ expect(element(by.id('link-4')).getAttribute('href')).toBe('');
14783
15054
  });
14784
15055
 
14785
15056
  it('should execute ng-click but not reload when no href but name specified', function() {
14786
- element('#link-5').click();
14787
- expect(input('value').val()).toEqual('5');
14788
- expect(element('#link-5').attr('href')).toBe(undefined);
15057
+ element(by.id('link-5')).click();
15058
+ expect(element(by.model('value')).getAttribute('value')).toEqual('5');
15059
+ expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
14789
15060
  });
14790
15061
 
14791
15062
  it('should only change url when only ng-href', function() {
14792
- input('value').enter('6');
14793
- expect(element('#link-6').attr('href')).toBe('6');
15063
+ element(by.model('value')).clear();
15064
+ element(by.model('value')).sendKeys('6');
15065
+ expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
14794
15066
 
14795
- element('#link-6').click();
14796
- expect(browser().location().url()).toEqual('/6');
15067
+ element(by.id('link-6')).click();
15068
+ expect(browser.getCurrentUrl()).toMatch(/\/6$/);
14797
15069
  });
14798
- </doc:scenario>
15070
+ </doc:protractor>
14799
15071
  </doc:example>
14800
15072
  */
14801
15073
 
@@ -14803,6 +15075,7 @@ var htmlAnchorDirective = valueFn({
14803
15075
  * @ngdoc directive
14804
15076
  * @name ng.directive:ngSrc
14805
15077
  * @restrict A
15078
+ * @priority 99
14806
15079
  *
14807
15080
  * @description
14808
15081
  * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
@@ -14828,6 +15101,7 @@ var htmlAnchorDirective = valueFn({
14828
15101
  * @ngdoc directive
14829
15102
  * @name ng.directive:ngSrcset
14830
15103
  * @restrict A
15104
+ * @priority 99
14831
15105
  *
14832
15106
  * @description
14833
15107
  * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
@@ -14853,6 +15127,7 @@ var htmlAnchorDirective = valueFn({
14853
15127
  * @ngdoc directive
14854
15128
  * @name ng.directive:ngDisabled
14855
15129
  * @restrict A
15130
+ * @priority 100
14856
15131
  *
14857
15132
  * @description
14858
15133
  *
@@ -14877,13 +15152,13 @@ var htmlAnchorDirective = valueFn({
14877
15152
  Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
14878
15153
  <button ng-model="button" ng-disabled="checked">Button</button>
14879
15154
  </doc:source>
14880
- <doc:scenario>
15155
+ <doc:protractor>
14881
15156
  it('should toggle button', function() {
14882
- expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
14883
- input('checked').check();
14884
- expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
15157
+ expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeFalsy();
15158
+ element(by.model('checked')).click();
15159
+ expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeTruthy();
14885
15160
  });
14886
- </doc:scenario>
15161
+ </doc:protractor>
14887
15162
  </doc:example>
14888
15163
  *
14889
15164
  * @element INPUT
@@ -14896,6 +15171,7 @@ var htmlAnchorDirective = valueFn({
14896
15171
  * @ngdoc directive
14897
15172
  * @name ng.directive:ngChecked
14898
15173
  * @restrict A
15174
+ * @priority 100
14899
15175
  *
14900
15176
  * @description
14901
15177
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -14911,13 +15187,13 @@ var htmlAnchorDirective = valueFn({
14911
15187
  Check me to check both: <input type="checkbox" ng-model="master"><br/>
14912
15188
  <input id="checkSlave" type="checkbox" ng-checked="master">
14913
15189
  </doc:source>
14914
- <doc:scenario>
15190
+ <doc:protractor>
14915
15191
  it('should check both checkBoxes', function() {
14916
- expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
14917
- input('master').check();
14918
- expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
15192
+ expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
15193
+ element(by.model('master')).click();
15194
+ expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
14919
15195
  });
14920
- </doc:scenario>
15196
+ </doc:protractor>
14921
15197
  </doc:example>
14922
15198
  *
14923
15199
  * @element INPUT
@@ -14930,6 +15206,7 @@ var htmlAnchorDirective = valueFn({
14930
15206
  * @ngdoc directive
14931
15207
  * @name ng.directive:ngReadonly
14932
15208
  * @restrict A
15209
+ * @priority 100
14933
15210
  *
14934
15211
  * @description
14935
15212
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -14939,20 +15216,19 @@ var htmlAnchorDirective = valueFn({
14939
15216
  * The `ngReadonly` directive solves this problem for the `readonly` attribute.
14940
15217
  * This complementary directive is not removed by the browser and so provides
14941
15218
  * a permanent reliable place to store the binding information.
14942
-
14943
15219
  * @example
14944
15220
  <doc:example>
14945
15221
  <doc:source>
14946
15222
  Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
14947
15223
  <input type="text" ng-readonly="checked" value="I'm Angular"/>
14948
15224
  </doc:source>
14949
- <doc:scenario>
15225
+ <doc:protractor>
14950
15226
  it('should toggle readonly attr', function() {
14951
- expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
14952
- input('checked').check();
14953
- expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
15227
+ expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeFalsy();
15228
+ element(by.model('checked')).click();
15229
+ expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeTruthy();
14954
15230
  });
14955
- </doc:scenario>
15231
+ </doc:protractor>
14956
15232
  </doc:example>
14957
15233
  *
14958
15234
  * @element INPUT
@@ -14965,6 +15241,7 @@ var htmlAnchorDirective = valueFn({
14965
15241
  * @ngdoc directive
14966
15242
  * @name ng.directive:ngSelected
14967
15243
  * @restrict A
15244
+ * @priority 100
14968
15245
  *
14969
15246
  * @description
14970
15247
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -14974,6 +15251,7 @@ var htmlAnchorDirective = valueFn({
14974
15251
  * The `ngSelected` directive solves this problem for the `selected` atttribute.
14975
15252
  * This complementary directive is not removed by the browser and so provides
14976
15253
  * a permanent reliable place to store the binding information.
15254
+ *
14977
15255
  * @example
14978
15256
  <doc:example>
14979
15257
  <doc:source>
@@ -14983,13 +15261,13 @@ var htmlAnchorDirective = valueFn({
14983
15261
  <option id="greet" ng-selected="selected">Greetings!</option>
14984
15262
  </select>
14985
15263
  </doc:source>
14986
- <doc:scenario>
15264
+ <doc:protractor>
14987
15265
  it('should select Greetings!', function() {
14988
- expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
14989
- input('selected').check();
14990
- expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
15266
+ expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
15267
+ element(by.model('selected')).click();
15268
+ expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
14991
15269
  });
14992
- </doc:scenario>
15270
+ </doc:protractor>
14993
15271
  </doc:example>
14994
15272
  *
14995
15273
  * @element OPTION
@@ -15001,6 +15279,7 @@ var htmlAnchorDirective = valueFn({
15001
15279
  * @ngdoc directive
15002
15280
  * @name ng.directive:ngOpen
15003
15281
  * @restrict A
15282
+ * @priority 100
15004
15283
  *
15005
15284
  * @description
15006
15285
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -15010,8 +15289,6 @@ var htmlAnchorDirective = valueFn({
15010
15289
  * The `ngOpen` directive solves this problem for the `open` attribute.
15011
15290
  * This complementary directive is not removed by the browser and so provides
15012
15291
  * a permanent reliable place to store the binding information.
15013
-
15014
- *
15015
15292
  * @example
15016
15293
  <doc:example>
15017
15294
  <doc:source>
@@ -15020,13 +15297,13 @@ var htmlAnchorDirective = valueFn({
15020
15297
  <summary>Show/Hide me</summary>
15021
15298
  </details>
15022
15299
  </doc:source>
15023
- <doc:scenario>
15300
+ <doc:protractor>
15024
15301
  it('should toggle open', function() {
15025
- expect(element('#details').prop('open')).toBeFalsy();
15026
- input('open').check();
15027
- expect(element('#details').prop('open')).toBeTruthy();
15302
+ expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
15303
+ element(by.model('open')).click();
15304
+ expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
15028
15305
  });
15029
- </doc:scenario>
15306
+ </doc:protractor>
15030
15307
  </doc:example>
15031
15308
  *
15032
15309
  * @element DETAILS
@@ -15046,12 +15323,10 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
15046
15323
  ngAttributeAliasDirectives[normalized] = function() {
15047
15324
  return {
15048
15325
  priority: 100,
15049
- compile: function() {
15050
- return function(scope, element, attr) {
15051
- scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
15052
- attr.$set(attrName, !!value);
15053
- });
15054
- };
15326
+ link: function(scope, element, attr) {
15327
+ scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
15328
+ attr.$set(attrName, !!value);
15329
+ });
15055
15330
  }
15056
15331
  };
15057
15332
  };
@@ -15331,10 +15606,10 @@ function FormController(element, attrs) {
15331
15606
  *
15332
15607
  *
15333
15608
  * # CSS classes
15334
- * - `ng-valid` Is set if the form is valid.
15335
- * - `ng-invalid` Is set if the form is invalid.
15336
- * - `ng-pristine` Is set if the form is pristine.
15337
- * - `ng-dirty` Is set if the form is dirty.
15609
+ * - `ng-valid` is set if the form is valid.
15610
+ * - `ng-invalid` is set if the form is invalid.
15611
+ * - `ng-pristine` is set if the form is pristine.
15612
+ * - `ng-dirty` is set if the form is dirty.
15338
15613
  *
15339
15614
  *
15340
15615
  * # Submitting a form and preventing the default action
@@ -15387,18 +15662,27 @@ function FormController(element, attrs) {
15387
15662
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
15388
15663
  </form>
15389
15664
  </doc:source>
15390
- <doc:scenario>
15665
+ <doc:protractor>
15391
15666
  it('should initialize to model', function() {
15392
- expect(binding('userType')).toEqual('guest');
15393
- expect(binding('myForm.input.$valid')).toEqual('true');
15667
+ var userType = element(by.binding('userType'));
15668
+ var valid = element(by.binding('myForm.input.$valid'));
15669
+
15670
+ expect(userType.getText()).toContain('guest');
15671
+ expect(valid.getText()).toContain('true');
15394
15672
  });
15395
15673
 
15396
15674
  it('should be invalid if empty', function() {
15397
- input('userType').enter('');
15398
- expect(binding('userType')).toEqual('');
15399
- expect(binding('myForm.input.$valid')).toEqual('false');
15675
+ var userType = element(by.binding('userType'));
15676
+ var valid = element(by.binding('myForm.input.$valid'));
15677
+ var userInput = element(by.model('userType'));
15678
+
15679
+ userInput.clear();
15680
+ userInput.sendKeys('');
15681
+
15682
+ expect(userType.getText()).toEqual('userType =');
15683
+ expect(valid.getText()).toContain('false');
15400
15684
  });
15401
- </doc:scenario>
15685
+ </doc:protractor>
15402
15686
  </doc:example>
15403
15687
  */
15404
15688
  var formDirectiveFactory = function(isNgForm) {
@@ -15470,7 +15754,7 @@ var ngFormDirective = formDirectiveFactory(true);
15470
15754
  */
15471
15755
 
15472
15756
  var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
15473
- var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
15757
+ var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
15474
15758
  var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
15475
15759
 
15476
15760
  var inputType = {
@@ -15523,29 +15807,31 @@ var inputType = {
15523
15807
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
15524
15808
  </form>
15525
15809
  </doc:source>
15526
- <doc:scenario>
15810
+ <doc:protractor>
15811
+ var text = element(by.binding('text'));
15812
+ var valid = element(by.binding('myForm.input.$valid'));
15813
+ var input = element(by.model('text'));
15814
+
15527
15815
  it('should initialize to model', function() {
15528
- expect(binding('text')).toEqual('guest');
15529
- expect(binding('myForm.input.$valid')).toEqual('true');
15816
+ expect(text.getText()).toContain('guest');
15817
+ expect(valid.getText()).toContain('true');
15530
15818
  });
15531
15819
 
15532
15820
  it('should be invalid if empty', function() {
15533
- input('text').enter('');
15534
- expect(binding('text')).toEqual('');
15535
- expect(binding('myForm.input.$valid')).toEqual('false');
15821
+ input.clear();
15822
+ input.sendKeys('');
15823
+
15824
+ expect(text.getText()).toEqual('text =');
15825
+ expect(valid.getText()).toContain('false');
15536
15826
  });
15537
15827
 
15538
15828
  it('should be invalid if multi word', function() {
15539
- input('text').enter('hello world');
15540
- expect(binding('myForm.input.$valid')).toEqual('false');
15541
- });
15829
+ input.clear();
15830
+ input.sendKeys('hello world');
15542
15831
 
15543
- it('should not be trimmed', function() {
15544
- input('text').enter('untrimmed ');
15545
- expect(binding('text')).toEqual('untrimmed ');
15546
- expect(binding('myForm.input.$valid')).toEqual('true');
15832
+ expect(valid.getText()).toContain('false');
15547
15833
  });
15548
- </doc:scenario>
15834
+ </doc:protractor>
15549
15835
  </doc:example>
15550
15836
  */
15551
15837
  'text': textInputType,
@@ -15599,24 +15885,30 @@ var inputType = {
15599
15885
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
15600
15886
  </form>
15601
15887
  </doc:source>
15602
- <doc:scenario>
15888
+ <doc:protractor>
15889
+ var value = element(by.binding('value'));
15890
+ var valid = element(by.binding('myForm.input.$valid'));
15891
+ var input = element(by.model('value'));
15892
+
15603
15893
  it('should initialize to model', function() {
15604
- expect(binding('value')).toEqual('12');
15605
- expect(binding('myForm.input.$valid')).toEqual('true');
15894
+ expect(value.getText()).toContain('12');
15895
+ expect(valid.getText()).toContain('true');
15606
15896
  });
15607
15897
 
15608
15898
  it('should be invalid if empty', function() {
15609
- input('value').enter('');
15610
- expect(binding('value')).toEqual('');
15611
- expect(binding('myForm.input.$valid')).toEqual('false');
15899
+ input.clear();
15900
+ input.sendKeys('');
15901
+ expect(value.getText()).toEqual('value =');
15902
+ expect(valid.getText()).toContain('false');
15612
15903
  });
15613
15904
 
15614
15905
  it('should be invalid if over max', function() {
15615
- input('value').enter('123');
15616
- expect(binding('value')).toEqual('');
15617
- expect(binding('myForm.input.$valid')).toEqual('false');
15906
+ input.clear();
15907
+ input.sendKeys('123');
15908
+ expect(value.getText()).toEqual('value =');
15909
+ expect(valid.getText()).toContain('false');
15618
15910
  });
15619
- </doc:scenario>
15911
+ </doc:protractor>
15620
15912
  </doc:example>
15621
15913
  */
15622
15914
  'number': numberInputType,
@@ -15668,23 +15960,31 @@ var inputType = {
15668
15960
  <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
15669
15961
  </form>
15670
15962
  </doc:source>
15671
- <doc:scenario>
15963
+ <doc:protractor>
15964
+ var text = element(by.binding('text'));
15965
+ var valid = element(by.binding('myForm.input.$valid'));
15966
+ var input = element(by.model('text'));
15967
+
15672
15968
  it('should initialize to model', function() {
15673
- expect(binding('text')).toEqual('http://google.com');
15674
- expect(binding('myForm.input.$valid')).toEqual('true');
15969
+ expect(text.getText()).toContain('http://google.com');
15970
+ expect(valid.getText()).toContain('true');
15675
15971
  });
15676
15972
 
15677
15973
  it('should be invalid if empty', function() {
15678
- input('text').enter('');
15679
- expect(binding('text')).toEqual('');
15680
- expect(binding('myForm.input.$valid')).toEqual('false');
15974
+ input.clear();
15975
+ input.sendKeys('');
15976
+
15977
+ expect(text.getText()).toEqual('text =');
15978
+ expect(valid.getText()).toContain('false');
15681
15979
  });
15682
15980
 
15683
15981
  it('should be invalid if not url', function() {
15684
- input('text').enter('xxx');
15685
- expect(binding('myForm.input.$valid')).toEqual('false');
15982
+ input.clear();
15983
+ input.sendKeys('box');
15984
+
15985
+ expect(valid.getText()).toContain('false');
15686
15986
  });
15687
- </doc:scenario>
15987
+ </doc:protractor>
15688
15988
  </doc:example>
15689
15989
  */
15690
15990
  'url': urlInputType,
@@ -15736,23 +16036,30 @@ var inputType = {
15736
16036
  <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
15737
16037
  </form>
15738
16038
  </doc:source>
15739
- <doc:scenario>
16039
+ <doc:protractor>
16040
+ var text = element(by.binding('text'));
16041
+ var valid = element(by.binding('myForm.input.$valid'));
16042
+ var input = element(by.model('text'));
16043
+
15740
16044
  it('should initialize to model', function() {
15741
- expect(binding('text')).toEqual('me@example.com');
15742
- expect(binding('myForm.input.$valid')).toEqual('true');
16045
+ expect(text.getText()).toContain('me@example.com');
16046
+ expect(valid.getText()).toContain('true');
15743
16047
  });
15744
16048
 
15745
16049
  it('should be invalid if empty', function() {
15746
- input('text').enter('');
15747
- expect(binding('text')).toEqual('');
15748
- expect(binding('myForm.input.$valid')).toEqual('false');
16050
+ input.clear();
16051
+ input.sendKeys('');
16052
+ expect(text.getText()).toEqual('text =');
16053
+ expect(valid.getText()).toContain('false');
15749
16054
  });
15750
16055
 
15751
16056
  it('should be invalid if not email', function() {
15752
- input('text').enter('xxx');
15753
- expect(binding('myForm.input.$valid')).toEqual('false');
16057
+ input.clear();
16058
+ input.sendKeys('xxx');
16059
+
16060
+ expect(valid.getText()).toContain('false');
15754
16061
  });
15755
- </doc:scenario>
16062
+ </doc:protractor>
15756
16063
  </doc:example>
15757
16064
  */
15758
16065
  'email': emailInputType,
@@ -15770,6 +16077,8 @@ var inputType = {
15770
16077
  * @param {string=} name Property name of the form under which the control is published.
15771
16078
  * @param {string=} ngChange Angular expression to be executed when input changes due to user
15772
16079
  * interaction with the input element.
16080
+ * @param {string} ngValue Angular expression which sets the value to which the expression should
16081
+ * be set when selected.
15773
16082
  *
15774
16083
  * @example
15775
16084
  <doc:example>
@@ -15777,23 +16086,31 @@ var inputType = {
15777
16086
  <script>
15778
16087
  function Ctrl($scope) {
15779
16088
  $scope.color = 'blue';
16089
+ $scope.specialValue = {
16090
+ "id": "12345",
16091
+ "value": "green"
16092
+ };
15780
16093
  }
15781
16094
  </script>
15782
16095
  <form name="myForm" ng-controller="Ctrl">
15783
16096
  <input type="radio" ng-model="color" value="red"> Red <br/>
15784
- <input type="radio" ng-model="color" value="green"> Green <br/>
16097
+ <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/>
15785
16098
  <input type="radio" ng-model="color" value="blue"> Blue <br/>
15786
- <tt>color = {{color}}</tt><br/>
16099
+ <tt>color = {{color | json}}</tt><br/>
15787
16100
  </form>
16101
+ Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
15788
16102
  </doc:source>
15789
- <doc:scenario>
16103
+ <doc:protractor>
15790
16104
  it('should change state', function() {
15791
- expect(binding('color')).toEqual('blue');
16105
+ var color = element(by.binding('color'));
15792
16106
 
15793
- input('color').select('red');
15794
- expect(binding('color')).toEqual('red');
16107
+ expect(color.getText()).toContain('blue');
16108
+
16109
+ element.all(by.model('color')).get(0).click();
16110
+
16111
+ expect(color.getText()).toContain('red');
15795
16112
  });
15796
- </doc:scenario>
16113
+ </doc:protractor>
15797
16114
  </doc:example>
15798
16115
  */
15799
16116
  'radio': radioInputType,
@@ -15830,17 +16147,21 @@ var inputType = {
15830
16147
  <tt>value2 = {{value2}}</tt><br/>
15831
16148
  </form>
15832
16149
  </doc:source>
15833
- <doc:scenario>
16150
+ <doc:protractor>
15834
16151
  it('should change state', function() {
15835
- expect(binding('value1')).toEqual('true');
15836
- expect(binding('value2')).toEqual('YES');
16152
+ var value1 = element(by.binding('value1'));
16153
+ var value2 = element(by.binding('value2'));
15837
16154
 
15838
- input('value1').check();
15839
- input('value2').check();
15840
- expect(binding('value1')).toEqual('false');
15841
- expect(binding('value2')).toEqual('NO');
16155
+ expect(value1.getText()).toContain('true');
16156
+ expect(value2.getText()).toContain('YES');
16157
+
16158
+ element(by.model('value1')).click();
16159
+ element(by.model('value2')).click();
16160
+
16161
+ expect(value1.getText()).toContain('false');
16162
+ expect(value2.getText()).toContain('NO');
15842
16163
  });
15843
- </doc:scenario>
16164
+ </doc:protractor>
15844
16165
  </doc:example>
15845
16166
  */
15846
16167
  'checkbox': checkboxInputType,
@@ -15848,23 +16169,33 @@ var inputType = {
15848
16169
  'hidden': noop,
15849
16170
  'button': noop,
15850
16171
  'submit': noop,
15851
- 'reset': noop
16172
+ 'reset': noop,
16173
+ 'file': noop
15852
16174
  };
15853
16175
 
16176
+ // A helper function to call $setValidity and return the value / undefined,
16177
+ // a pattern that is repeated a lot in the input validation logic.
16178
+ function validate(ctrl, validatorName, validity, value){
16179
+ ctrl.$setValidity(validatorName, validity);
16180
+ return validity ? value : undefined;
16181
+ }
15854
16182
 
15855
16183
  function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15856
16184
  // In composition mode, users are still inputing intermediate text buffer,
15857
16185
  // hold the listener until composition is done.
15858
16186
  // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
15859
- var composing = false;
16187
+ if (!$sniffer.android) {
16188
+ var composing = false;
15860
16189
 
15861
- element.on('compositionstart', function() {
15862
- composing = true;
15863
- });
16190
+ element.on('compositionstart', function(data) {
16191
+ composing = true;
16192
+ });
15864
16193
 
15865
- element.on('compositionend', function() {
15866
- composing = false;
15867
- });
16194
+ element.on('compositionend', function() {
16195
+ composing = false;
16196
+ listener();
16197
+ });
16198
+ }
15868
16199
 
15869
16200
  var listener = function() {
15870
16201
  if (composing) return;
@@ -15878,9 +16209,13 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15878
16209
  }
15879
16210
 
15880
16211
  if (ctrl.$viewValue !== value) {
15881
- scope.$apply(function() {
16212
+ if (scope.$$phase) {
15882
16213
  ctrl.$setViewValue(value);
15883
- });
16214
+ } else {
16215
+ scope.$apply(function() {
16216
+ ctrl.$setViewValue(value);
16217
+ });
16218
+ }
15884
16219
  }
15885
16220
  };
15886
16221
 
@@ -15929,22 +16264,15 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15929
16264
  patternValidator,
15930
16265
  match;
15931
16266
 
15932
- var validate = function(regexp, value) {
15933
- if (ctrl.$isEmpty(value) || regexp.test(value)) {
15934
- ctrl.$setValidity('pattern', true);
15935
- return value;
15936
- } else {
15937
- ctrl.$setValidity('pattern', false);
15938
- return undefined;
15939
- }
15940
- };
15941
-
15942
16267
  if (pattern) {
16268
+ var validateRegex = function(regexp, value) {
16269
+ return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value);
16270
+ };
15943
16271
  match = pattern.match(/^\/(.*)\/([gim]*)$/);
15944
16272
  if (match) {
15945
16273
  pattern = new RegExp(match[1], match[2]);
15946
16274
  patternValidator = function(value) {
15947
- return validate(pattern, value);
16275
+ return validateRegex(pattern, value);
15948
16276
  };
15949
16277
  } else {
15950
16278
  patternValidator = function(value) {
@@ -15955,7 +16283,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15955
16283
  'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern,
15956
16284
  patternObj, startingTag(element));
15957
16285
  }
15958
- return validate(patternObj, value);
16286
+ return validateRegex(patternObj, value);
15959
16287
  };
15960
16288
  }
15961
16289
 
@@ -15967,13 +16295,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15967
16295
  if (attr.ngMinlength) {
15968
16296
  var minlength = int(attr.ngMinlength);
15969
16297
  var minLengthValidator = function(value) {
15970
- if (!ctrl.$isEmpty(value) && value.length < minlength) {
15971
- ctrl.$setValidity('minlength', false);
15972
- return undefined;
15973
- } else {
15974
- ctrl.$setValidity('minlength', true);
15975
- return value;
15976
- }
16298
+ return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value);
15977
16299
  };
15978
16300
 
15979
16301
  ctrl.$parsers.push(minLengthValidator);
@@ -15984,13 +16306,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
15984
16306
  if (attr.ngMaxlength) {
15985
16307
  var maxlength = int(attr.ngMaxlength);
15986
16308
  var maxLengthValidator = function(value) {
15987
- if (!ctrl.$isEmpty(value) && value.length > maxlength) {
15988
- ctrl.$setValidity('maxlength', false);
15989
- return undefined;
15990
- } else {
15991
- ctrl.$setValidity('maxlength', true);
15992
- return value;
15993
- }
16309
+ return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value);
15994
16310
  };
15995
16311
 
15996
16312
  ctrl.$parsers.push(maxLengthValidator);
@@ -16019,13 +16335,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16019
16335
  if (attr.min) {
16020
16336
  var minValidator = function(value) {
16021
16337
  var min = parseFloat(attr.min);
16022
- if (!ctrl.$isEmpty(value) && value < min) {
16023
- ctrl.$setValidity('min', false);
16024
- return undefined;
16025
- } else {
16026
- ctrl.$setValidity('min', true);
16027
- return value;
16028
- }
16338
+ return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value);
16029
16339
  };
16030
16340
 
16031
16341
  ctrl.$parsers.push(minValidator);
@@ -16035,13 +16345,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16035
16345
  if (attr.max) {
16036
16346
  var maxValidator = function(value) {
16037
16347
  var max = parseFloat(attr.max);
16038
- if (!ctrl.$isEmpty(value) && value > max) {
16039
- ctrl.$setValidity('max', false);
16040
- return undefined;
16041
- } else {
16042
- ctrl.$setValidity('max', true);
16043
- return value;
16044
- }
16348
+ return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value);
16045
16349
  };
16046
16350
 
16047
16351
  ctrl.$parsers.push(maxValidator);
@@ -16049,14 +16353,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16049
16353
  }
16050
16354
 
16051
16355
  ctrl.$formatters.push(function(value) {
16052
-
16053
- if (ctrl.$isEmpty(value) || isNumber(value)) {
16054
- ctrl.$setValidity('number', true);
16055
- return value;
16056
- } else {
16057
- ctrl.$setValidity('number', false);
16058
- return undefined;
16059
- }
16356
+ return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value);
16060
16357
  });
16061
16358
  }
16062
16359
 
@@ -16064,13 +16361,7 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16064
16361
  textInputType(scope, element, attr, ctrl, $sniffer, $browser);
16065
16362
 
16066
16363
  var urlValidator = function(value) {
16067
- if (ctrl.$isEmpty(value) || URL_REGEXP.test(value)) {
16068
- ctrl.$setValidity('url', true);
16069
- return value;
16070
- } else {
16071
- ctrl.$setValidity('url', false);
16072
- return undefined;
16073
- }
16364
+ return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value);
16074
16365
  };
16075
16366
 
16076
16367
  ctrl.$formatters.push(urlValidator);
@@ -16081,13 +16372,7 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
16081
16372
  textInputType(scope, element, attr, ctrl, $sniffer, $browser);
16082
16373
 
16083
16374
  var emailValidator = function(value) {
16084
- if (ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value)) {
16085
- ctrl.$setValidity('email', true);
16086
- return value;
16087
- } else {
16088
- ctrl.$setValidity('email', false);
16089
- return undefined;
16090
- }
16375
+ return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value);
16091
16376
  };
16092
16377
 
16093
16378
  ctrl.$formatters.push(emailValidator);
@@ -16231,44 +16516,59 @@ function checkboxInputType(scope, element, attr, ctrl) {
16231
16516
  <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
16232
16517
  </div>
16233
16518
  </doc:source>
16234
- <doc:scenario>
16519
+ <doc:protractor>
16520
+ var user = element(by.binding('{{user}}'));
16521
+ var userNameValid = element(by.binding('myForm.userName.$valid'));
16522
+ var lastNameValid = element(by.binding('myForm.lastName.$valid'));
16523
+ var lastNameError = element(by.binding('myForm.lastName.$error'));
16524
+ var formValid = element(by.binding('myForm.$valid'));
16525
+ var userNameInput = element(by.model('user.name'));
16526
+ var userLastInput = element(by.model('user.last'));
16527
+
16235
16528
  it('should initialize to model', function() {
16236
- expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
16237
- expect(binding('myForm.userName.$valid')).toEqual('true');
16238
- expect(binding('myForm.$valid')).toEqual('true');
16529
+ expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
16530
+ expect(userNameValid.getText()).toContain('true');
16531
+ expect(formValid.getText()).toContain('true');
16239
16532
  });
16240
16533
 
16241
16534
  it('should be invalid if empty when required', function() {
16242
- input('user.name').enter('');
16243
- expect(binding('user')).toEqual('{"last":"visitor"}');
16244
- expect(binding('myForm.userName.$valid')).toEqual('false');
16245
- expect(binding('myForm.$valid')).toEqual('false');
16535
+ userNameInput.clear();
16536
+ userNameInput.sendKeys('');
16537
+
16538
+ expect(user.getText()).toContain('{"last":"visitor"}');
16539
+ expect(userNameValid.getText()).toContain('false');
16540
+ expect(formValid.getText()).toContain('false');
16246
16541
  });
16247
16542
 
16248
16543
  it('should be valid if empty when min length is set', function() {
16249
- input('user.last').enter('');
16250
- expect(binding('user')).toEqual('{"name":"guest","last":""}');
16251
- expect(binding('myForm.lastName.$valid')).toEqual('true');
16252
- expect(binding('myForm.$valid')).toEqual('true');
16544
+ userLastInput.clear();
16545
+ userLastInput.sendKeys('');
16546
+
16547
+ expect(user.getText()).toContain('{"name":"guest","last":""}');
16548
+ expect(lastNameValid.getText()).toContain('true');
16549
+ expect(formValid.getText()).toContain('true');
16253
16550
  });
16254
16551
 
16255
16552
  it('should be invalid if less than required min length', function() {
16256
- input('user.last').enter('xx');
16257
- expect(binding('user')).toEqual('{"name":"guest"}');
16258
- expect(binding('myForm.lastName.$valid')).toEqual('false');
16259
- expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
16260
- expect(binding('myForm.$valid')).toEqual('false');
16553
+ userLastInput.clear();
16554
+ userLastInput.sendKeys('xx');
16555
+
16556
+ expect(user.getText()).toContain('{"name":"guest"}');
16557
+ expect(lastNameValid.getText()).toContain('false');
16558
+ expect(lastNameError.getText()).toContain('minlength');
16559
+ expect(formValid.getText()).toContain('false');
16261
16560
  });
16262
16561
 
16263
16562
  it('should be invalid if longer than max length', function() {
16264
- input('user.last').enter('some ridiculously long name');
16265
- expect(binding('user'))
16266
- .toEqual('{"name":"guest"}');
16267
- expect(binding('myForm.lastName.$valid')).toEqual('false');
16268
- expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
16269
- expect(binding('myForm.$valid')).toEqual('false');
16563
+ userLastInput.clear();
16564
+ userLastInput.sendKeys('some ridiculously long name');
16565
+
16566
+ expect(user.getText()).toContain('{"name":"guest"}');
16567
+ expect(lastNameValid.getText()).toContain('false');
16568
+ expect(lastNameError.getText()).toContain('maxlength');
16569
+ expect(formValid.getText()).toContain('false');
16270
16570
  });
16271
- </doc:scenario>
16571
+ </doc:protractor>
16272
16572
  </doc:example>
16273
16573
  */
16274
16574
  var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
@@ -16400,14 +16700,23 @@ var VALID_CLASS = 'ng-valid',
16400
16700
  <textarea ng-model="userContent"></textarea>
16401
16701
  </form>
16402
16702
  </file>
16403
- <file name="scenario.js">
16703
+ <file name="protractorTest.js">
16404
16704
  it('should data-bind and become invalid', function() {
16405
- var contentEditable = element('[contenteditable]');
16705
+ if (browser.params.browser = 'safari') {
16706
+ // SafariDriver can't handle contenteditable.
16707
+ return;
16708
+ };
16709
+ var contentEditable = element(by.css('.doc-example-live [contenteditable]'));
16710
+
16711
+ expect(contentEditable.getText()).toEqual('Change me!');
16712
+
16713
+ // Firefox driver doesn't trigger the proper events on 'clear', so do this hack
16714
+ contentEditable.click();
16715
+ contentEditable.sendKeys(protractor.Key.chord(protractor.Key.COMMAND, "a"));
16716
+ contentEditable.sendKeys(protractor.Key.BACK_SPACE);
16406
16717
 
16407
- expect(contentEditable.text()).toEqual('Change me!');
16408
- input('userContent').enter('');
16409
- expect(contentEditable.text()).toEqual('');
16410
- expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
16718
+ expect(contentEditable.getText()).toEqual('');
16719
+ expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
16411
16720
  });
16412
16721
  </file>
16413
16722
  * </example>
@@ -16460,6 +16769,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
16460
16769
  * You can override this for input directives whose concept of being empty is different to the
16461
16770
  * default. The `checkboxInputType` directive does this because in its case a value of `false`
16462
16771
  * implies empty.
16772
+ *
16773
+ * @param {*} value Reference to check.
16774
+ * @returns {boolean} True if `value` is empty.
16463
16775
  */
16464
16776
  this.$isEmpty = function(value) {
16465
16777
  return isUndefined(value) || value === '' || value === null || value !== value;
@@ -16687,7 +16999,10 @@ var ngModelDirective = function() {
16687
16999
  * @name ng.directive:ngChange
16688
17000
  *
16689
17001
  * @description
16690
- * Evaluate given expression when user changes the input.
17002
+ * Evaluate the given expression when the user changes the input.
17003
+ * The expression is evaluated immediately, unlike the JavaScript onchange event
17004
+ * which only triggers at the end of a change (usually, when the user leaves the
17005
+ * form element or presses the return key).
16691
17006
  * The expression is not evaluated when the value change is coming from the model.
16692
17007
  *
16693
17008
  * Note, this directive requires `ngModel` to be present.
@@ -16711,24 +17026,30 @@ var ngModelDirective = function() {
16711
17026
  * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
16712
17027
  * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
16713
17028
  * <label for="ng-change-example2">Confirmed</label><br />
16714
- * debug = {{confirmed}}<br />
16715
- * counter = {{counter}}
17029
+ * <tt>debug = {{confirmed}}</tt><br/>
17030
+ * <tt>counter = {{counter}}</tt><br/>
16716
17031
  * </div>
16717
17032
  * </doc:source>
16718
- * <doc:scenario>
17033
+ * <doc:protractor>
17034
+ * var counter = element(by.binding('counter'));
17035
+ * var debug = element(by.binding('confirmed'));
17036
+ *
16719
17037
  * it('should evaluate the expression if changing from view', function() {
16720
- * expect(binding('counter')).toEqual('0');
16721
- * element('#ng-change-example1').click();
16722
- * expect(binding('counter')).toEqual('1');
16723
- * expect(binding('confirmed')).toEqual('true');
17038
+ * expect(counter.getText()).toContain('0');
17039
+ *
17040
+ * element(by.id('ng-change-example1')).click();
17041
+ *
17042
+ * expect(counter.getText()).toContain('1');
17043
+ * expect(debug.getText()).toContain('true');
16724
17044
  * });
16725
17045
  *
16726
17046
  * it('should not evaluate the expression if changing from model', function() {
16727
- * element('#ng-change-example2').click();
16728
- * expect(binding('counter')).toEqual('0');
16729
- * expect(binding('confirmed')).toEqual('true');
17047
+ * element(by.id('ng-change-example2')).click();
17048
+
17049
+ * expect(counter.getText()).toContain('0');
17050
+ * expect(debug.getText()).toContain('true');
16730
17051
  * });
16731
- * </doc:scenario>
17052
+ * </doc:protractor>
16732
17053
  * </doc:example>
16733
17054
  */
16734
17055
  var ngChangeDirective = valueFn({
@@ -16801,20 +17122,26 @@ var requiredDirective = function() {
16801
17122
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
16802
17123
  </form>
16803
17124
  </doc:source>
16804
- <doc:scenario>
17125
+ <doc:protractor>
17126
+ var listInput = element(by.model('names'));
17127
+ var names = element(by.binding('{{names}}'));
17128
+ var valid = element(by.binding('myForm.namesInput.$valid'));
17129
+ var error = element(by.css('span.error'));
17130
+
16805
17131
  it('should initialize to model', function() {
16806
- expect(binding('names')).toEqual('["igor","misko","vojta"]');
16807
- expect(binding('myForm.namesInput.$valid')).toEqual('true');
16808
- expect(element('span.error').css('display')).toBe('none');
17132
+ expect(names.getText()).toContain('["igor","misko","vojta"]');
17133
+ expect(valid.getText()).toContain('true');
17134
+ expect(error.getCssValue('display')).toBe('none');
16809
17135
  });
16810
17136
 
16811
17137
  it('should be invalid if empty', function() {
16812
- input('names').enter('');
16813
- expect(binding('names')).toEqual('');
16814
- expect(binding('myForm.namesInput.$valid')).toEqual('false');
16815
- expect(element('span.error').css('display')).not().toBe('none');
16816
- });
16817
- </doc:scenario>
17138
+ listInput.clear();
17139
+ listInput.sendKeys('');
17140
+
17141
+ expect(names.getText()).toContain('');
17142
+ expect(valid.getText()).toContain('false');
17143
+ expect(error.getCssValue('display')).not.toBe('none'); });
17144
+ </doc:protractor>
16818
17145
  </doc:example>
16819
17146
  */
16820
17147
  var ngListDirective = function() {
@@ -16896,15 +17223,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
16896
17223
  <div>You chose {{my.favorite}}</div>
16897
17224
  </form>
16898
17225
  </doc:source>
16899
- <doc:scenario>
17226
+ <doc:protractor>
17227
+ var favorite = element(by.binding('my.favorite'));
17228
+
16900
17229
  it('should initialize to model', function() {
16901
- expect(binding('my.favorite')).toEqual('unicorns');
17230
+ expect(favorite.getText()).toContain('unicorns');
16902
17231
  });
16903
17232
  it('should bind the values to the inputs', function() {
16904
- input('my.favorite').select('pizza');
16905
- expect(binding('my.favorite')).toEqual('pizza');
17233
+ element.all(by.model('my.favorite')).get(0).click();
17234
+ expect(favorite.getText()).toContain('pizza');
16906
17235
  });
16907
- </doc:scenario>
17236
+ </doc:protractor>
16908
17237
  </doc:example>
16909
17238
  */
16910
17239
  var ngValueDirective = function() {
@@ -16964,13 +17293,17 @@ var ngValueDirective = function() {
16964
17293
  Hello <span ng-bind="name"></span>!
16965
17294
  </div>
16966
17295
  </doc:source>
16967
- <doc:scenario>
17296
+ <doc:protractor>
16968
17297
  it('should check ng-bind', function() {
16969
- expect(using('.doc-example-live').binding('name')).toBe('Whirled');
16970
- using('.doc-example-live').input('name').enter('world');
16971
- expect(using('.doc-example-live').binding('name')).toBe('world');
17298
+ var exampleContainer = $('.doc-example-live');
17299
+ var nameInput = element(by.model('name'));
17300
+
17301
+ expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('Whirled');
17302
+ nameInput.clear();
17303
+ nameInput.sendKeys('world');
17304
+ expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('world');
16972
17305
  });
16973
- </doc:scenario>
17306
+ </doc:protractor>
16974
17307
  </doc:example>
16975
17308
  */
16976
17309
  var ngBindDirective = ngDirective(function(scope, element, attr) {
@@ -17016,20 +17349,22 @@ var ngBindDirective = ngDirective(function(scope, element, attr) {
17016
17349
  <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
17017
17350
  </div>
17018
17351
  </doc:source>
17019
- <doc:scenario>
17352
+ <doc:protractor>
17020
17353
  it('should check ng-bind', function() {
17021
- expect(using('.doc-example-live').binding('salutation')).
17022
- toBe('Hello');
17023
- expect(using('.doc-example-live').binding('name')).
17024
- toBe('World');
17025
- using('.doc-example-live').input('salutation').enter('Greetings');
17026
- using('.doc-example-live').input('name').enter('user');
17027
- expect(using('.doc-example-live').binding('salutation')).
17028
- toBe('Greetings');
17029
- expect(using('.doc-example-live').binding('name')).
17030
- toBe('user');
17354
+ var salutationElem = element(by.binding('salutation'));
17355
+ var salutationInput = element(by.model('salutation'));
17356
+ var nameInput = element(by.model('name'));
17357
+
17358
+ expect(salutationElem.getText()).toBe('Hello World!');
17359
+
17360
+ salutationInput.clear();
17361
+ salutationInput.sendKeys('Greetings');
17362
+ nameInput.clear();
17363
+ nameInput.sendKeys('user');
17364
+
17365
+ expect(salutationElem.getText()).toBe('Greetings user!');
17031
17366
  });
17032
- </doc:scenario>
17367
+ </doc:protractor>
17033
17368
  </doc:example>
17034
17369
  */
17035
17370
  var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
@@ -17082,12 +17417,10 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
17082
17417
  }]);
17083
17418
  </file>
17084
17419
 
17085
- <file name="scenario.js">
17420
+ <file name="protractorTest.js">
17086
17421
  it('should check ng-bind-html', function() {
17087
- expect(using('.doc-example-live').binding('myHTML')).
17088
- toBe(
17089
- 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'
17090
- );
17422
+ expect(element(by.binding('myHTML')).getText()).toBe(
17423
+ 'I am an HTMLstring with links! and other stuff');
17091
17424
  });
17092
17425
  </file>
17093
17426
  </example>
@@ -17219,31 +17552,34 @@ function classDirective(name, selector) {
17219
17552
  color: red;
17220
17553
  }
17221
17554
  </file>
17222
- <file name="scenario.js">
17555
+ <file name="protractorTest.js">
17556
+ var ps = element.all(by.css('.doc-example-live p'));
17557
+
17223
17558
  it('should let you toggle the class', function() {
17224
17559
 
17225
- expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/);
17226
- expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/);
17560
+ expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
17561
+ expect(ps.first().getAttribute('class')).not.toMatch(/red/);
17227
17562
 
17228
- input('important').check();
17229
- expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/);
17563
+ element(by.model('important')).click();
17564
+ expect(ps.first().getAttribute('class')).toMatch(/bold/);
17230
17565
 
17231
- input('error').check();
17232
- expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/);
17566
+ element(by.model('error')).click();
17567
+ expect(ps.first().getAttribute('class')).toMatch(/red/);
17233
17568
  });
17234
17569
 
17235
17570
  it('should let you toggle string example', function() {
17236
- expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('');
17237
- input('style').enter('red');
17238
- expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red');
17571
+ expect(ps.get(1).getAttribute('class')).toBe('');
17572
+ element(by.model('style')).clear();
17573
+ element(by.model('style')).sendKeys('red');
17574
+ expect(ps.get(1).getAttribute('class')).toBe('red');
17239
17575
  });
17240
17576
 
17241
17577
  it('array example should have 3 classes', function() {
17242
- expect(element('.doc-example-live p:last').prop('className')).toBe('');
17243
- input('style1').enter('bold');
17244
- input('style2').enter('strike');
17245
- input('style3').enter('red');
17246
- expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red');
17578
+ expect(ps.last().getAttribute('class')).toBe('');
17579
+ element(by.model('style1')).sendKeys('bold');
17580
+ element(by.model('style2')).sendKeys('strike');
17581
+ element(by.model('style3')).sendKeys('red');
17582
+ expect(ps.last().getAttribute('class')).toBe('bold strike red');
17247
17583
  });
17248
17584
  </file>
17249
17585
  </example>
@@ -17254,8 +17590,8 @@ function classDirective(name, selector) {
17254
17590
 
17255
17591
  <example animations="true">
17256
17592
  <file name="index.html">
17257
- <input type="button" value="set" ng-click="myVar='my-class'">
17258
- <input type="button" value="clear" ng-click="myVar=''">
17593
+ <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
17594
+ <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
17259
17595
  <br>
17260
17596
  <span class="base-class" ng-class="myVar">Sample Text</span>
17261
17597
  </file>
@@ -17270,19 +17606,19 @@ function classDirective(name, selector) {
17270
17606
  font-size:3em;
17271
17607
  }
17272
17608
  </file>
17273
- <file name="scenario.js">
17609
+ <file name="protractorTest.js">
17274
17610
  it('should check ng-class', function() {
17275
- expect(element('.doc-example-live span').prop('className')).not().
17611
+ expect(element(by.css('.base-class')).getAttribute('class')).not.
17276
17612
  toMatch(/my-class/);
17277
17613
 
17278
- using('.doc-example-live').element(':button:first').click();
17614
+ element(by.id('setbtn')).click();
17279
17615
 
17280
- expect(element('.doc-example-live span').prop('className')).
17616
+ expect(element(by.css('.base-class')).getAttribute('class')).
17281
17617
  toMatch(/my-class/);
17282
17618
 
17283
- using('.doc-example-live').element(':button:last').click();
17619
+ element(by.id('clearbtn')).click();
17284
17620
 
17285
- expect(element('.doc-example-live span').prop('className')).not().
17621
+ expect(element(by.css('.base-class')).getAttribute('class')).not.
17286
17622
  toMatch(/my-class/);
17287
17623
  });
17288
17624
  </file>
@@ -17334,11 +17670,11 @@ var ngClassDirective = classDirective('', true);
17334
17670
  color: blue;
17335
17671
  }
17336
17672
  </file>
17337
- <file name="scenario.js">
17673
+ <file name="protractorTest.js">
17338
17674
  it('should check ng-class-odd and ng-class-even', function() {
17339
- expect(element('.doc-example-live li:first span').prop('className')).
17675
+ expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
17340
17676
  toMatch(/odd/);
17341
- expect(element('.doc-example-live li:last span').prop('className')).
17677
+ expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
17342
17678
  toMatch(/even/);
17343
17679
  });
17344
17680
  </file>
@@ -17382,11 +17718,11 @@ var ngClassOddDirective = classDirective('Odd', 0);
17382
17718
  color: blue;
17383
17719
  }
17384
17720
  </file>
17385
- <file name="scenario.js">
17721
+ <file name="protractorTest.js">
17386
17722
  it('should check ng-class-odd and ng-class-even', function() {
17387
- expect(element('.doc-example-live li:first span').prop('className')).
17723
+ expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
17388
17724
  toMatch(/odd/);
17389
- expect(element('.doc-example-live li:last span').prop('className')).
17725
+ expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
17390
17726
  toMatch(/even/);
17391
17727
  });
17392
17728
  </file>
@@ -17429,7 +17765,7 @@ var ngClassEvenDirective = classDirective('Even', 1);
17429
17765
  *
17430
17766
  * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
17431
17767
  * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
17432
- * class `ngCloak` in addition to the `ngCloak` directive as shown in the example below.
17768
+ * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below.
17433
17769
  *
17434
17770
  * @element ANY
17435
17771
  *
@@ -17439,14 +17775,14 @@ var ngClassEvenDirective = classDirective('Even', 1);
17439
17775
  <div id="template1" ng-cloak>{{ 'hello' }}</div>
17440
17776
  <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
17441
17777
  </doc:source>
17442
- <doc:scenario>
17778
+ <doc:protractor>
17443
17779
  it('should remove the template directive and css class', function() {
17444
- expect(element('.doc-example-live #template1').attr('ng-cloak')).
17445
- not().toBeDefined();
17446
- expect(element('.doc-example-live #template2').attr('ng-cloak')).
17447
- not().toBeDefined();
17780
+ expect($('.doc-example-live #template1').getAttribute('ng-cloak')).
17781
+ toBeNull();
17782
+ expect($('.doc-example-live #template2').getAttribute('ng-cloak')).
17783
+ toBeNull();
17448
17784
  });
17449
- </doc:scenario>
17785
+ </doc:protractor>
17450
17786
  </doc:example>
17451
17787
  *
17452
17788
  */
@@ -17539,22 +17875,36 @@ var ngCloakDirective = ngDirective({
17539
17875
  </ul>
17540
17876
  </div>
17541
17877
  </doc:source>
17542
- <doc:scenario>
17878
+ <doc:protractor>
17543
17879
  it('should check controller as', function() {
17544
- expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith');
17545
- expect(element('#ctrl-as-exmpl li:nth-child(1) input').val())
17546
- .toBe('408 555 1212');
17547
- expect(element('#ctrl-as-exmpl li:nth-child(2) input').val())
17548
- .toBe('john.smith@example.org');
17549
-
17550
- element('#ctrl-as-exmpl li:first a:contains("clear")').click();
17551
- expect(element('#ctrl-as-exmpl li:first input').val()).toBe('');
17552
-
17553
- element('#ctrl-as-exmpl li:last a:contains("add")').click();
17554
- expect(element('#ctrl-as-exmpl li:nth-child(3) input').val())
17555
- .toBe('yourname@example.org');
17880
+ var container = element(by.id('ctrl-as-exmpl'));
17881
+
17882
+ expect(container.findElement(by.model('settings.name'))
17883
+ .getAttribute('value')).toBe('John Smith');
17884
+
17885
+ var firstRepeat =
17886
+ container.findElement(by.repeater('contact in settings.contacts').row(0));
17887
+ var secondRepeat =
17888
+ container.findElement(by.repeater('contact in settings.contacts').row(1));
17889
+
17890
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
17891
+ .toBe('408 555 1212');
17892
+ expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
17893
+ .toBe('john.smith@example.org');
17894
+
17895
+ firstRepeat.findElement(by.linkText('clear')).click()
17896
+
17897
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
17898
+ .toBe('');
17899
+
17900
+ container.findElement(by.linkText('add')).click();
17901
+
17902
+ expect(container.findElement(by.repeater('contact in settings.contacts').row(2))
17903
+ .findElement(by.model('contact.value'))
17904
+ .getAttribute('value'))
17905
+ .toBe('yourname@example.org');
17556
17906
  });
17557
- </doc:scenario>
17907
+ </doc:protractor>
17558
17908
  </doc:example>
17559
17909
  <doc:example>
17560
17910
  <doc:source>
@@ -17602,22 +17952,36 @@ var ngCloakDirective = ngDirective({
17602
17952
  </ul>
17603
17953
  </div>
17604
17954
  </doc:source>
17605
- <doc:scenario>
17955
+ <doc:protractor>
17606
17956
  it('should check controller', function() {
17607
- expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith');
17608
- expect(element('#ctrl-exmpl li:nth-child(1) input').val())
17609
- .toBe('408 555 1212');
17610
- expect(element('#ctrl-exmpl li:nth-child(2) input').val())
17611
- .toBe('john.smith@example.org');
17612
-
17613
- element('#ctrl-exmpl li:first a:contains("clear")').click();
17614
- expect(element('#ctrl-exmpl li:first input').val()).toBe('');
17615
-
17616
- element('#ctrl-exmpl li:last a:contains("add")').click();
17617
- expect(element('#ctrl-exmpl li:nth-child(3) input').val())
17618
- .toBe('yourname@example.org');
17957
+ var container = element(by.id('ctrl-exmpl'));
17958
+
17959
+ expect(container.findElement(by.model('name'))
17960
+ .getAttribute('value')).toBe('John Smith');
17961
+
17962
+ var firstRepeat =
17963
+ container.findElement(by.repeater('contact in contacts').row(0));
17964
+ var secondRepeat =
17965
+ container.findElement(by.repeater('contact in contacts').row(1));
17966
+
17967
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
17968
+ .toBe('408 555 1212');
17969
+ expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
17970
+ .toBe('john.smith@example.org');
17971
+
17972
+ firstRepeat.findElement(by.linkText('clear')).click()
17973
+
17974
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
17975
+ .toBe('');
17976
+
17977
+ container.findElement(by.linkText('add')).click();
17978
+
17979
+ expect(container.findElement(by.repeater('contact in contacts').row(2))
17980
+ .findElement(by.model('contact.value'))
17981
+ .getAttribute('value'))
17982
+ .toBe('yourname@example.org');
17619
17983
  });
17620
- </doc:scenario>
17984
+ </doc:protractor>
17621
17985
  </doc:example>
17622
17986
 
17623
17987
  */
@@ -17680,6 +18044,7 @@ var ngControllerDirective = [function() {
17680
18044
  * an element is clicked.
17681
18045
  *
17682
18046
  * @element ANY
18047
+ * @priority 0
17683
18048
  * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
17684
18049
  * click. (Event object is available as `$event`)
17685
18050
  *
@@ -17691,13 +18056,13 @@ var ngControllerDirective = [function() {
17691
18056
  </button>
17692
18057
  count: {{count}}
17693
18058
  </doc:source>
17694
- <doc:scenario>
18059
+ <doc:protractor>
17695
18060
  it('should check ng-click', function() {
17696
- expect(binding('count')).toBe('0');
17697
- element('.doc-example-live :button').click();
17698
- expect(binding('count')).toBe('1');
18061
+ expect(element(by.binding('count')).getText()).toMatch('0');
18062
+ element(by.css('.doc-example-live button')).click();
18063
+ expect(element(by.binding('count')).getText()).toMatch('1');
17699
18064
  });
17700
- </doc:scenario>
18065
+ </doc:protractor>
17701
18066
  </doc:example>
17702
18067
  */
17703
18068
  /*
@@ -17736,11 +18101,19 @@ forEach(
17736
18101
  * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
17737
18102
  *
17738
18103
  * @element ANY
18104
+ * @priority 0
17739
18105
  * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
17740
18106
  * a dblclick. (The Event object is available as `$event`)
17741
18107
  *
17742
18108
  * @example
17743
- * See {@link ng.directive:ngClick ngClick}
18109
+ <doc:example>
18110
+ <doc:source>
18111
+ <button ng-dblclick="count = count + 1" ng-init="count=0">
18112
+ Increment (on double click)
18113
+ </button>
18114
+ count: {{count}}
18115
+ </doc:source>
18116
+ </doc:example>
17744
18117
  */
17745
18118
 
17746
18119
 
@@ -17752,11 +18125,19 @@ forEach(
17752
18125
  * The ngMousedown directive allows you to specify custom behavior on mousedown event.
17753
18126
  *
17754
18127
  * @element ANY
18128
+ * @priority 0
17755
18129
  * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
17756
18130
  * mousedown. (Event object is available as `$event`)
17757
18131
  *
17758
18132
  * @example
17759
- * See {@link ng.directive:ngClick ngClick}
18133
+ <doc:example>
18134
+ <doc:source>
18135
+ <button ng-mousedown="count = count + 1" ng-init="count=0">
18136
+ Increment (on mouse down)
18137
+ </button>
18138
+ count: {{count}}
18139
+ </doc:source>
18140
+ </doc:example>
17760
18141
  */
17761
18142
 
17762
18143
 
@@ -17768,11 +18149,19 @@ forEach(
17768
18149
  * Specify custom behavior on mouseup event.
17769
18150
  *
17770
18151
  * @element ANY
18152
+ * @priority 0
17771
18153
  * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
17772
18154
  * mouseup. (Event object is available as `$event`)
17773
18155
  *
17774
18156
  * @example
17775
- * See {@link ng.directive:ngClick ngClick}
18157
+ <doc:example>
18158
+ <doc:source>
18159
+ <button ng-mouseup="count = count + 1" ng-init="count=0">
18160
+ Increment (on mouse up)
18161
+ </button>
18162
+ count: {{count}}
18163
+ </doc:source>
18164
+ </doc:example>
17776
18165
  */
17777
18166
 
17778
18167
  /**
@@ -17783,11 +18172,19 @@ forEach(
17783
18172
  * Specify custom behavior on mouseover event.
17784
18173
  *
17785
18174
  * @element ANY
18175
+ * @priority 0
17786
18176
  * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
17787
18177
  * mouseover. (Event object is available as `$event`)
17788
18178
  *
17789
18179
  * @example
17790
- * See {@link ng.directive:ngClick ngClick}
18180
+ <doc:example>
18181
+ <doc:source>
18182
+ <button ng-mouseover="count = count + 1" ng-init="count=0">
18183
+ Increment (when mouse is over)
18184
+ </button>
18185
+ count: {{count}}
18186
+ </doc:source>
18187
+ </doc:example>
17791
18188
  */
17792
18189
 
17793
18190
 
@@ -17799,11 +18196,19 @@ forEach(
17799
18196
  * Specify custom behavior on mouseenter event.
17800
18197
  *
17801
18198
  * @element ANY
18199
+ * @priority 0
17802
18200
  * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
17803
18201
  * mouseenter. (Event object is available as `$event`)
17804
18202
  *
17805
18203
  * @example
17806
- * See {@link ng.directive:ngClick ngClick}
18204
+ <doc:example>
18205
+ <doc:source>
18206
+ <button ng-mouseenter="count = count + 1" ng-init="count=0">
18207
+ Increment (when mouse enters)
18208
+ </button>
18209
+ count: {{count}}
18210
+ </doc:source>
18211
+ </doc:example>
17807
18212
  */
17808
18213
 
17809
18214
 
@@ -17815,11 +18220,19 @@ forEach(
17815
18220
  * Specify custom behavior on mouseleave event.
17816
18221
  *
17817
18222
  * @element ANY
18223
+ * @priority 0
17818
18224
  * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
17819
18225
  * mouseleave. (Event object is available as `$event`)
17820
18226
  *
17821
18227
  * @example
17822
- * See {@link ng.directive:ngClick ngClick}
18228
+ <doc:example>
18229
+ <doc:source>
18230
+ <button ng-mouseleave="count = count + 1" ng-init="count=0">
18231
+ Increment (when mouse leaves)
18232
+ </button>
18233
+ count: {{count}}
18234
+ </doc:source>
18235
+ </doc:example>
17823
18236
  */
17824
18237
 
17825
18238
 
@@ -17831,11 +18244,19 @@ forEach(
17831
18244
  * Specify custom behavior on mousemove event.
17832
18245
  *
17833
18246
  * @element ANY
18247
+ * @priority 0
17834
18248
  * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
17835
18249
  * mousemove. (Event object is available as `$event`)
17836
18250
  *
17837
18251
  * @example
17838
- * See {@link ng.directive:ngClick ngClick}
18252
+ <doc:example>
18253
+ <doc:source>
18254
+ <button ng-mousemove="count = count + 1" ng-init="count=0">
18255
+ Increment (when mouse moves)
18256
+ </button>
18257
+ count: {{count}}
18258
+ </doc:source>
18259
+ </doc:example>
17839
18260
  */
17840
18261
 
17841
18262
 
@@ -17847,11 +18268,17 @@ forEach(
17847
18268
  * Specify custom behavior on keydown event.
17848
18269
  *
17849
18270
  * @element ANY
18271
+ * @priority 0
17850
18272
  * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
17851
18273
  * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
17852
18274
  *
17853
18275
  * @example
17854
- * See {@link ng.directive:ngClick ngClick}
18276
+ <doc:example>
18277
+ <doc:source>
18278
+ <input ng-keydown="count = count + 1" ng-init="count=0">
18279
+ key down count: {{count}}
18280
+ </doc:source>
18281
+ </doc:example>
17855
18282
  */
17856
18283
 
17857
18284
 
@@ -17863,11 +18290,17 @@ forEach(
17863
18290
  * Specify custom behavior on keyup event.
17864
18291
  *
17865
18292
  * @element ANY
18293
+ * @priority 0
17866
18294
  * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
17867
18295
  * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
17868
18296
  *
17869
18297
  * @example
17870
- * See {@link ng.directive:ngClick ngClick}
18298
+ <doc:example>
18299
+ <doc:source>
18300
+ <input ng-keyup="count = count + 1" ng-init="count=0">
18301
+ key up count: {{count}}
18302
+ </doc:source>
18303
+ </doc:example>
17871
18304
  */
17872
18305
 
17873
18306
 
@@ -17883,7 +18316,12 @@ forEach(
17883
18316
  * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
17884
18317
  *
17885
18318
  * @example
17886
- * See {@link ng.directive:ngClick ngClick}
18319
+ <doc:example>
18320
+ <doc:source>
18321
+ <input ng-keypress="count = count + 1" ng-init="count=0">
18322
+ key press count: {{count}}
18323
+ </doc:source>
18324
+ </doc:example>
17887
18325
  */
17888
18326
 
17889
18327
 
@@ -17895,10 +18333,11 @@ forEach(
17895
18333
  * Enables binding angular expressions to onsubmit events.
17896
18334
  *
17897
18335
  * Additionally it prevents the default action (which for form means sending the request to the
17898
- * server and reloading the current page) **but only if the form does not contain an `action`
17899
- * attribute**.
18336
+ * server and reloading the current page), but only if the form does not contain `action`,
18337
+ * `data-action`, or `x-action` attributes.
17900
18338
  *
17901
18339
  * @element form
18340
+ * @priority 0
17902
18341
  * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`)
17903
18342
  *
17904
18343
  * @example
@@ -17923,20 +18362,20 @@ forEach(
17923
18362
  <pre>list={{list}}</pre>
17924
18363
  </form>
17925
18364
  </doc:source>
17926
- <doc:scenario>
18365
+ <doc:protractor>
17927
18366
  it('should check ng-submit', function() {
17928
- expect(binding('list')).toBe('[]');
17929
- element('.doc-example-live #submit').click();
17930
- expect(binding('list')).toBe('["hello"]');
17931
- expect(input('text').val()).toBe('');
18367
+ expect(element(by.binding('list')).getText()).toBe('list=[]');
18368
+ element(by.css('.doc-example-live #submit')).click();
18369
+ expect(element(by.binding('list')).getText()).toContain('hello');
18370
+ expect(element(by.input('text')).getAttribute('value')).toBe('');
17932
18371
  });
17933
18372
  it('should ignore empty strings', function() {
17934
- expect(binding('list')).toBe('[]');
17935
- element('.doc-example-live #submit').click();
17936
- element('.doc-example-live #submit').click();
17937
- expect(binding('list')).toBe('["hello"]');
17938
- });
17939
- </doc:scenario>
18373
+ expect(element(by.binding('list')).getText()).toBe('list=[]');
18374
+ element(by.css('.doc-example-live #submit')).click();
18375
+ element(by.css('.doc-example-live #submit')).click();
18376
+ expect(element(by.binding('list')).getText()).toContain('hello');
18377
+ });
18378
+ </doc:protractor>
17940
18379
  </doc:example>
17941
18380
  */
17942
18381
 
@@ -17948,6 +18387,7 @@ forEach(
17948
18387
  * Specify custom behavior on focus event.
17949
18388
  *
17950
18389
  * @element window, input, select, textarea, a
18390
+ * @priority 0
17951
18391
  * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
17952
18392
  * focus. (Event object is available as `$event`)
17953
18393
  *
@@ -17963,6 +18403,7 @@ forEach(
17963
18403
  * Specify custom behavior on blur event.
17964
18404
  *
17965
18405
  * @element window, input, select, textarea, a
18406
+ * @priority 0
17966
18407
  * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
17967
18408
  * blur. (Event object is available as `$event`)
17968
18409
  *
@@ -17978,11 +18419,17 @@ forEach(
17978
18419
  * Specify custom behavior on copy event.
17979
18420
  *
17980
18421
  * @element window, input, select, textarea, a
18422
+ * @priority 0
17981
18423
  * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
17982
18424
  * copy. (Event object is available as `$event`)
17983
18425
  *
17984
18426
  * @example
17985
- * See {@link ng.directive:ngClick ngClick}
18427
+ <doc:example>
18428
+ <doc:source>
18429
+ <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
18430
+ copied: {{copied}}
18431
+ </doc:source>
18432
+ </doc:example>
17986
18433
  */
17987
18434
 
17988
18435
  /**
@@ -17993,11 +18440,17 @@ forEach(
17993
18440
  * Specify custom behavior on cut event.
17994
18441
  *
17995
18442
  * @element window, input, select, textarea, a
18443
+ * @priority 0
17996
18444
  * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
17997
18445
  * cut. (Event object is available as `$event`)
17998
18446
  *
17999
18447
  * @example
18000
- * See {@link ng.directive:ngClick ngClick}
18448
+ <doc:example>
18449
+ <doc:source>
18450
+ <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
18451
+ cut: {{cut}}
18452
+ </doc:source>
18453
+ </doc:example>
18001
18454
  */
18002
18455
 
18003
18456
  /**
@@ -18008,11 +18461,17 @@ forEach(
18008
18461
  * Specify custom behavior on paste event.
18009
18462
  *
18010
18463
  * @element window, input, select, textarea, a
18464
+ * @priority 0
18011
18465
  * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
18012
18466
  * paste. (Event object is available as `$event`)
18013
18467
  *
18014
18468
  * @example
18015
- * See {@link ng.directive:ngClick ngClick}
18469
+ <doc:example>
18470
+ <doc:source>
18471
+ <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
18472
+ pasted: {{paste}}
18473
+ </doc:source>
18474
+ </doc:example>
18016
18475
  */
18017
18476
 
18018
18477
  /**
@@ -18074,9 +18533,6 @@ forEach(
18074
18533
  padding:10px;
18075
18534
  }
18076
18535
 
18077
- /&#42;
18078
- The transition styles can also be placed on the CSS base class above
18079
- &#42;/
18080
18536
  .animate-if.ng-enter, .animate-if.ng-leave {
18081
18537
  -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
18082
18538
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
@@ -18246,19 +18702,33 @@ var ngIfDirective = ['$animate', function($animate) {
18246
18702
  top:50px;
18247
18703
  }
18248
18704
  </file>
18249
- <file name="scenario.js">
18705
+ <file name="protractorTest.js">
18706
+ var templateSelect = element(by.model('template'));
18707
+ var includeElem = element(by.css('.doc-example-live [ng-include]'));
18708
+
18250
18709
  it('should load template1.html', function() {
18251
- expect(element('.doc-example-live [ng-include]').text()).
18252
- toMatch(/Content of template1.html/);
18710
+ expect(includeElem.getText()).toMatch(/Content of template1.html/);
18253
18711
  });
18712
+
18254
18713
  it('should load template2.html', function() {
18255
- select('template').option('1');
18256
- expect(element('.doc-example-live [ng-include]').text()).
18257
- toMatch(/Content of template2.html/);
18714
+ if (browser.params.browser == 'firefox') {
18715
+ // Firefox can't handle using selects
18716
+ // See https://github.com/angular/protractor/issues/480
18717
+ return;
18718
+ }
18719
+ templateSelect.click();
18720
+ templateSelect.element.all(by.css('option')).get(2).click();
18721
+ expect(includeElem.getText()).toMatch(/Content of template2.html/);
18258
18722
  });
18723
+
18259
18724
  it('should change to blank', function() {
18260
- select('template').option('');
18261
- expect(element('.doc-example-live [ng-include]')).toBe(undefined);
18725
+ if (browser.params.browser == 'firefox') {
18726
+ // Firefox can't handle using selects
18727
+ return;
18728
+ }
18729
+ templateSelect.click();
18730
+ templateSelect.element.all(by.css('option')).get(0).click();
18731
+ expect(includeElem.isPresent()).toBe(false);
18262
18732
  });
18263
18733
  </file>
18264
18734
  </example>
@@ -18384,11 +18854,18 @@ var ngIncludeFillContentDirective = ['$compile',
18384
18854
  * current scope.
18385
18855
  *
18386
18856
  * <div class="alert alert-error">
18387
- * The only appropriate use of `ngInit` for aliasing special properties of
18857
+ * The only appropriate use of `ngInit` is for aliasing special properties of
18388
18858
  * {@link api/ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you
18389
18859
  * should use {@link guide/controller controllers} rather than `ngInit`
18390
18860
  * to initialize values on a scope.
18391
18861
  * </div>
18862
+ * <div class="alert alert-warning">
18863
+ * **Note**: If you have assignment in `ngInit` along with {@link api/ng.$filter `$filter`}, make
18864
+ * sure you have parenthesis for correct precedence:
18865
+ * <pre class="prettyprint">
18866
+ * <div ng-init="test1 = (data | orderBy:'name')"></div>
18867
+ * </pre>
18868
+ * </div>
18392
18869
  *
18393
18870
  * @priority 450
18394
18871
  *
@@ -18411,15 +18888,15 @@ var ngIncludeFillContentDirective = ['$compile',
18411
18888
  </div>
18412
18889
  </div>
18413
18890
  </doc:source>
18414
- <doc:scenario>
18891
+ <doc:protractor>
18415
18892
  it('should alias index positions', function() {
18416
- expect(element('.example-init').text())
18417
- .toBe('list[ 0 ][ 0 ] = a;' +
18418
- 'list[ 0 ][ 1 ] = b;' +
18419
- 'list[ 1 ][ 0 ] = c;' +
18420
- 'list[ 1 ][ 1 ] = d;');
18893
+ var elements = element.all(by.css('.example-init'));
18894
+ expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
18895
+ expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
18896
+ expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
18897
+ expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
18421
18898
  });
18422
- </doc:scenario>
18899
+ </doc:protractor>
18423
18900
  </doc:example>
18424
18901
  */
18425
18902
  var ngInitDirective = ngDirective({
@@ -18457,13 +18934,12 @@ var ngInitDirective = ngDirective({
18457
18934
  <div>Normal: {{1 + 2}}</div>
18458
18935
  <div ng-non-bindable>Ignored: {{1 + 2}}</div>
18459
18936
  </doc:source>
18460
- <doc:scenario>
18937
+ <doc:protractor>
18461
18938
  it('should check ng-non-bindable', function() {
18462
- expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
18463
- expect(using('.doc-example-live').element('div:last').text()).
18464
- toMatch(/1 \+ 2/);
18939
+ expect(element(by.binding('1 + 2')).getText()).toContain('3');
18940
+ expect(element.all(by.css('.doc-example-live div')).last().getText()).toMatch(/1 \+ 2/);
18465
18941
  });
18466
- </doc:scenario>
18942
+ </doc:protractor>
18467
18943
  </doc:example>
18468
18944
  */
18469
18945
  var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
@@ -18591,49 +19067,53 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
18591
19067
  </ng-pluralize>
18592
19068
  </div>
18593
19069
  </doc:source>
18594
- <doc:scenario>
19070
+ <doc:protractor>
18595
19071
  it('should show correct pluralized string', function() {
18596
- expect(element('.doc-example-live ng-pluralize:first').text()).
18597
- toBe('1 person is viewing.');
18598
- expect(element('.doc-example-live ng-pluralize:last').text()).
18599
- toBe('Igor is viewing.');
18600
-
18601
- using('.doc-example-live').input('personCount').enter('0');
18602
- expect(element('.doc-example-live ng-pluralize:first').text()).
18603
- toBe('Nobody is viewing.');
18604
- expect(element('.doc-example-live ng-pluralize:last').text()).
18605
- toBe('Nobody is viewing.');
18606
-
18607
- using('.doc-example-live').input('personCount').enter('2');
18608
- expect(element('.doc-example-live ng-pluralize:first').text()).
18609
- toBe('2 people are viewing.');
18610
- expect(element('.doc-example-live ng-pluralize:last').text()).
18611
- toBe('Igor and Misko are viewing.');
18612
-
18613
- using('.doc-example-live').input('personCount').enter('3');
18614
- expect(element('.doc-example-live ng-pluralize:first').text()).
18615
- toBe('3 people are viewing.');
18616
- expect(element('.doc-example-live ng-pluralize:last').text()).
18617
- toBe('Igor, Misko and one other person are viewing.');
18618
-
18619
- using('.doc-example-live').input('personCount').enter('4');
18620
- expect(element('.doc-example-live ng-pluralize:first').text()).
18621
- toBe('4 people are viewing.');
18622
- expect(element('.doc-example-live ng-pluralize:last').text()).
18623
- toBe('Igor, Misko and 2 other people are viewing.');
18624
- });
19072
+ var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
19073
+ var withOffset = element.all(by.css('ng-pluralize')).get(1);
19074
+ var countInput = element(by.model('personCount'));
19075
+
19076
+ expect(withoutOffset.getText()).toEqual('1 person is viewing.');
19077
+ expect(withOffset.getText()).toEqual('Igor is viewing.');
19078
+
19079
+ countInput.clear();
19080
+ countInput.sendKeys('0');
19081
+
19082
+ expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
19083
+ expect(withOffset.getText()).toEqual('Nobody is viewing.');
19084
+
19085
+ countInput.clear();
19086
+ countInput.sendKeys('2');
18625
19087
 
18626
- it('should show data-binded names', function() {
18627
- using('.doc-example-live').input('personCount').enter('4');
18628
- expect(element('.doc-example-live ng-pluralize:last').text()).
18629
- toBe('Igor, Misko and 2 other people are viewing.');
19088
+ expect(withoutOffset.getText()).toEqual('2 people are viewing.');
19089
+ expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
18630
19090
 
18631
- using('.doc-example-live').input('person1').enter('Di');
18632
- using('.doc-example-live').input('person2').enter('Vojta');
18633
- expect(element('.doc-example-live ng-pluralize:last').text()).
18634
- toBe('Di, Vojta and 2 other people are viewing.');
19091
+ countInput.clear();
19092
+ countInput.sendKeys('3');
19093
+
19094
+ expect(withoutOffset.getText()).toEqual('3 people are viewing.');
19095
+ expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
19096
+
19097
+ countInput.clear();
19098
+ countInput.sendKeys('4');
19099
+
19100
+ expect(withoutOffset.getText()).toEqual('4 people are viewing.');
19101
+ expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
18635
19102
  });
18636
- </doc:scenario>
19103
+ it('should show data-bound names', function() {
19104
+ var withOffset = element.all(by.css('ng-pluralize')).get(1);
19105
+ var personCount = element(by.model('personCount'));
19106
+ var person1 = element(by.model('person1'));
19107
+ var person2 = element(by.model('person2'));
19108
+ personCount.clear();
19109
+ personCount.sendKeys('4');
19110
+ person1.clear();
19111
+ person1.sendKeys('Di');
19112
+ person2.clear();
19113
+ person2.sendKeys('Vojta');
19114
+ expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
19115
+ });
19116
+ </doc:protractor>
18637
19117
  </doc:example>
18638
19118
  */
18639
19119
  var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
@@ -18700,6 +19180,8 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
18700
19180
  * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
18701
19181
  * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
18702
19182
  *
19183
+ * Creating aliases for these properties is possible with {@link api/ng.directive:ngInit `ngInit`}.
19184
+ * This may be useful when, for instance, nesting ngRepeats.
18703
19185
  *
18704
19186
  * # Special repeat start and end points
18705
19187
  * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
@@ -18850,25 +19332,27 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
18850
19332
  max-height:40px;
18851
19333
  }
18852
19334
  </file>
18853
- <file name="scenario.js">
18854
- it('should render initial data set', function() {
18855
- var r = using('.doc-example-live').repeater('ul li');
18856
- expect(r.count()).toBe(10);
18857
- expect(r.row(0)).toEqual(["1","John","25"]);
18858
- expect(r.row(1)).toEqual(["2","Jessie","30"]);
18859
- expect(r.row(9)).toEqual(["10","Samantha","60"]);
18860
- expect(binding('friends.length')).toBe("10");
18861
- });
19335
+ <file name="protractorTest.js">
19336
+ var friends = element(by.css('.doc-example-live'))
19337
+ .element.all(by.repeater('friend in friends'));
19338
+
19339
+ it('should render initial data set', function() {
19340
+ expect(friends.count()).toBe(10);
19341
+ expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
19342
+ expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
19343
+ expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
19344
+ expect(element(by.binding('friends.length')).getText())
19345
+ .toMatch("I have 10 friends. They are:");
19346
+ });
18862
19347
 
18863
19348
  it('should update repeater when filter predicate changes', function() {
18864
- var r = using('.doc-example-live').repeater('ul li');
18865
- expect(r.count()).toBe(10);
19349
+ expect(friends.count()).toBe(10);
18866
19350
 
18867
- input('q').enter('ma');
19351
+ element(by.css('.doc-example-live')).element(by.model('q')).sendKeys('ma');
18868
19352
 
18869
- expect(r.count()).toBe(2);
18870
- expect(r.row(0)).toEqual(["1","Mary","28"]);
18871
- expect(r.row(1)).toEqual(["2","Samantha","60"]);
19353
+ expect(friends.count()).toBe(2);
19354
+ expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
19355
+ expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
18872
19356
  });
18873
19357
  </file>
18874
19358
  </example>
@@ -18883,7 +19367,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
18883
19367
  $$tlb: true,
18884
19368
  link: function($scope, $element, $attr, ctrl, $transclude){
18885
19369
  var expression = $attr.ngRepeat;
18886
- var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
19370
+ var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),
18887
19371
  trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn,
18888
19372
  lhs, rhs, valueIdentifier, keyIdentifier,
18889
19373
  hashFnLocals = {$id: hashKey};
@@ -18895,7 +19379,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
18895
19379
 
18896
19380
  lhs = match[1];
18897
19381
  rhs = match[2];
18898
- trackByExp = match[4];
19382
+ trackByExp = match[3];
18899
19383
 
18900
19384
  if (trackByExp) {
18901
19385
  trackByExpGetter = $parse(trackByExp);
@@ -19122,6 +19606,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
19122
19606
  *
19123
19607
  * Just remember to include the important flag so the CSS override will function.
19124
19608
  *
19609
+ * <div class="alert alert-warning">
19610
+ * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br />
19611
+ * "f" / "0" / "false" / "no" / "n" / "[]"
19612
+ * </div>
19613
+ *
19125
19614
  * ## A note about animations with ngShow
19126
19615
  *
19127
19616
  * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
@@ -19197,16 +19686,19 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
19197
19686
  background:white;
19198
19687
  }
19199
19688
  </file>
19200
- <file name="scenario.js">
19201
- it('should check ng-show / ng-hide', function() {
19202
- expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
19203
- expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
19689
+ <file name="protractorTest.js">
19690
+ var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up'));
19691
+ var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down'));
19204
19692
 
19205
- input('checked').check();
19693
+ it('should check ng-show / ng-hide', function() {
19694
+ expect(thumbsUp.isDisplayed()).toBeFalsy();
19695
+ expect(thumbsDown.isDisplayed()).toBeTruthy();
19206
19696
 
19207
- expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
19208
- expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
19209
- });
19697
+ element(by.model('checked')).click();
19698
+
19699
+ expect(thumbsUp.isDisplayed()).toBeTruthy();
19700
+ expect(thumbsDown.isDisplayed()).toBeFalsy();
19701
+ });
19210
19702
  </file>
19211
19703
  </example>
19212
19704
  */
@@ -19270,6 +19762,11 @@ var ngShowDirective = ['$animate', function($animate) {
19270
19762
  * </pre>
19271
19763
  *
19272
19764
  * Just remember to include the important flag so the CSS override will function.
19765
+ *
19766
+ * <div class="alert alert-warning">
19767
+ * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br />
19768
+ * "f" / "0" / "false" / "no" / "n" / "[]"
19769
+ * </div>
19273
19770
  *
19274
19771
  * ## A note about animations with ngHide
19275
19772
  *
@@ -19346,16 +19843,19 @@ var ngShowDirective = ['$animate', function($animate) {
19346
19843
  background:white;
19347
19844
  }
19348
19845
  </file>
19349
- <file name="scenario.js">
19350
- it('should check ng-show / ng-hide', function() {
19351
- expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1);
19352
- expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1);
19846
+ <file name="protractorTest.js">
19847
+ var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up'));
19848
+ var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down'));
19353
19849
 
19354
- input('checked').check();
19850
+ it('should check ng-show / ng-hide', function() {
19851
+ expect(thumbsUp.isDisplayed()).toBeFalsy();
19852
+ expect(thumbsDown.isDisplayed()).toBeTruthy();
19355
19853
 
19356
- expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1);
19357
- expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1);
19358
- });
19854
+ element(by.model('checked')).click();
19855
+
19856
+ expect(thumbsUp.isDisplayed()).toBeTruthy();
19857
+ expect(thumbsDown.isDisplayed()).toBeFalsy();
19858
+ });
19359
19859
  </file>
19360
19860
  </example>
19361
19861
  */
@@ -19394,13 +19894,15 @@ var ngHideDirective = ['$animate', function($animate) {
19394
19894
  color: black;
19395
19895
  }
19396
19896
  </file>
19397
- <file name="scenario.js">
19897
+ <file name="protractorTest.js">
19898
+ var colorSpan = element(by.css('.doc-example-live span'));
19899
+
19398
19900
  it('should check ng-style', function() {
19399
- expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
19400
- element('.doc-example-live :button[value=set]').click();
19401
- expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
19402
- element('.doc-example-live :button[value=clear]').click();
19403
- expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
19901
+ expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
19902
+ element(by.css('.doc-example-live input[value=set]')).click();
19903
+ expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
19904
+ element(by.css('.doc-example-live input[value=clear]')).click();
19905
+ expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
19404
19906
  });
19405
19907
  </file>
19406
19908
  </example>
@@ -19425,7 +19927,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
19425
19927
  * as specified in the template.
19426
19928
  *
19427
19929
  * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
19428
- * from the template cache), `ngSwitch` simply choses one of the nested elements and makes it visible based on which element
19930
+ * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
19429
19931
  * matches the value obtained from the evaluated expression. In other words, you define a container element
19430
19932
  * (where you place the directive), place an expression on the **`on="..."` attribute**
19431
19933
  * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
@@ -19521,17 +20023,20 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
19521
20023
  top:0;
19522
20024
  }
19523
20025
  </file>
19524
- <file name="scenario.js">
20026
+ <file name="protractorTest.js">
20027
+ var switchElem = element(by.css('.doc-example-live [ng-switch]'));
20028
+ var select = element(by.model('selection'));
20029
+
19525
20030
  it('should start in settings', function() {
19526
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
20031
+ expect(switchElem.getText()).toMatch(/Settings Div/);
19527
20032
  });
19528
20033
  it('should change to home', function() {
19529
- select('selection').option('home');
19530
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
20034
+ select.element.all(by.css('option')).get(1).click();
20035
+ expect(switchElem.getText()).toMatch(/Home Span/);
19531
20036
  });
19532
20037
  it('should select default', function() {
19533
- select('selection').option('other');
19534
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
20038
+ select.element.all(by.css('option')).get(2).click();
20039
+ expect(switchElem.getText()).toMatch(/default/);
19535
20040
  });
19536
20041
  </file>
19537
20042
  </example>
@@ -19582,11 +20087,9 @@ var ngSwitchWhenDirective = ngDirective({
19582
20087
  transclude: 'element',
19583
20088
  priority: 800,
19584
20089
  require: '^ngSwitch',
19585
- compile: function(element, attrs) {
19586
- return function(scope, element, attr, ctrl, $transclude) {
19587
- ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
19588
- ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
19589
- };
20090
+ link: function(scope, element, attrs, ctrl, $transclude) {
20091
+ ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
20092
+ ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
19590
20093
  }
19591
20094
  });
19592
20095
 
@@ -19640,35 +20143,32 @@ var ngSwitchDefaultDirective = ngDirective({
19640
20143
  <pane title="{{title}}">{{text}}</pane>
19641
20144
  </div>
19642
20145
  </doc:source>
19643
- <doc:scenario>
20146
+ <doc:protractor>
19644
20147
  it('should have transcluded', function() {
19645
- input('title').enter('TITLE');
19646
- input('text').enter('TEXT');
19647
- expect(binding('title')).toEqual('TITLE');
19648
- expect(binding('text')).toEqual('TEXT');
20148
+ var titleElement = element(by.model('title'));
20149
+ titleElement.clear();
20150
+ titleElement.sendKeys('TITLE');
20151
+ var textElement = element(by.model('text'));
20152
+ textElement.clear();
20153
+ textElement.sendKeys('TEXT');
20154
+ expect(element(by.binding('title')).getText()).toEqual('TITLE');
20155
+ expect(element(by.binding('text')).getText()).toEqual('TEXT');
19649
20156
  });
19650
- </doc:scenario>
20157
+ </doc:protractor>
19651
20158
  </doc:example>
19652
20159
  *
19653
20160
  */
19654
20161
  var ngTranscludeDirective = ngDirective({
19655
- controller: ['$element', '$transclude', function($element, $transclude) {
20162
+ link: function($scope, $element, $attrs, controller, $transclude) {
19656
20163
  if (!$transclude) {
19657
20164
  throw minErr('ngTransclude')('orphan',
19658
- 'Illegal use of ngTransclude directive in the template! ' +
19659
- 'No parent directive that requires a transclusion found. ' +
19660
- 'Element: {0}',
19661
- startingTag($element));
20165
+ 'Illegal use of ngTransclude directive in the template! ' +
20166
+ 'No parent directive that requires a transclusion found. ' +
20167
+ 'Element: {0}',
20168
+ startingTag($element));
19662
20169
  }
19663
-
19664
- // remember the transclusion fn but call it during linking so that we don't process transclusion before directives on
19665
- // the parent element even when the transclusion replaces the current element. (we can't use priority here because
19666
- // that applies only to compile fns and not controllers
19667
- this.$transclude = $transclude;
19668
- }],
19669
-
19670
- link: function($scope, $element, $attrs, controller) {
19671
- controller.$transclude(function(clone) {
20170
+
20171
+ $transclude(function(clone) {
19672
20172
  $element.empty();
19673
20173
  $element.append(clone);
19674
20174
  });
@@ -19681,10 +20181,14 @@ var ngTranscludeDirective = ngDirective({
19681
20181
  * @restrict E
19682
20182
  *
19683
20183
  * @description
19684
- * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
19685
- * template can be used by `ngInclude`, `ngView` or directive templates.
20184
+ * Load the content of a `<script>` element into {@link api/ng.$templateCache `$templateCache`}, so that the
20185
+ * template can be used by {@link api/ng.directive:ngInclude `ngInclude`},
20186
+ * {@link api/ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
20187
+ * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
20188
+ * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
19686
20189
  *
19687
- * @param {'text/ng-template'} type must be set to `'text/ng-template'`
20190
+ * @param {'text/ng-template'} type Must be set to `'text/ng-template'`.
20191
+ * @param {string} id Cache name of the template.
19688
20192
  *
19689
20193
  * @example
19690
20194
  <doc:example>
@@ -19696,12 +20200,12 @@ var ngTranscludeDirective = ngDirective({
19696
20200
  <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
19697
20201
  <div id="tpl-content" ng-include src="currentTpl"></div>
19698
20202
  </doc:source>
19699
- <doc:scenario>
20203
+ <doc:protractor>
19700
20204
  it('should load template defined inside script tag', function() {
19701
- element('#tpl-link').click();
19702
- expect(element('#tpl-content').text()).toMatch(/Content of the template/);
20205
+ element(by.css('#tpl-link')).click();
20206
+ expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
19703
20207
  });
19704
- </doc:scenario>
20208
+ </doc:protractor>
19705
20209
  </doc:example>
19706
20210
  */
19707
20211
  var scriptDirective = ['$templateCache', function($templateCache) {
@@ -19739,14 +20243,21 @@ var ngOptionsMinErr = minErr('ngOptions');
19739
20243
  * represented by the selected option will be bound to the model identified by the `ngModel`
19740
20244
  * directive.
19741
20245
  *
20246
+ * <div class="alert alert-warning">
20247
+ * **Note:** `ngModel` compares by reference, not value. This is important when binding to an
20248
+ * array of objects. See an example {@link http://jsfiddle.net/qWzTb/ in this jsfiddle}.
20249
+ * </div>
20250
+ *
19742
20251
  * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
19743
20252
  * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
19744
20253
  * option. See example below for demonstration.
19745
20254
  *
19746
- * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
20255
+ * <div class="alert alert-warning">
20256
+ * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead
19747
20257
  * of {@link ng.directive:ngRepeat ngRepeat} when you want the
19748
20258
  * `select` model to be bound to a non-string value. This is because an option element can only
19749
20259
  * be bound to string values at present.
20260
+ * </div>
19750
20261
  *
19751
20262
  * @param {string} ngModel Assignable angular expression to data-bind to.
19752
20263
  * @param {string=} name Property name of the form under which the control is published.
@@ -19833,23 +20344,25 @@ var ngOptionsMinErr = minErr('ngOptions');
19833
20344
  </div>
19834
20345
  </div>
19835
20346
  </doc:source>
19836
- <doc:scenario>
20347
+ <doc:protractor>
19837
20348
  it('should check ng-options', function() {
19838
- expect(binding('{selected_color:color}')).toMatch('red');
19839
- select('color').option('0');
19840
- expect(binding('{selected_color:color}')).toMatch('black');
19841
- using('.nullable').select('color').option('');
19842
- expect(binding('{selected_color:color}')).toMatch('null');
20349
+ expect(element(by.binding('{selected_color:color}')).getText()).toMatch('red');
20350
+ element.all(by.select('color')).first().click();
20351
+ element.all(by.css('select[ng-model="color"] option')).first().click();
20352
+ expect(element(by.binding('{selected_color:color}')).getText()).toMatch('black');
20353
+ element(by.css('.nullable select[ng-model="color"]')).click();
20354
+ element.all(by.css('.nullable select[ng-model="color"] option')).first().click();
20355
+ expect(element(by.binding('{selected_color:color}')).getText()).toMatch('null');
19843
20356
  });
19844
- </doc:scenario>
20357
+ </doc:protractor>
19845
20358
  </doc:example>
19846
20359
  */
19847
20360
 
19848
20361
  var ngOptionsDirective = valueFn({ terminal: true });
19849
20362
  // jshint maxlen: false
19850
20363
  var selectDirective = ['$compile', '$parse', function($compile, $parse) {
19851
- //0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888
19852
- var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,
20364
+ //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
20365
+ var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
19853
20366
  nullModelCtrl = {$setViewValue: noop};
19854
20367
  // jshint maxlen: 100
19855
20368
 
@@ -19941,18 +20454,10 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
19941
20454
  selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
19942
20455
 
19943
20456
  // required validator
19944
- if (multiple && (attr.required || attr.ngRequired)) {
19945
- var requiredValidator = function(value) {
19946
- ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
19947
- return value;
20457
+ if (multiple) {
20458
+ ngModelCtrl.$isEmpty = function(value) {
20459
+ return !value || value.length === 0;
19948
20460
  };
19949
-
19950
- ngModelCtrl.$parsers.push(requiredValidator);
19951
- ngModelCtrl.$formatters.unshift(requiredValidator);
19952
-
19953
- attr.$observe('required', function() {
19954
- requiredValidator(ngModelCtrl.$viewValue);
19955
- });
19956
20461
  }
19957
20462
 
19958
20463
  if (optionsExp) setupAsOptions(scope, element, ngModelCtrl);
@@ -20158,7 +20663,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
20158
20663
 
20159
20664
  // We now build up the list of options we need (we merge later)
20160
20665
  for (index = 0; length = keys.length, index < length; index++) {
20161
-
20666
+
20162
20667
  key = index;
20163
20668
  if (keyName) {
20164
20669
  key = keys[index];
@@ -20366,4 +20871,4 @@ var styleDirective = valueFn({
20366
20871
 
20367
20872
  })(window, document);
20368
20873
 
20369
- !angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-start{border-spacing:1px 1px;-ms-zoom:1.0001;}.ng-animate-active{border-spacing:0px 0px;-ms-zoom:1;}</style>');
20874
+ !angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-block-transitions{transition:0s all!important;-webkit-transition:0s all!important;}</style>');