angular-rails-engine 1.2.5.0 → 1.2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>');