YouTube - Version 12.2

Version Description

Download this release

Release Info

Developer embedplus
Plugin Icon 128x128 YouTube
Version 12.2
Comparing to
See all releases

Code changes from version 12.1 to 12.2

images/adstxt-invalid.png ADDED
Binary file
images/adstxt-valid.png ADDED
Binary file
images/btn_embedplusstats.png DELETED
Binary file
images/btn_embedplusstatsoff.png DELETED
Binary file
images/icon-hw-adstxt.png ADDED
Binary file
includes/vi/vi_actions.php CHANGED
@@ -23,6 +23,9 @@ if (self::vi_logged_in())
23
  {
24
  add_action("wp_ajax_my_embedplus_vi_logout_ajax", array(get_class(), 'vi_logout_ajax'));
25
  add_action("wp_ajax_my_embedplus_vi_reports_ajax", array(get_class(), 'vi_reports_ajax'));
 
 
 
26
 
27
  add_filter('cron_schedules', array(get_class(), 'vi_cron_interval'));
28
  add_action('ytvi_cron_cache_js_hook', array(get_class(), 'vi_cron_cache_js'));
23
  {
24
  add_action("wp_ajax_my_embedplus_vi_logout_ajax", array(get_class(), 'vi_logout_ajax'));
25
  add_action("wp_ajax_my_embedplus_vi_reports_ajax", array(get_class(), 'vi_reports_ajax'));
26
+ add_action("wp_ajax_my_embedplus_vi_adstxt_status_soft_ajax", array(get_class(), 'vi_adstxt_status_soft_ajax'));
27
+
28
+ add_action('admin_init', array(get_class(), 'vi_token_expire'), 9);
29
 
30
  add_filter('cron_schedules', array(get_class(), 'vi_cron_interval'));
31
  add_action('ytvi_cron_cache_js_hook', array(get_class(), 'vi_cron_cache_js'));
includes/vi/vi_registration_form.php CHANGED
@@ -30,10 +30,21 @@
30
  </div>
31
  <div class="vi-demo-col-content">
32
  <div class="vi-demo">
 
 
 
 
 
 
 
 
 
 
33
  <p class="vi-demo-lede">
34
  You now have the option to make money embedding quality video ads that offer up to 10 times higher CPMs than display advertising. The ads that you will embed are privacy/GDPR friendly,
35
  powered by <img class="vi-logo-text" alt="vi" src="<?php echo plugins_url(self::$folder_name . '/images/vi_logo.svg'); ?>">
36
- <a href="https://www.vi.ai/publisher-video-monetization/?aid=WP_embedplus&utm_source=Wordpress&utm_medium=WP_embedplus" target="_blank">video intelligence</a>,
 
37
  completely separate from your YouTube embeds, and can provide extra income on top of revenue from your current ads.
38
  </p>
39
  <p>
@@ -49,8 +60,8 @@
49
  <div class="ytvi-step ytvi-step-1">
50
  <div class="ytvi-step-1--form">
51
  <div class="side-signup ytprefs-ajax-form">
52
- <h1>Sign up with vi.ai</h1>
53
- <h2>Join 30,000+ publishers</h2>
54
  <p class="description">Where should we send your welcome and revenue info?</p>
55
  <p>
56
  <input class="textinput regular-text ytvi-register-email" type="text" placeholder="Your email" />
@@ -67,8 +78,8 @@
67
  </p>
68
  </div>
69
  <div class="side-login ytprefs-ajax-form">
70
- <h1>Log in to vi.ai</h1>
71
- <h2>Earn 10x higher CPMs</h2>
72
  <p class="description">Already signed up? Login here using the info from your welcome email.</p>
73
  <p>
74
  <input class="textinput regular-text ytvi-login-email" type="text" placeholder="Your email" />
@@ -79,7 +90,6 @@
79
  <p>
80
  <input class="button-primary ytvi-step-1--submit-login ytprefs-ajax-form--submit" type="button" value="Log In &raquo;">
81
  <a class="vi-forgot-pw" href="https://dashboard.vi.ai/resetPassword/" target="_blank">Forgot Password?</a>
82
- <input type="hidden" value="" class="ytvi-login-adstxt" />
83
  </p>
84
  </div>
85
  <div class="vi-contact-support">
@@ -102,12 +112,12 @@
102
  <ol>
103
  <li><strong>Fill out</strong> the below</li>
104
  <li><strong>Check your email</strong> for a confirmation link</li>
105
- <li><strong>Come back</strong> and
106
  <?php
107
  $curr_screen = get_current_screen();
108
- echo strpos($curr_screen->id, 'youtube-ep-vi') !== false ? 'refresh this page' : '<a target="_blank" href="' . admin_url('admin.php?page=youtube-ep-vi') . '">click here</a>'
109
- ?> to login
110
- </li>
111
  </ol>
112
  </div>
113
  <iframe frameborder="0"></iframe>
30
  </div>
31
  <div class="vi-demo-col-content">
32
  <div class="vi-demo">
33
+ <?php
34
+ if (self::vi_script_setup_done() && !self::vi_last_login_valid())
35
+ {
36
+ ?>
37
+ <div class="login-expire">
38
+ For your security, your session expires every 30 days. Please login to vi again to view your settings.
39
+ </div>
40
+ <?php
41
+ }
42
+ ?>
43
  <p class="vi-demo-lede">
44
  You now have the option to make money embedding quality video ads that offer up to 10 times higher CPMs than display advertising. The ads that you will embed are privacy/GDPR friendly,
45
  powered by <img class="vi-logo-text" alt="vi" src="<?php echo plugins_url(self::$folder_name . '/images/vi_logo.svg'); ?>">
46
+ video intelligence,
47
+ <!-- <a href="https://www.vi.ai/publisher-video-monetization/?aid=WP_embedplus&utm_source=Wordpress&utm_medium=WP_embedplus" target="_blank">video intelligence</a>,-->
48
  completely separate from your YouTube embeds, and can provide extra income on top of revenue from your current ads.
49
  </p>
50
  <p>
60
  <div class="ytvi-step ytvi-step-1">
61
  <div class="ytvi-step-1--form">
62
  <div class="side-signup ytprefs-ajax-form">
63
+ <h1>Start earning today</h1>
64
+ <h2>Earn 10x higher CPMs</h2>
65
  <p class="description">Where should we send your welcome and revenue info?</p>
66
  <p>
67
  <input class="textinput regular-text ytvi-register-email" type="text" placeholder="Your email" />
78
  </p>
79
  </div>
80
  <div class="side-login ytprefs-ajax-form">
81
+ <h1>Log in to vi</h1>
82
+ <h2>Join 30,000+ publishers</h2>
83
  <p class="description">Already signed up? Login here using the info from your welcome email.</p>
84
  <p>
85
  <input class="textinput regular-text ytvi-login-email" type="text" placeholder="Your email" />
90
  <p>
91
  <input class="button-primary ytvi-step-1--submit-login ytprefs-ajax-form--submit" type="button" value="Log In &raquo;">
92
  <a class="vi-forgot-pw" href="https://dashboard.vi.ai/resetPassword/" target="_blank">Forgot Password?</a>
 
93
  </p>
94
  </div>
95
  <div class="vi-contact-support">
112
  <ol>
113
  <li><strong>Fill out</strong> the below</li>
114
  <li><strong>Check your email</strong> for a confirmation link</li>
115
+ <li><strong>Come right back here</strong> after creating your password and
116
  <?php
117
  $curr_screen = get_current_screen();
118
+ echo strpos($curr_screen->id, 'youtube-ep-vi') !== false || strpos($curr_screen->id, 'youtube-my-preferences') !== false ? 'refresh this page' : '<a target="_blank" href="' . admin_url('admin.php?page=youtube-ep-vi') . '">click here</a>'
119
+ ?> to login below
120
+ </li>
121
  </ol>
122
  </div>
123
  <iframe frameborder="0"></iframe>
readme.txt CHANGED
@@ -4,14 +4,14 @@ Plugin Name: YouTube Embed
4
  Tags: youtube gallery, video gallery, youtube channel, youtube live, live stream
5
  Requires at least: 3.6.1
6
  Tested up to: 4.9
7
- Stable tag: 12.1
8
  License: GPLv3 or later
9
 
10
  YouTube Embed WordPress Plugin. Embed a responsive video, YouTube channel gallery, playlist gallery, or YouTube.com live stream (with GDPR options)
11
 
12
  == Description ==
13
 
14
- **Your WordPress YouTube embed, YouTube gallery (channel and playlist), and even YouTube live stream can be customized in a wide variety of ways with this plugin. Here are a few recently added features:**
15
 
16
  * Privacy and Consent: Improved privacy and GDPR compliance options like YouTube no cookie, YouTube API restrictions, and a customizable GDPR consent message
17
  * YouTube gallery capability (channel and playlist) – The ability to make playlist and channel embeds have a gallery layout. By default, the plugin can generate a grid-based [responsive playlist or channel gallery >>](https://www.embedplus.com/responsive-youtube-playlist-channel-gallery-for-wordpress.aspx). Your visitors can browse through pages of video thumbnails and choose from videos that are pulled from an entire YouTube channel or playlist.
@@ -63,7 +63,6 @@ Customizations can be also made to each YouTube embed by adding more to the link
63
  * rel - Set this to 0 to not show related videos at the end of playing (or 1 to show them). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&rel=0"`
64
  * showinfo - Set this to 0 to hide the video title and other info (or 1 to show it). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&showinfo=0"`
65
  * fs - Set this to 0 to hide the fullscreen button (or 1 to show it). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&fs=0"`
66
- * autohide - Set this to 1 to slide away the control bar after the video starts playing. It will automatically slide back in again if you mouse over the video. (Set to 2 to always show it). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&autohide=1"`
67
 
68
  You can also start and end each individual video at particular times. Like the above, each option should begin with '&'
69
 
@@ -148,6 +147,11 @@ You can also start and end each individual video at particular times. Like the a
148
 
149
  == Changelog ==
150
 
 
 
 
 
 
151
  = WordPress YouTube Embed 12.1 =
152
  * Improved autoplay compatibility
153
  * Improved sign-up process for the new monetization feature
4
  Tags: youtube gallery, video gallery, youtube channel, youtube live, live stream
5
  Requires at least: 3.6.1
6
  Tested up to: 4.9
7
+ Stable tag: 12.2
8
  License: GPLv3 or later
9
 
10
  YouTube Embed WordPress Plugin. Embed a responsive video, YouTube channel gallery, playlist gallery, or YouTube.com live stream (with GDPR options)
11
 
12
  == Description ==
13
 
14
+ **Your WordPress YouTube embed, YouTube gallery (channel and playlist), and even YouTube live stream can be customized in a wide variety of ways with this plugin. Here are a few recently added features: (Gutenberg compatibility coming soon!)**
15
 
16
  * Privacy and Consent: Improved privacy and GDPR compliance options like YouTube no cookie, YouTube API restrictions, and a customizable GDPR consent message
17
  * YouTube gallery capability (channel and playlist) – The ability to make playlist and channel embeds have a gallery layout. By default, the plugin can generate a grid-based [responsive playlist or channel gallery >>](https://www.embedplus.com/responsive-youtube-playlist-channel-gallery-for-wordpress.aspx). Your visitors can browse through pages of video thumbnails and choose from videos that are pulled from an entire YouTube channel or playlist.
63
  * rel - Set this to 0 to not show related videos at the end of playing (or 1 to show them). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&rel=0"`
64
  * showinfo - Set this to 0 to hide the video title and other info (or 1 to show it). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&showinfo=0"`
65
  * fs - Set this to 0 to hide the fullscreen button (or 1 to show it). Example: `"https://www.youtube.com/watch?v=quwebVjAEJA&fs=0"`
 
66
 
67
  You can also start and end each individual video at particular times. Like the above, each option should begin with '&'
68
 
147
 
148
  == Changelog ==
149
 
150
+ = WordPress YouTube Embed 12.2 =
151
+ * Improved ads.txt verification management
152
+ * Fixed gallery box-sizing bug
153
+ * Remove some deprecated YouTube parameters
154
+
155
  = WordPress YouTube Embed 12.1 =
156
  * Improved autoplay compatibility
157
  * Improved sign-up process for the new monetization feature
scripts/angular.js DELETED
@@ -1,33831 +0,0 @@
1
- /**
2
- * @license AngularJS v1.6.5
3
- * (c) 2010-2017 Google, Inc. http://angularjs.org
4
- * License: MIT
5
- */
6
- (function(window) {'use strict';
7
-
8
- /* exported
9
- minErrConfig,
10
- errorHandlingConfig,
11
- isValidObjectMaxDepth
12
- */
13
-
14
- var minErrConfig = {
15
- objectMaxDepth: 5
16
- };
17
-
18
- /**
19
- * @ngdoc function
20
- * @name angular.errorHandlingConfig
21
- * @module ng
22
- * @kind function
23
- *
24
- * @description
25
- * Configure several aspects of error handling in AngularJS if used as a setter or return the
26
- * current configuration if used as a getter. The following options are supported:
27
- *
28
- * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages.
29
- *
30
- * Omitted or undefined options will leave the corresponding configuration values unchanged.
31
- *
32
- * @param {Object=} config - The configuration object. May only contain the options that need to be
33
- * updated. Supported keys:
34
- *
35
- * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
36
- * non-positive or non-numeric value, removes the max depth limit.
37
- * Default: 5
38
- */
39
- function errorHandlingConfig(config) {
40
- if (isObject(config)) {
41
- if (isDefined(config.objectMaxDepth)) {
42
- minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
43
- }
44
- } else {
45
- return minErrConfig;
46
- }
47
- }
48
-
49
- /**
50
- * @private
51
- * @param {Number} maxDepth
52
- * @return {boolean}
53
- */
54
- function isValidObjectMaxDepth(maxDepth) {
55
- return isNumber(maxDepth) && maxDepth > 0;
56
- }
57
-
58
- /**
59
- * @description
60
- *
61
- * This object provides a utility for producing rich Error messages within
62
- * Angular. It can be called as follows:
63
- *
64
- * var exampleMinErr = minErr('example');
65
- * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
66
- *
67
- * The above creates an instance of minErr in the example namespace. The
68
- * resulting error will have a namespaced error code of example.one. The
69
- * resulting error will replace {0} with the value of foo, and {1} with the
70
- * value of bar. The object is not restricted in the number of arguments it can
71
- * take.
72
- *
73
- * If fewer arguments are specified than necessary for interpolation, the extra
74
- * interpolation markers will be preserved in the final string.
75
- *
76
- * Since data will be parsed statically during a build step, some restrictions
77
- * are applied with respect to how minErr instances are created and called.
78
- * Instances should have names of the form namespaceMinErr for a minErr created
79
- * using minErr('namespace') . Error codes, namespaces and template strings
80
- * should all be static strings, not variables or general expressions.
81
- *
82
- * @param {string} module The namespace to use for the new minErr instance.
83
- * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
84
- * error from returned function, for cases when a particular type of error is useful.
85
- * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
86
- */
87
-
88
- function minErr(module, ErrorConstructor) {
89
- ErrorConstructor = ErrorConstructor || Error;
90
- return function() {
91
- var code = arguments[0],
92
- template = arguments[1],
93
- message = '[' + (module ? module + ':' : '') + code + '] ',
94
- templateArgs = sliceArgs(arguments, 2).map(function(arg) {
95
- return toDebugString(arg, minErrConfig.objectMaxDepth);
96
- }),
97
- paramPrefix, i;
98
-
99
- message += template.replace(/\{\d+\}/g, function(match) {
100
- var index = +match.slice(1, -1);
101
-
102
- if (index < templateArgs.length) {
103
- return templateArgs[index];
104
- }
105
-
106
- return match;
107
- });
108
-
109
- message += '\nhttp://errors.angularjs.org/1.6.5/' +
110
- (module ? module + '/' : '') + code;
111
-
112
- for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
113
- message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
114
- }
115
-
116
- return new ErrorConstructor(message);
117
- };
118
- }
119
-
120
- /* We need to tell ESLint what variables are being exported */
121
- /* exported
122
- angular,
123
- msie,
124
- jqLite,
125
- jQuery,
126
- slice,
127
- splice,
128
- push,
129
- toString,
130
- minErrConfig,
131
- errorHandlingConfig,
132
- isValidObjectMaxDepth,
133
- ngMinErr,
134
- angularModule,
135
- uid,
136
- REGEX_STRING_REGEXP,
137
- VALIDITY_STATE_PROPERTY,
138
-
139
- lowercase,
140
- uppercase,
141
- manualLowercase,
142
- manualUppercase,
143
- nodeName_,
144
- isArrayLike,
145
- forEach,
146
- forEachSorted,
147
- reverseParams,
148
- nextUid,
149
- setHashKey,
150
- extend,
151
- toInt,
152
- inherit,
153
- merge,
154
- noop,
155
- identity,
156
- valueFn,
157
- isUndefined,
158
- isDefined,
159
- isObject,
160
- isBlankObject,
161
- isString,
162
- isNumber,
163
- isNumberNaN,
164
- isDate,
165
- isError,
166
- isArray,
167
- isFunction,
168
- isRegExp,
169
- isWindow,
170
- isScope,
171
- isFile,
172
- isFormData,
173
- isBlob,
174
- isBoolean,
175
- isPromiseLike,
176
- trim,
177
- escapeForRegexp,
178
- isElement,
179
- makeMap,
180
- includes,
181
- arrayRemove,
182
- copy,
183
- simpleCompare,
184
- equals,
185
- csp,
186
- jq,
187
- concat,
188
- sliceArgs,
189
- bind,
190
- toJsonReplacer,
191
- toJson,
192
- fromJson,
193
- convertTimezoneToLocal,
194
- timezoneToOffset,
195
- startingTag,
196
- tryDecodeURIComponent,
197
- parseKeyValue,
198
- toKeyValue,
199
- encodeUriSegment,
200
- encodeUriQuery,
201
- angularInit,
202
- bootstrap,
203
- getTestability,
204
- snake_case,
205
- bindJQuery,
206
- assertArg,
207
- assertArgFn,
208
- assertNotHasOwnProperty,
209
- getter,
210
- getBlockNodes,
211
- hasOwnProperty,
212
- createMap,
213
- stringify,
214
-
215
- NODE_TYPE_ELEMENT,
216
- NODE_TYPE_ATTRIBUTE,
217
- NODE_TYPE_TEXT,
218
- NODE_TYPE_COMMENT,
219
- NODE_TYPE_DOCUMENT,
220
- NODE_TYPE_DOCUMENT_FRAGMENT
221
- */
222
-
223
- ////////////////////////////////////
224
-
225
- /**
226
- * @ngdoc module
227
- * @name ng
228
- * @module ng
229
- * @installation
230
- * @description
231
- *
232
- * # ng (core module)
233
- * The ng module is loaded by default when an AngularJS application is started. The module itself
234
- * contains the essential components for an AngularJS application to function. The table below
235
- * lists a high level breakdown of each of the services/factories, filters, directives and testing
236
- * components available within this core module.
237
- *
238
- * <div doc-module-components="ng"></div>
239
- */
240
-
241
- var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
242
-
243
- // The name of a form control's ValidityState property.
244
- // This is used so that it's possible for internal tests to create mock ValidityStates.
245
- var VALIDITY_STATE_PROPERTY = 'validity';
246
-
247
-
248
- var hasOwnProperty = Object.prototype.hasOwnProperty;
249
-
250
- /**
251
- * @ngdoc function
252
- * @name angular.lowercase
253
- * @module ng
254
- * @kind function
255
- *
256
- * @deprecated
257
- * sinceVersion="1.5.0"
258
- * removeVersion="1.7.0"
259
- * Use [String.prototype.toLowerCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) instead.
260
- *
261
- * @description Converts the specified string to lowercase.
262
- * @param {string} string String to be converted to lowercase.
263
- * @returns {string} Lowercased string.
264
- */
265
- var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
266
-
267
- /**
268
- * @ngdoc function
269
- * @name angular.uppercase
270
- * @module ng
271
- * @kind function
272
- *
273
- * @deprecated
274
- * sinceVersion="1.5.0"
275
- * removeVersion="1.7.0"
276
- * Use [String.prototype.toUpperCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) instead.
277
- *
278
- * @description Converts the specified string to uppercase.
279
- * @param {string} string String to be converted to uppercase.
280
- * @returns {string} Uppercased string.
281
- */
282
- var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
283
-
284
-
285
- var manualLowercase = function(s) {
286
- /* eslint-disable no-bitwise */
287
- return isString(s)
288
- ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
289
- : s;
290
- /* eslint-enable */
291
- };
292
- var manualUppercase = function(s) {
293
- /* eslint-disable no-bitwise */
294
- return isString(s)
295
- ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
296
- : s;
297
- /* eslint-enable */
298
- };
299
-
300
-
301
- // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
302
- // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
303
- // with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387
304
- if ('i' !== 'I'.toLowerCase()) {
305
- lowercase = manualLowercase;
306
- uppercase = manualUppercase;
307
- }
308
-
309
-
310
- var
311
- msie, // holds major version number for IE, or NaN if UA is not IE.
312
- jqLite, // delay binding since jQuery could be loaded after us.
313
- jQuery, // delay binding
314
- slice = [].slice,
315
- splice = [].splice,
316
- push = [].push,
317
- toString = Object.prototype.toString,
318
- getPrototypeOf = Object.getPrototypeOf,
319
- ngMinErr = minErr('ng'),
320
-
321
- /** @name angular */
322
- angular = window.angular || (window.angular = {}),
323
- angularModule,
324
- uid = 0;
325
-
326
- // Support: IE 9-11 only
327
- /**
328
- * documentMode is an IE-only property
329
- * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
330
- */
331
- msie = window.document.documentMode;
332
-
333
-
334
- /**
335
- * @private
336
- * @param {*} obj
337
- * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
338
- * String ...)
339
- */
340
- function isArrayLike(obj) {
341
-
342
- // `null`, `undefined` and `window` are not array-like
343
- if (obj == null || isWindow(obj)) return false;
344
-
345
- // arrays, strings and jQuery/jqLite objects are array like
346
- // * jqLite is either the jQuery or jqLite constructor function
347
- // * we have to check the existence of jqLite first as this method is called
348
- // via the forEach method when constructing the jqLite object in the first place
349
- if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
350
-
351
- // Support: iOS 8.2 (not reproducible in simulator)
352
- // "length" in obj used to prevent JIT error (gh-11508)
353
- var length = 'length' in Object(obj) && obj.length;
354
-
355
- // NodeList objects (with `item` method) and
356
- // other objects with suitable length characteristics are array-like
357
- return isNumber(length) &&
358
- (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function');
359
-
360
- }
361
-
362
- /**
363
- * @ngdoc function
364
- * @name angular.forEach
365
- * @module ng
366
- * @kind function
367
- *
368
- * @description
369
- * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
370
- * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
371
- * is the value of an object property or an array element, `key` is the object property key or
372
- * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
373
- *
374
- * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
375
- * using the `hasOwnProperty` method.
376
- *
377
- * Unlike ES262's
378
- * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
379
- * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
380
- * return the value provided.
381
- *
382
- ```js
383
- var values = {name: 'misko', gender: 'male'};
384
- var log = [];
385
- angular.forEach(values, function(value, key) {
386
- this.push(key + ': ' + value);
387
- }, log);
388
- expect(log).toEqual(['name: misko', 'gender: male']);
389
- ```
390
- *
391
- * @param {Object|Array} obj Object to iterate over.
392
- * @param {Function} iterator Iterator function.
393
- * @param {Object=} context Object to become context (`this`) for the iterator function.
394
- * @returns {Object|Array} Reference to `obj`.
395
- */
396
-
397
- function forEach(obj, iterator, context) {
398
- var key, length;
399
- if (obj) {
400
- if (isFunction(obj)) {
401
- for (key in obj) {
402
- if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) {
403
- iterator.call(context, obj[key], key, obj);
404
- }
405
- }
406
- } else if (isArray(obj) || isArrayLike(obj)) {
407
- var isPrimitive = typeof obj !== 'object';
408
- for (key = 0, length = obj.length; key < length; key++) {
409
- if (isPrimitive || key in obj) {
410
- iterator.call(context, obj[key], key, obj);
411
- }
412
- }
413
- } else if (obj.forEach && obj.forEach !== forEach) {
414
- obj.forEach(iterator, context, obj);
415
- } else if (isBlankObject(obj)) {
416
- // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
417
- for (key in obj) {
418
- iterator.call(context, obj[key], key, obj);
419
- }
420
- } else if (typeof obj.hasOwnProperty === 'function') {
421
- // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
422
- for (key in obj) {
423
- if (obj.hasOwnProperty(key)) {
424
- iterator.call(context, obj[key], key, obj);
425
- }
426
- }
427
- } else {
428
- // Slow path for objects which do not have a method `hasOwnProperty`
429
- for (key in obj) {
430
- if (hasOwnProperty.call(obj, key)) {
431
- iterator.call(context, obj[key], key, obj);
432
- }
433
- }
434
- }
435
- }
436
- return obj;
437
- }
438
-
439
- function forEachSorted(obj, iterator, context) {
440
- var keys = Object.keys(obj).sort();
441
- for (var i = 0; i < keys.length; i++) {
442
- iterator.call(context, obj[keys[i]], keys[i]);
443
- }
444
- return keys;
445
- }
446
-
447
-
448
- /**
449
- * when using forEach the params are value, key, but it is often useful to have key, value.
450
- * @param {function(string, *)} iteratorFn
451
- * @returns {function(*, string)}
452
- */
453
- function reverseParams(iteratorFn) {
454
- return function(value, key) {iteratorFn(key, value);};
455
- }
456
-
457
- /**
458
- * A consistent way of creating unique IDs in angular.
459
- *
460
- * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
461
- * we hit number precision issues in JavaScript.
462
- *
463
- * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
464
- *
465
- * @returns {number} an unique alpha-numeric string
466
- */
467
- function nextUid() {
468
- return ++uid;
469
- }
470
-
471
-
472
- /**
473
- * Set or clear the hashkey for an object.
474
- * @param obj object
475
- * @param h the hashkey (!truthy to delete the hashkey)
476
- */
477
- function setHashKey(obj, h) {
478
- if (h) {
479
- obj.$$hashKey = h;
480
- } else {
481
- delete obj.$$hashKey;
482
- }
483
- }
484
-
485
-
486
- function baseExtend(dst, objs, deep) {
487
- var h = dst.$$hashKey;
488
-
489
- for (var i = 0, ii = objs.length; i < ii; ++i) {
490
- var obj = objs[i];
491
- if (!isObject(obj) && !isFunction(obj)) continue;
492
- var keys = Object.keys(obj);
493
- for (var j = 0, jj = keys.length; j < jj; j++) {
494
- var key = keys[j];
495
- var src = obj[key];
496
-
497
- if (deep && isObject(src)) {
498
- if (isDate(src)) {
499
- dst[key] = new Date(src.valueOf());
500
- } else if (isRegExp(src)) {
501
- dst[key] = new RegExp(src);
502
- } else if (src.nodeName) {
503
- dst[key] = src.cloneNode(true);
504
- } else if (isElement(src)) {
505
- dst[key] = src.clone();
506
- } else {
507
- if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
508
- baseExtend(dst[key], [src], true);
509
- }
510
- } else {
511
- dst[key] = src;
512
- }
513
- }
514
- }
515
-
516
- setHashKey(dst, h);
517
- return dst;
518
- }
519
-
520
- /**
521
- * @ngdoc function
522
- * @name angular.extend
523
- * @module ng
524
- * @kind function
525
- *
526
- * @description
527
- * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
528
- * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
529
- * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
530
- *
531
- * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
532
- * {@link angular.merge} for this.
533
- *
534
- * @param {Object} dst Destination object.
535
- * @param {...Object} src Source object(s).
536
- * @returns {Object} Reference to `dst`.
537
- */
538
- function extend(dst) {
539
- return baseExtend(dst, slice.call(arguments, 1), false);
540
- }
541
-
542
-
543
- /**
544
- * @ngdoc function
545
- * @name angular.merge
546
- * @module ng
547
- * @kind function
548
- *
549
- * @description
550
- * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
551
- * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
552
- * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
553
- *
554
- * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
555
- * objects, performing a deep copy.
556
- *
557
- * @deprecated
558
- * sinceVersion="1.6.5"
559
- * This function is deprecated, but will not be removed in the 1.x lifecycle.
560
- * There are edge cases (see {@link angular.merge#known-issues known issues}) that are not
561
- * supported by this function. We suggest
562
- * using [lodash's merge()](https://lodash.com/docs/4.17.4#merge) instead.
563
- *
564
- * @knownIssue
565
- * This is a list of (known) object types that are not handled correctly by this function:
566
- * - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob)
567
- * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
568
- * - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient)
569
- * - AngularJS {@link $rootScope.Scope scopes};
570
- *
571
- * @param {Object} dst Destination object.
572
- * @param {...Object} src Source object(s).
573
- * @returns {Object} Reference to `dst`.
574
- */
575
- function merge(dst) {
576
- return baseExtend(dst, slice.call(arguments, 1), true);
577
- }
578
-
579
-
580
-
581
- function toInt(str) {
582
- return parseInt(str, 10);
583
- }
584
-
585
- var isNumberNaN = Number.isNaN || function isNumberNaN(num) {
586
- // eslint-disable-next-line no-self-compare
587
- return num !== num;
588
- };
589
-
590
-
591
- function inherit(parent, extra) {
592
- return extend(Object.create(parent), extra);
593
- }
594
-
595
- /**
596
- * @ngdoc function
597
- * @name angular.noop
598
- * @module ng
599
- * @kind function
600
- *
601
- * @description
602
- * A function that performs no operations. This function can be useful when writing code in the
603
- * functional style.
604
- ```js
605
- function foo(callback) {
606
- var result = calculateResult();
607
- (callback || angular.noop)(result);
608
- }
609
- ```
610
- */
611
- function noop() {}
612
- noop.$inject = [];
613
-
614
-
615
- /**
616
- * @ngdoc function
617
- * @name angular.identity
618
- * @module ng
619
- * @kind function
620
- *
621
- * @description
622
- * A function that returns its first argument. This function is useful when writing code in the
623
- * functional style.
624
- *
625
- ```js
626
- function transformer(transformationFn, value) {
627
- return (transformationFn || angular.identity)(value);
628
- };
629
-
630
- // E.g.
631
- function getResult(fn, input) {
632
- return (fn || angular.identity)(input);
633
- };
634
-
635
- getResult(function(n) { return n * 2; }, 21); // returns 42
636
- getResult(null, 21); // returns 21
637
- getResult(undefined, 21); // returns 21
638
- ```
639
- *
640
- * @param {*} value to be returned.
641
- * @returns {*} the value passed in.
642
- */
643
- function identity($) {return $;}
644
- identity.$inject = [];
645
-
646
-
647
- function valueFn(value) {return function valueRef() {return value;};}
648
-
649
- function hasCustomToString(obj) {
650
- return isFunction(obj.toString) && obj.toString !== toString;
651
- }
652
-
653
-
654
- /**
655
- * @ngdoc function
656
- * @name angular.isUndefined
657
- * @module ng
658
- * @kind function
659
- *
660
- * @description
661
- * Determines if a reference is undefined.
662
- *
663
- * @param {*} value Reference to check.
664
- * @returns {boolean} True if `value` is undefined.
665
- */
666
- function isUndefined(value) {return typeof value === 'undefined';}
667
-
668
-
669
- /**
670
- * @ngdoc function
671
- * @name angular.isDefined
672
- * @module ng
673
- * @kind function
674
- *
675
- * @description
676
- * Determines if a reference is defined.
677
- *
678
- * @param {*} value Reference to check.
679
- * @returns {boolean} True if `value` is defined.
680
- */
681
- function isDefined(value) {return typeof value !== 'undefined';}
682
-
683
-
684
- /**
685
- * @ngdoc function
686
- * @name angular.isObject
687
- * @module ng
688
- * @kind function
689
- *
690
- * @description
691
- * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
692
- * considered to be objects. Note that JavaScript arrays are objects.
693
- *
694
- * @param {*} value Reference to check.
695
- * @returns {boolean} True if `value` is an `Object` but not `null`.
696
- */
697
- function isObject(value) {
698
- // http://jsperf.com/isobject4
699
- return value !== null && typeof value === 'object';
700
- }
701
-
702
-
703
- /**
704
- * Determine if a value is an object with a null prototype
705
- *
706
- * @returns {boolean} True if `value` is an `Object` with a null prototype
707
- */
708
- function isBlankObject(value) {
709
- return value !== null && typeof value === 'object' && !getPrototypeOf(value);
710
- }
711
-
712
-
713
- /**
714
- * @ngdoc function
715
- * @name angular.isString
716
- * @module ng
717
- * @kind function
718
- *
719
- * @description
720
- * Determines if a reference is a `String`.
721
- *
722
- * @param {*} value Reference to check.
723
- * @returns {boolean} True if `value` is a `String`.
724
- */
725
- function isString(value) {return typeof value === 'string';}
726
-
727
-
728
- /**
729
- * @ngdoc function
730
- * @name angular.isNumber
731
- * @module ng
732
- * @kind function
733
- *
734
- * @description
735
- * Determines if a reference is a `Number`.
736
- *
737
- * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
738
- *
739
- * If you wish to exclude these then you can use the native
740
- * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
741
- * method.
742
- *
743
- * @param {*} value Reference to check.
744
- * @returns {boolean} True if `value` is a `Number`.
745
- */
746
- function isNumber(value) {return typeof value === 'number';}
747
-
748
-
749
- /**
750
- * @ngdoc function
751
- * @name angular.isDate
752
- * @module ng
753
- * @kind function
754
- *
755
- * @description
756
- * Determines if a value is a date.
757
- *
758
- * @param {*} value Reference to check.
759
- * @returns {boolean} True if `value` is a `Date`.
760
- */
761
- function isDate(value) {
762
- return toString.call(value) === '[object Date]';
763
- }
764
-
765
-
766
- /**
767
- * @ngdoc function
768
- * @name angular.isArray
769
- * @module ng
770
- * @kind function
771
- *
772
- * @description
773
- * Determines if a reference is an `Array`. Alias of Array.isArray.
774
- *
775
- * @param {*} value Reference to check.
776
- * @returns {boolean} True if `value` is an `Array`.
777
- */
778
- var isArray = Array.isArray;
779
-
780
- /**
781
- * @description
782
- * Determines if a reference is an `Error`.
783
- * Loosely based on https://www.npmjs.com/package/iserror
784
- *
785
- * @param {*} value Reference to check.
786
- * @returns {boolean} True if `value` is an `Error`.
787
- */
788
- function isError(value) {
789
- var tag = toString.call(value);
790
- switch (tag) {
791
- case '[object Error]': return true;
792
- case '[object Exception]': return true;
793
- case '[object DOMException]': return true;
794
- default: return value instanceof Error;
795
- }
796
- }
797
-
798
- /**
799
- * @ngdoc function
800
- * @name angular.isFunction
801
- * @module ng
802
- * @kind function
803
- *
804
- * @description
805
- * Determines if a reference is a `Function`.
806
- *
807
- * @param {*} value Reference to check.
808
- * @returns {boolean} True if `value` is a `Function`.
809
- */
810
- function isFunction(value) {return typeof value === 'function';}
811
-
812
-
813
- /**
814
- * Determines if a value is a regular expression object.
815
- *
816
- * @private
817
- * @param {*} value Reference to check.
818
- * @returns {boolean} True if `value` is a `RegExp`.
819
- */
820
- function isRegExp(value) {
821
- return toString.call(value) === '[object RegExp]';
822
- }
823
-
824
-
825
- /**
826
- * Checks if `obj` is a window object.
827
- *
828
- * @private
829
- * @param {*} obj Object to check
830
- * @returns {boolean} True if `obj` is a window obj.
831
- */
832
- function isWindow(obj) {
833
- return obj && obj.window === obj;
834
- }
835
-
836
-
837
- function isScope(obj) {
838
- return obj && obj.$evalAsync && obj.$watch;
839
- }
840
-
841
-
842
- function isFile(obj) {
843
- return toString.call(obj) === '[object File]';
844
- }
845
-
846
-
847
- function isFormData(obj) {
848
- return toString.call(obj) === '[object FormData]';
849
- }
850
-
851
-
852
- function isBlob(obj) {
853
- return toString.call(obj) === '[object Blob]';
854
- }
855
-
856
-
857
- function isBoolean(value) {
858
- return typeof value === 'boolean';
859
- }
860
-
861
-
862
- function isPromiseLike(obj) {
863
- return obj && isFunction(obj.then);
864
- }
865
-
866
-
867
- var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/;
868
- function isTypedArray(value) {
869
- return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
870
- }
871
-
872
- function isArrayBuffer(obj) {
873
- return toString.call(obj) === '[object ArrayBuffer]';
874
- }
875
-
876
-
877
- var trim = function(value) {
878
- return isString(value) ? value.trim() : value;
879
- };
880
-
881
- // Copied from:
882
- // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
883
- // Prereq: s is a string.
884
- var escapeForRegexp = function(s) {
885
- return s
886
- .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1')
887
- // eslint-disable-next-line no-control-regex
888
- .replace(/\x08/g, '\\x08');
889
- };
890
-
891
-
892
- /**
893
- * @ngdoc function
894
- * @name angular.isElement
895
- * @module ng
896
- * @kind function
897
- *
898
- * @description
899
- * Determines if a reference is a DOM element (or wrapped jQuery element).
900
- *
901
- * @param {*} value Reference to check.
902
- * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
903
- */
904
- function isElement(node) {
905
- return !!(node &&
906
- (node.nodeName // We are a direct element.
907
- || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API.
908
- }
909
-
910
- /**
911
- * @param str 'key1,key2,...'
912
- * @returns {object} in the form of {key1:true, key2:true, ...}
913
- */
914
- function makeMap(str) {
915
- var obj = {}, items = str.split(','), i;
916
- for (i = 0; i < items.length; i++) {
917
- obj[items[i]] = true;
918
- }
919
- return obj;
920
- }
921
-
922
-
923
- function nodeName_(element) {
924
- return lowercase(element.nodeName || (element[0] && element[0].nodeName));
925
- }
926
-
927
- function includes(array, obj) {
928
- return Array.prototype.indexOf.call(array, obj) !== -1;
929
- }
930
-
931
- function arrayRemove(array, value) {
932
- var index = array.indexOf(value);
933
- if (index >= 0) {
934
- array.splice(index, 1);
935
- }
936
- return index;
937
- }
938
-
939
- /**
940
- * @ngdoc function
941
- * @name angular.copy
942
- * @module ng
943
- * @kind function
944
- *
945
- * @description
946
- * Creates a deep copy of `source`, which should be an object or an array.
947
- *
948
- * * If no destination is supplied, a copy of the object or array is created.
949
- * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
950
- * are deleted and then all elements/properties from the source are copied to it.
951
- * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
952
- * * If `source` is identical to `destination` an exception will be thrown.
953
- *
954
- * <br />
955
- * <div class="alert alert-warning">
956
- * Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
957
- * and on `destination`) will be ignored.
958
- * </div>
959
- *
960
- * @param {*} source The source that will be used to make a copy.
961
- * Can be any type, including primitives, `null`, and `undefined`.
962
- * @param {(Object|Array)=} destination Destination into which the source is copied. If
963
- * provided, must be of the same type as `source`.
964
- * @returns {*} The copy or updated `destination`, if `destination` was specified.
965
- *
966
- * @example
967
- <example module="copyExample" name="angular-copy">
968
- <file name="index.html">
969
- <div ng-controller="ExampleController">
970
- <form novalidate class="simple-form">
971
- <label>Name: <input type="text" ng-model="user.name" /></label><br />
972
- <label>Age: <input type="number" ng-model="user.age" /></label><br />
973
- Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
974
- <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
975
- <button ng-click="reset()">RESET</button>
976
- <button ng-click="update(user)">SAVE</button>
977
- </form>
978
- <pre>form = {{user | json}}</pre>
979
- <pre>master = {{master | json}}</pre>
980
- </div>
981
- </file>
982
- <file name="script.js">
983
- // Module: copyExample
984
- angular.
985
- module('copyExample', []).
986
- controller('ExampleController', ['$scope', function($scope) {
987
- $scope.master = {};
988
-
989
- $scope.reset = function() {
990
- // Example with 1 argument
991
- $scope.user = angular.copy($scope.master);
992
- };
993
-
994
- $scope.update = function(user) {
995
- // Example with 2 arguments
996
- angular.copy(user, $scope.master);
997
- };
998
-
999
- $scope.reset();
1000
- }]);
1001
- </file>
1002
- </example>
1003
- */
1004
- function copy(source, destination, maxDepth) {
1005
- var stackSource = [];
1006
- var stackDest = [];
1007
- maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN;
1008
-
1009
- if (destination) {
1010
- if (isTypedArray(destination) || isArrayBuffer(destination)) {
1011
- throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.');
1012
- }
1013
- if (source === destination) {
1014
- throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.');
1015
- }
1016
-
1017
- // Empty the destination object
1018
- if (isArray(destination)) {
1019
- destination.length = 0;
1020
- } else {
1021
- forEach(destination, function(value, key) {
1022
- if (key !== '$$hashKey') {
1023
- delete destination[key];
1024
- }
1025
- });
1026
- }
1027
-
1028
- stackSource.push(source);
1029
- stackDest.push(destination);
1030
- return copyRecurse(source, destination, maxDepth);
1031
- }
1032
-
1033
- return copyElement(source, maxDepth);
1034
-
1035
- function copyRecurse(source, destination, maxDepth) {
1036
- maxDepth--;
1037
- if (maxDepth < 0) {
1038
- return '...';
1039
- }
1040
- var h = destination.$$hashKey;
1041
- var key;
1042
- if (isArray(source)) {
1043
- for (var i = 0, ii = source.length; i < ii; i++) {
1044
- destination.push(copyElement(source[i], maxDepth));
1045
- }
1046
- } else if (isBlankObject(source)) {
1047
- // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
1048
- for (key in source) {
1049
- destination[key] = copyElement(source[key], maxDepth);
1050
- }
1051
- } else if (source && typeof source.hasOwnProperty === 'function') {
1052
- // Slow path, which must rely on hasOwnProperty
1053
- for (key in source) {
1054
- if (source.hasOwnProperty(key)) {
1055
- destination[key] = copyElement(source[key], maxDepth);
1056
- }
1057
- }
1058
- } else {
1059
- // Slowest path --- hasOwnProperty can't be called as a method
1060
- for (key in source) {
1061
- if (hasOwnProperty.call(source, key)) {
1062
- destination[key] = copyElement(source[key], maxDepth);
1063
- }
1064
- }
1065
- }
1066
- setHashKey(destination, h);
1067
- return destination;
1068
- }
1069
-
1070
- function copyElement(source, maxDepth) {
1071
- // Simple values
1072
- if (!isObject(source)) {
1073
- return source;
1074
- }
1075
-
1076
- // Already copied values
1077
- var index = stackSource.indexOf(source);
1078
- if (index !== -1) {
1079
- return stackDest[index];
1080
- }
1081
-
1082
- if (isWindow(source) || isScope(source)) {
1083
- throw ngMinErr('cpws',
1084
- 'Can\'t copy! Making copies of Window or Scope instances is not supported.');
1085
- }
1086
-
1087
- var needsRecurse = false;
1088
- var destination = copyType(source);
1089
-
1090
- if (destination === undefined) {
1091
- destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
1092
- needsRecurse = true;
1093
- }
1094
-
1095
- stackSource.push(source);
1096
- stackDest.push(destination);
1097
-
1098
- return needsRecurse
1099
- ? copyRecurse(source, destination, maxDepth)
1100
- : destination;
1101
- }
1102
-
1103
- function copyType(source) {
1104
- switch (toString.call(source)) {
1105
- case '[object Int8Array]':
1106
- case '[object Int16Array]':
1107
- case '[object Int32Array]':
1108
- case '[object Float32Array]':
1109
- case '[object Float64Array]':
1110
- case '[object Uint8Array]':
1111
- case '[object Uint8ClampedArray]':
1112
- case '[object Uint16Array]':
1113
- case '[object Uint32Array]':
1114
- return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);
1115
-
1116
- case '[object ArrayBuffer]':
1117
- // Support: IE10
1118
- if (!source.slice) {
1119
- // If we're in this case we know the environment supports ArrayBuffer
1120
- /* eslint-disable no-undef */
1121
- var copied = new ArrayBuffer(source.byteLength);
1122
- new Uint8Array(copied).set(new Uint8Array(source));
1123
- /* eslint-enable */
1124
- return copied;
1125
- }
1126
- return source.slice(0);
1127
-
1128
- case '[object Boolean]':
1129
- case '[object Number]':
1130
- case '[object String]':
1131
- case '[object Date]':
1132
- return new source.constructor(source.valueOf());
1133
-
1134
- case '[object RegExp]':
1135
- var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
1136
- re.lastIndex = source.lastIndex;
1137
- return re;
1138
-
1139
- case '[object Blob]':
1140
- return new source.constructor([source], {type: source.type});
1141
- }
1142
-
1143
- if (isFunction(source.cloneNode)) {
1144
- return source.cloneNode(true);
1145
- }
1146
- }
1147
- }
1148
-
1149
-
1150
- // eslint-disable-next-line no-self-compare
1151
- function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }
1152
-
1153
-
1154
- /**
1155
- * @ngdoc function
1156
- * @name angular.equals
1157
- * @module ng
1158
- * @kind function
1159
- *
1160
- * @description
1161
- * Determines if two objects or two values are equivalent. Supports value types, regular
1162
- * expressions, arrays and objects.
1163
- *
1164
- * Two objects or values are considered equivalent if at least one of the following is true:
1165
- *
1166
- * * Both objects or values pass `===` comparison.
1167
- * * Both objects or values are of the same type and all of their properties are equal by
1168
- * comparing them with `angular.equals`.
1169
- * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1170
- * * Both values represent the same regular expression (In JavaScript,
1171
- * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1172
- * representation matches).
1173
- *
1174
- * During a property comparison, properties of `function` type and properties with names
1175
- * that begin with `$` are ignored.
1176
- *
1177
- * Scope and DOMWindow objects are being compared only by identify (`===`).
1178
- *
1179
- * @param {*} o1 Object or value to compare.
1180
- * @param {*} o2 Object or value to compare.
1181
- * @returns {boolean} True if arguments are equal.
1182
- *
1183
- * @example
1184
- <example module="equalsExample" name="equalsExample">
1185
- <file name="index.html">
1186
- <div ng-controller="ExampleController">
1187
- <form novalidate>
1188
- <h3>User 1</h3>
1189
- Name: <input type="text" ng-model="user1.name">
1190
- Age: <input type="number" ng-model="user1.age">
1191
-
1192
- <h3>User 2</h3>
1193
- Name: <input type="text" ng-model="user2.name">
1194
- Age: <input type="number" ng-model="user2.age">
1195
-
1196
- <div>
1197
- <br/>
1198
- <input type="button" value="Compare" ng-click="compare()">
1199
- </div>
1200
- User 1: <pre>{{user1 | json}}</pre>
1201
- User 2: <pre>{{user2 | json}}</pre>
1202
- Equal: <pre>{{result}}</pre>
1203
- </form>
1204
- </div>
1205
- </file>
1206
- <file name="script.js">
1207
- angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
1208
- $scope.user1 = {};
1209
- $scope.user2 = {};
1210
- $scope.compare = function() {
1211
- $scope.result = angular.equals($scope.user1, $scope.user2);
1212
- };
1213
- }]);
1214
- </file>
1215
- </example>
1216
- */
1217
- function equals(o1, o2) {
1218
- if (o1 === o2) return true;
1219
- if (o1 === null || o2 === null) return false;
1220
- // eslint-disable-next-line no-self-compare
1221
- if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1222
- var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1223
- if (t1 === t2 && t1 === 'object') {
1224
- if (isArray(o1)) {
1225
- if (!isArray(o2)) return false;
1226
- if ((length = o1.length) === o2.length) {
1227
- for (key = 0; key < length; key++) {
1228
- if (!equals(o1[key], o2[key])) return false;
1229
- }
1230
- return true;
1231
- }
1232
- } else if (isDate(o1)) {
1233
- if (!isDate(o2)) return false;
1234
- return simpleCompare(o1.getTime(), o2.getTime());
1235
- } else if (isRegExp(o1)) {
1236
- if (!isRegExp(o2)) return false;
1237
- return o1.toString() === o2.toString();
1238
- } else {
1239
- if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1240
- isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1241
- keySet = createMap();
1242
- for (key in o1) {
1243
- if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1244
- if (!equals(o1[key], o2[key])) return false;
1245
- keySet[key] = true;
1246
- }
1247
- for (key in o2) {
1248
- if (!(key in keySet) &&
1249
- key.charAt(0) !== '$' &&
1250
- isDefined(o2[key]) &&
1251
- !isFunction(o2[key])) return false;
1252
- }
1253
- return true;
1254
- }
1255
- }
1256
- return false;
1257
- }
1258
-
1259
- var csp = function() {
1260
- if (!isDefined(csp.rules)) {
1261
-
1262
-
1263
- var ngCspElement = (window.document.querySelector('[ng-csp]') ||
1264
- window.document.querySelector('[data-ng-csp]'));
1265
-
1266
- if (ngCspElement) {
1267
- var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1268
- ngCspElement.getAttribute('data-ng-csp');
1269
- csp.rules = {
1270
- noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1271
- noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1272
- };
1273
- } else {
1274
- csp.rules = {
1275
- noUnsafeEval: noUnsafeEval(),
1276
- noInlineStyle: false
1277
- };
1278
- }
1279
- }
1280
-
1281
- return csp.rules;
1282
-
1283
- function noUnsafeEval() {
1284
- try {
1285
- // eslint-disable-next-line no-new, no-new-func
1286
- new Function('');
1287
- return false;
1288
- } catch (e) {
1289
- return true;
1290
- }
1291
- }
1292
- };
1293
-
1294
- /**
1295
- * @ngdoc directive
1296
- * @module ng
1297
- * @name ngJq
1298
- *
1299
- * @element ANY
1300
- * @param {string=} ngJq the name of the library available under `window`
1301
- * to be used for angular.element
1302
- * @description
1303
- * Use this directive to force the angular.element library. This should be
1304
- * used to force either jqLite by leaving ng-jq blank or setting the name of
1305
- * the jquery variable under window (eg. jQuery).
1306
- *
1307
- * Since angular looks for this directive when it is loaded (doesn't wait for the
1308
- * DOMContentLoaded event), it must be placed on an element that comes before the script
1309
- * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1310
- * others ignored.
1311
- *
1312
- * @example
1313
- * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1314
- ```html
1315
- <!doctype html>
1316
- <html ng-app ng-jq>
1317
- ...
1318
- ...
1319
- </html>
1320
- ```
1321
- * @example
1322
- * This example shows how to use a jQuery based library of a different name.
1323
- * The library name must be available at the top most 'window'.
1324
- ```html
1325
- <!doctype html>
1326
- <html ng-app ng-jq="jQueryLib">
1327
- ...
1328
- ...
1329
- </html>
1330
- ```
1331
- */
1332
- var jq = function() {
1333
- if (isDefined(jq.name_)) return jq.name_;
1334
- var el;
1335
- var i, ii = ngAttrPrefixes.length, prefix, name;
1336
- for (i = 0; i < ii; ++i) {
1337
- prefix = ngAttrPrefixes[i];
1338
- el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]');
1339
- if (el) {
1340
- name = el.getAttribute(prefix + 'jq');
1341
- break;
1342
- }
1343
- }
1344
-
1345
- return (jq.name_ = name);
1346
- };
1347
-
1348
- function concat(array1, array2, index) {
1349
- return array1.concat(slice.call(array2, index));
1350
- }
1351
-
1352
- function sliceArgs(args, startIndex) {
1353
- return slice.call(args, startIndex || 0);
1354
- }
1355
-
1356
-
1357
- /**
1358
- * @ngdoc function
1359
- * @name angular.bind
1360
- * @module ng
1361
- * @kind function
1362
- *
1363
- * @description
1364
- * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1365
- * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1366
- * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1367
- * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1368
- *
1369
- * @param {Object} self Context which `fn` should be evaluated in.
1370
- * @param {function()} fn Function to be bound.
1371
- * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1372
- * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1373
- */
1374
- function bind(self, fn) {
1375
- var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1376
- if (isFunction(fn) && !(fn instanceof RegExp)) {
1377
- return curryArgs.length
1378
- ? function() {
1379
- return arguments.length
1380
- ? fn.apply(self, concat(curryArgs, arguments, 0))
1381
- : fn.apply(self, curryArgs);
1382
- }
1383
- : function() {
1384
- return arguments.length
1385
- ? fn.apply(self, arguments)
1386
- : fn.call(self);
1387
- };
1388
- } else {
1389
- // In IE, native methods are not functions so they cannot be bound (note: they don't need to be).
1390
- return fn;
1391
- }
1392
- }
1393
-
1394
-
1395
- function toJsonReplacer(key, value) {
1396
- var val = value;
1397
-
1398
- if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1399
- val = undefined;
1400
- } else if (isWindow(value)) {
1401
- val = '$WINDOW';
1402
- } else if (value && window.document === value) {
1403
- val = '$DOCUMENT';
1404
- } else if (isScope(value)) {
1405
- val = '$SCOPE';
1406
- }
1407
-
1408
- return val;
1409
- }
1410
-
1411
-
1412
- /**
1413
- * @ngdoc function
1414
- * @name angular.toJson
1415
- * @module ng
1416
- * @kind function
1417
- *
1418
- * @description
1419
- * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1420
- * stripped since angular uses this notation internally.
1421
- *
1422
- * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON.
1423
- * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1424
- * If set to an integer, the JSON output will contain that many spaces per indentation.
1425
- * @returns {string|undefined} JSON-ified string representing `obj`.
1426
- * @knownIssue
1427
- *
1428
- * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date`
1429
- * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the
1430
- * `Date.prototype.toJSON` method as follows:
1431
- *
1432
- * ```
1433
- * var _DatetoJSON = Date.prototype.toJSON;
1434
- * Date.prototype.toJSON = function() {
1435
- * try {
1436
- * return _DatetoJSON.call(this);
1437
- * } catch(e) {
1438
- * if (e instanceof RangeError) {
1439
- * return null;
1440
- * }
1441
- * throw e;
1442
- * }
1443
- * };
1444
- * ```
1445
- *
1446
- * See https://github.com/angular/angular.js/pull/14221 for more information.
1447
- */
1448
- function toJson(obj, pretty) {
1449
- if (isUndefined(obj)) return undefined;
1450
- if (!isNumber(pretty)) {
1451
- pretty = pretty ? 2 : null;
1452
- }
1453
- return JSON.stringify(obj, toJsonReplacer, pretty);
1454
- }
1455
-
1456
-
1457
- /**
1458
- * @ngdoc function
1459
- * @name angular.fromJson
1460
- * @module ng
1461
- * @kind function
1462
- *
1463
- * @description
1464
- * Deserializes a JSON string.
1465
- *
1466
- * @param {string} json JSON string to deserialize.
1467
- * @returns {Object|Array|string|number} Deserialized JSON string.
1468
- */
1469
- function fromJson(json) {
1470
- return isString(json)
1471
- ? JSON.parse(json)
1472
- : json;
1473
- }
1474
-
1475
-
1476
- var ALL_COLONS = /:/g;
1477
- function timezoneToOffset(timezone, fallback) {
1478
- // Support: IE 9-11 only, Edge 13-15+
1479
- // IE/Edge do not "understand" colon (`:`) in timezone
1480
- timezone = timezone.replace(ALL_COLONS, '');
1481
- var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1482
- return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1483
- }
1484
-
1485
-
1486
- function addDateMinutes(date, minutes) {
1487
- date = new Date(date.getTime());
1488
- date.setMinutes(date.getMinutes() + minutes);
1489
- return date;
1490
- }
1491
-
1492
-
1493
- function convertTimezoneToLocal(date, timezone, reverse) {
1494
- reverse = reverse ? -1 : 1;
1495
- var dateTimezoneOffset = date.getTimezoneOffset();
1496
- var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
1497
- return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
1498
- }
1499
-
1500
-
1501
- /**
1502
- * @returns {string} Returns the string representation of the element.
1503
- */
1504
- function startingTag(element) {
1505
- element = jqLite(element).clone().empty();
1506
- var elemHtml = jqLite('<div>').append(element).html();
1507
- try {
1508
- return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1509
- elemHtml.
1510
- match(/^(<[^>]+>)/)[1].
1511
- replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
1512
- } catch (e) {
1513
- return lowercase(elemHtml);
1514
- }
1515
-
1516
- }
1517
-
1518
-
1519
- /////////////////////////////////////////////////
1520
-
1521
- /**
1522
- * Tries to decode the URI component without throwing an exception.
1523
- *
1524
- * @private
1525
- * @param str value potential URI component to check.
1526
- * @returns {boolean} True if `value` can be decoded
1527
- * with the decodeURIComponent function.
1528
- */
1529
- function tryDecodeURIComponent(value) {
1530
- try {
1531
- return decodeURIComponent(value);
1532
- } catch (e) {
1533
- // Ignore any invalid uri component.
1534
- }
1535
- }
1536
-
1537
-
1538
- /**
1539
- * Parses an escaped url query string into key-value pairs.
1540
- * @returns {Object.<string,boolean|Array>}
1541
- */
1542
- function parseKeyValue(/**string*/keyValue) {
1543
- var obj = {};
1544
- forEach((keyValue || '').split('&'), function(keyValue) {
1545
- var splitPoint, key, val;
1546
- if (keyValue) {
1547
- key = keyValue = keyValue.replace(/\+/g,'%20');
1548
- splitPoint = keyValue.indexOf('=');
1549
- if (splitPoint !== -1) {
1550
- key = keyValue.substring(0, splitPoint);
1551
- val = keyValue.substring(splitPoint + 1);
1552
- }
1553
- key = tryDecodeURIComponent(key);
1554
- if (isDefined(key)) {
1555
- val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1556
- if (!hasOwnProperty.call(obj, key)) {
1557
- obj[key] = val;
1558
- } else if (isArray(obj[key])) {
1559
- obj[key].push(val);
1560
- } else {
1561
- obj[key] = [obj[key],val];
1562
- }
1563
- }
1564
- }
1565
- });
1566
- return obj;
1567
- }
1568
-
1569
- function toKeyValue(obj) {
1570
- var parts = [];
1571
- forEach(obj, function(value, key) {
1572
- if (isArray(value)) {
1573
- forEach(value, function(arrayValue) {
1574
- parts.push(encodeUriQuery(key, true) +
1575
- (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1576
- });
1577
- } else {
1578
- parts.push(encodeUriQuery(key, true) +
1579
- (value === true ? '' : '=' + encodeUriQuery(value, true)));
1580
- }
1581
- });
1582
- return parts.length ? parts.join('&') : '';
1583
- }
1584
-
1585
-
1586
- /**
1587
- * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1588
- * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1589
- * segments:
1590
- * segment = *pchar
1591
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1592
- * pct-encoded = "%" HEXDIG HEXDIG
1593
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1594
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1595
- * / "*" / "+" / "," / ";" / "="
1596
- */
1597
- function encodeUriSegment(val) {
1598
- return encodeUriQuery(val, true).
1599
- replace(/%26/gi, '&').
1600
- replace(/%3D/gi, '=').
1601
- replace(/%2B/gi, '+');
1602
- }
1603
-
1604
-
1605
- /**
1606
- * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1607
- * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1608
- * encoded per http://tools.ietf.org/html/rfc3986:
1609
- * query = *( pchar / "/" / "?" )
1610
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1611
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1612
- * pct-encoded = "%" HEXDIG HEXDIG
1613
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1614
- * / "*" / "+" / "," / ";" / "="
1615
- */
1616
- function encodeUriQuery(val, pctEncodeSpaces) {
1617
- return encodeURIComponent(val).
1618
- replace(/%40/gi, '@').
1619
- replace(/%3A/gi, ':').
1620
- replace(/%24/g, '$').
1621
- replace(/%2C/gi, ',').
1622
- replace(/%3B/gi, ';').
1623
- replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1624
- }
1625
-
1626
- var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1627
-
1628
- function getNgAttribute(element, ngAttr) {
1629
- var attr, i, ii = ngAttrPrefixes.length;
1630
- for (i = 0; i < ii; ++i) {
1631
- attr = ngAttrPrefixes[i] + ngAttr;
1632
- if (isString(attr = element.getAttribute(attr))) {
1633
- return attr;
1634
- }
1635
- }
1636
- return null;
1637
- }
1638
-
1639
- function allowAutoBootstrap(document) {
1640
- var script = document.currentScript;
1641
-
1642
- if (!script) {
1643
- // Support: IE 9-11 only
1644
- // IE does not have `document.currentScript`
1645
- return true;
1646
- }
1647
-
1648
- // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack
1649
- if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) {
1650
- return false;
1651
- }
1652
-
1653
- var attributes = script.attributes;
1654
- var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')];
1655
-
1656
- return srcs.every(function(src) {
1657
- if (!src) {
1658
- return true;
1659
- }
1660
- if (!src.value) {
1661
- return false;
1662
- }
1663
-
1664
- var link = document.createElement('a');
1665
- link.href = src.value;
1666
-
1667
- if (document.location.origin === link.origin) {
1668
- // Same-origin resources are always allowed, even for non-whitelisted schemes.
1669
- return true;
1670
- }
1671
- // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web.
1672
- // This is to prevent angular.js bundled with browser extensions from being used to bypass the
1673
- // content security policy in web pages and other browser extensions.
1674
- switch (link.protocol) {
1675
- case 'http:':
1676
- case 'https:':
1677
- case 'ftp:':
1678
- case 'blob:':
1679
- case 'file:':
1680
- case 'data:':
1681
- return true;
1682
- default:
1683
- return false;
1684
- }
1685
- });
1686
- }
1687
-
1688
- // Cached as it has to run during loading so that document.currentScript is available.
1689
- var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1690
-
1691
- /**
1692
- * @ngdoc directive
1693
- * @name ngApp
1694
- * @module ng
1695
- *
1696
- * @element ANY
1697
- * @param {angular.Module} ngApp an optional application
1698
- * {@link angular.module module} name to load.
1699
- * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1700
- * created in "strict-di" mode. This means that the application will fail to invoke functions which
1701
- * do not use explicit function annotation (and are thus unsuitable for minification), as described
1702
- * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1703
- * tracking down the root of these bugs.
1704
- *
1705
- * @description
1706
- *
1707
- * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1708
- * designates the **root element** of the application and is typically placed near the root element
1709
- * of the page - e.g. on the `<body>` or `<html>` tags.
1710
- *
1711
- * There are a few things to keep in mind when using `ngApp`:
1712
- * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1713
- * found in the document will be used to define the root element to auto-bootstrap as an
1714
- * application. To run multiple applications in an HTML document you must manually bootstrap them using
1715
- * {@link angular.bootstrap} instead.
1716
- * - AngularJS applications cannot be nested within each other.
1717
- * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`.
1718
- * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and
1719
- * {@link ngRoute.ngView `ngView`}.
1720
- * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1721
- * causing animations to stop working and making the injector inaccessible from outside the app.
1722
- *
1723
- * You can specify an **AngularJS module** to be used as the root module for the application. This
1724
- * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1725
- * should contain the application code needed or have dependencies on other modules that will
1726
- * contain the code. See {@link angular.module} for more information.
1727
- *
1728
- * In the example below if the `ngApp` directive were not placed on the `html` element then the
1729
- * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1730
- * would not be resolved to `3`.
1731
- *
1732
- * `ngApp` is the easiest, and most common way to bootstrap an application.
1733
- *
1734
- <example module="ngAppDemo" name="ng-app">
1735
- <file name="index.html">
1736
- <div ng-controller="ngAppDemoController">
1737
- I can add: {{a}} + {{b}} = {{ a+b }}
1738
- </div>
1739
- </file>
1740
- <file name="script.js">
1741
- angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1742
- $scope.a = 1;
1743
- $scope.b = 2;
1744
- });
1745
- </file>
1746
- </example>
1747
- *
1748
- * Using `ngStrictDi`, you would see something like this:
1749
- *
1750
- <example ng-app-included="true" name="strict-di">
1751
- <file name="index.html">
1752
- <div ng-app="ngAppStrictDemo" ng-strict-di>
1753
- <div ng-controller="GoodController1">
1754
- I can add: {{a}} + {{b}} = {{ a+b }}
1755
-
1756
- <p>This renders because the controller does not fail to
1757
- instantiate, by using explicit annotation style (see
1758
- script.js for details)
1759
- </p>
1760
- </div>
1761
-
1762
- <div ng-controller="GoodController2">
1763
- Name: <input ng-model="name"><br />
1764
- Hello, {{name}}!
1765
-
1766
- <p>This renders because the controller does not fail to
1767
- instantiate, by using explicit annotation style
1768
- (see script.js for details)
1769
- </p>
1770
- </div>
1771
-
1772
- <div ng-controller="BadController">
1773
- I can add: {{a}} + {{b}} = {{ a+b }}
1774
-
1775
- <p>The controller could not be instantiated, due to relying
1776
- on automatic function annotations (which are disabled in
1777
- strict mode). As such, the content of this section is not
1778
- interpolated, and there should be an error in your web console.
1779
- </p>
1780
- </div>
1781
- </div>
1782
- </file>
1783
- <file name="script.js">
1784
- angular.module('ngAppStrictDemo', [])
1785
- // BadController will fail to instantiate, due to relying on automatic function annotation,
1786
- // rather than an explicit annotation
1787
- .controller('BadController', function($scope) {
1788
- $scope.a = 1;
1789
- $scope.b = 2;
1790
- })
1791
- // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1792
- // due to using explicit annotations using the array style and $inject property, respectively.
1793
- .controller('GoodController1', ['$scope', function($scope) {
1794
- $scope.a = 1;
1795
- $scope.b = 2;
1796
- }])
1797
- .controller('GoodController2', GoodController2);
1798
- function GoodController2($scope) {
1799
- $scope.name = 'World';
1800
- }
1801
- GoodController2.$inject = ['$scope'];
1802
- </file>
1803
- <file name="style.css">
1804
- div[ng-controller] {
1805
- margin-bottom: 1em;
1806
- -webkit-border-radius: 4px;
1807
- border-radius: 4px;
1808
- border: 1px solid;
1809
- padding: .5em;
1810
- }
1811
- div[ng-controller^=Good] {
1812
- border-color: #d6e9c6;
1813
- background-color: #dff0d8;
1814
- color: #3c763d;
1815
- }
1816
- div[ng-controller^=Bad] {
1817
- border-color: #ebccd1;
1818
- background-color: #f2dede;
1819
- color: #a94442;
1820
- margin-bottom: 0;
1821
- }
1822
- </file>
1823
- </example>
1824
- */
1825
- function angularInit(element, bootstrap) {
1826
- var appElement,
1827
- module,
1828
- config = {};
1829
-
1830
- // The element `element` has priority over any other element.
1831
- forEach(ngAttrPrefixes, function(prefix) {
1832
- var name = prefix + 'app';
1833
-
1834
- if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1835
- appElement = element;
1836
- module = element.getAttribute(name);
1837
- }
1838
- });
1839
- forEach(ngAttrPrefixes, function(prefix) {
1840
- var name = prefix + 'app';
1841
- var candidate;
1842
-
1843
- if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1844
- appElement = candidate;
1845
- module = candidate.getAttribute(name);
1846
- }
1847
- });
1848
- if (appElement) {
1849
- if (!isAutoBootstrapAllowed) {
1850
- window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' +
1851
- 'an extension, document.location.href does not match.');
1852
- return;
1853
- }
1854
- config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
1855
- bootstrap(appElement, module ? [module] : [], config);
1856
- }
1857
- }
1858
-
1859
- /**
1860
- * @ngdoc function
1861
- * @name angular.bootstrap
1862
- * @module ng
1863
- * @description
1864
- * Use this function to manually start up angular application.
1865
- *
1866
- * For more information, see the {@link guide/bootstrap Bootstrap guide}.
1867
- *
1868
- * Angular will detect if it has been loaded into the browser more than once and only allow the
1869
- * first loaded script to be bootstrapped and will report a warning to the browser console for
1870
- * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1871
- * multiple instances of Angular try to work on the DOM.
1872
- *
1873
- * <div class="alert alert-warning">
1874
- * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually.
1875
- * They must use {@link ng.directive:ngApp ngApp}.
1876
- * </div>
1877
- *
1878
- * <div class="alert alert-warning">
1879
- * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion},
1880
- * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
1881
- * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
1882
- * causing animations to stop working and making the injector inaccessible from outside the app.
1883
- * </div>
1884
- *
1885
- * ```html
1886
- * <!doctype html>
1887
- * <html>
1888
- * <body>
1889
- * <div ng-controller="WelcomeController">
1890
- * {{greeting}}
1891
- * </div>
1892
- *
1893
- * <script src="angular.js"></script>
1894
- * <script>
1895
- * var app = angular.module('demo', [])
1896
- * .controller('WelcomeController', function($scope) {
1897
- * $scope.greeting = 'Welcome!';
1898
- * });
1899
- * angular.bootstrap(document, ['demo']);
1900
- * </script>
1901
- * </body>
1902
- * </html>
1903
- * ```
1904
- *
1905
- * @param {DOMElement} element DOM element which is the root of angular application.
1906
- * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1907
- * Each item in the array should be the name of a predefined module or a (DI annotated)
1908
- * function that will be invoked by the injector as a `config` block.
1909
- * See: {@link angular.module modules}
1910
- * @param {Object=} config an object for defining configuration options for the application. The
1911
- * following keys are supported:
1912
- *
1913
- * * `strictDi` - disable automatic function annotation for the application. This is meant to
1914
- * assist in finding bugs which break minified code. Defaults to `false`.
1915
- *
1916
- * @returns {auto.$injector} Returns the newly created injector for this app.
1917
- */
1918
- function bootstrap(element, modules, config) {
1919
- if (!isObject(config)) config = {};
1920
- var defaultConfig = {
1921
- strictDi: false
1922
- };
1923
- config = extend(defaultConfig, config);
1924
- var doBootstrap = function() {
1925
- element = jqLite(element);
1926
-
1927
- if (element.injector()) {
1928
- var tag = (element[0] === window.document) ? 'document' : startingTag(element);
1929
- // Encode angle brackets to prevent input from being sanitized to empty string #8683.
1930
- throw ngMinErr(
1931
- 'btstrpd',
1932
- 'App already bootstrapped with this element \'{0}\'',
1933
- tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
1934
- }
1935
-
1936
- modules = modules || [];
1937
- modules.unshift(['$provide', function($provide) {
1938
- $provide.value('$rootElement', element);
1939
- }]);
1940
-
1941
- if (config.debugInfoEnabled) {
1942
- // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1943
- modules.push(['$compileProvider', function($compileProvider) {
1944
- $compileProvider.debugInfoEnabled(true);
1945
- }]);
1946
- }
1947
-
1948
- modules.unshift('ng');
1949
- var injector = createInjector(modules, config.strictDi);
1950
- injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1951
- function bootstrapApply(scope, element, compile, injector) {
1952
- scope.$apply(function() {
1953
- element.data('$injector', injector);
1954
- compile(element)(scope);
1955
- });
1956
- }]
1957
- );
1958
- return injector;
1959
- };
1960
-
1961
- var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1962
- var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1963
-
1964
- if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1965
- config.debugInfoEnabled = true;
1966
- window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1967
- }
1968
-
1969
- if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1970
- return doBootstrap();
1971
- }
1972
-
1973
- window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1974
- angular.resumeBootstrap = function(extraModules) {
1975
- forEach(extraModules, function(module) {
1976
- modules.push(module);
1977
- });
1978
- return doBootstrap();
1979
- };
1980
-
1981
- if (isFunction(angular.resumeDeferredBootstrap)) {
1982
- angular.resumeDeferredBootstrap();
1983
- }
1984
- }
1985
-
1986
- /**
1987
- * @ngdoc function
1988
- * @name angular.reloadWithDebugInfo
1989
- * @module ng
1990
- * @description
1991
- * Use this function to reload the current application with debug information turned on.
1992
- * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1993
- *
1994
- * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1995
- */
1996
- function reloadWithDebugInfo() {
1997
- window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1998
- window.location.reload();
1999
- }
2000
-
2001
- /**
2002
- * @name angular.getTestability
2003
- * @module ng
2004
- * @description
2005
- * Get the testability service for the instance of Angular on the given
2006
- * element.
2007
- * @param {DOMElement} element DOM element which is the root of angular application.
2008
- */
2009
- function getTestability(rootElement) {
2010
- var injector = angular.element(rootElement).injector();
2011
- if (!injector) {
2012
- throw ngMinErr('test',
2013
- 'no injector found for element argument to getTestability');
2014
- }
2015
- return injector.get('$$testability');
2016
- }
2017
-
2018
- var SNAKE_CASE_REGEXP = /[A-Z]/g;
2019
- function snake_case(name, separator) {
2020
- separator = separator || '_';
2021
- return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
2022
- return (pos ? separator : '') + letter.toLowerCase();
2023
- });
2024
- }
2025
-
2026
- var bindJQueryFired = false;
2027
- function bindJQuery() {
2028
- var originalCleanData;
2029
-
2030
- if (bindJQueryFired) {
2031
- return;
2032
- }
2033
-
2034
- // bind to jQuery if present;
2035
- var jqName = jq();
2036
- jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
2037
- !jqName ? undefined : // use jqLite
2038
- window[jqName]; // use jQuery specified by `ngJq`
2039
-
2040
- // Use jQuery if it exists with proper functionality, otherwise default to us.
2041
- // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
2042
- // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
2043
- // versions. It will not work for sure with jQuery <1.7, though.
2044
- if (jQuery && jQuery.fn.on) {
2045
- jqLite = jQuery;
2046
- extend(jQuery.fn, {
2047
- scope: JQLitePrototype.scope,
2048
- isolateScope: JQLitePrototype.isolateScope,
2049
- controller: /** @type {?} */ (JQLitePrototype).controller,
2050
- injector: JQLitePrototype.injector,
2051
- inheritedData: JQLitePrototype.inheritedData
2052
- });
2053
-
2054
- // All nodes removed from the DOM via various jQuery APIs like .remove()
2055
- // are passed through jQuery.cleanData. Monkey-patch this method to fire
2056
- // the $destroy event on all removed nodes.
2057
- originalCleanData = jQuery.cleanData;
2058
- jQuery.cleanData = function(elems) {
2059
- var events;
2060
- for (var i = 0, elem; (elem = elems[i]) != null; i++) {
2061
- events = jQuery._data(elem, 'events');
2062
- if (events && events.$destroy) {
2063
- jQuery(elem).triggerHandler('$destroy');
2064
- }
2065
- }
2066
- originalCleanData(elems);
2067
- };
2068
- } else {
2069
- jqLite = JQLite;
2070
- }
2071
-
2072
- angular.element = jqLite;
2073
-
2074
- // Prevent double-proxying.
2075
- bindJQueryFired = true;
2076
- }
2077
-
2078
- /**
2079
- * throw error if the argument is falsy.
2080
- */
2081
- function assertArg(arg, name, reason) {
2082
- if (!arg) {
2083
- throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required'));
2084
- }
2085
- return arg;
2086
- }
2087
-
2088
- function assertArgFn(arg, name, acceptArrayAnnotation) {
2089
- if (acceptArrayAnnotation && isArray(arg)) {
2090
- arg = arg[arg.length - 1];
2091
- }
2092
-
2093
- assertArg(isFunction(arg), name, 'not a function, got ' +
2094
- (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
2095
- return arg;
2096
- }
2097
-
2098
- /**
2099
- * throw error if the name given is hasOwnProperty
2100
- * @param {String} name the name to test
2101
- * @param {String} context the context in which the name is used, such as module or directive
2102
- */
2103
- function assertNotHasOwnProperty(name, context) {
2104
- if (name === 'hasOwnProperty') {
2105
- throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2106
- }
2107
- }
2108
-
2109
- /**
2110
- * Return the value accessible from the object by path. Any undefined traversals are ignored
2111
- * @param {Object} obj starting object
2112
- * @param {String} path path to traverse
2113
- * @param {boolean} [bindFnToScope=true]
2114
- * @returns {Object} value as accessible by path
2115
- */
2116
- //TODO(misko): this function needs to be removed
2117
- function getter(obj, path, bindFnToScope) {
2118
- if (!path) return obj;
2119
- var keys = path.split('.');
2120
- var key;
2121
- var lastInstance = obj;
2122
- var len = keys.length;
2123
-
2124
- for (var i = 0; i < len; i++) {
2125
- key = keys[i];
2126
- if (obj) {
2127
- obj = (lastInstance = obj)[key];
2128
- }
2129
- }
2130
- if (!bindFnToScope && isFunction(obj)) {
2131
- return bind(lastInstance, obj);
2132
- }
2133
- return obj;
2134
- }
2135
-
2136
- /**
2137
- * Return the DOM siblings between the first and last node in the given array.
2138
- * @param {Array} array like object
2139
- * @returns {Array} the inputted object or a jqLite collection containing the nodes
2140
- */
2141
- function getBlockNodes(nodes) {
2142
- // TODO(perf): update `nodes` instead of creating a new object?
2143
- var node = nodes[0];
2144
- var endNode = nodes[nodes.length - 1];
2145
- var blockNodes;
2146
-
2147
- for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
2148
- if (blockNodes || nodes[i] !== node) {
2149
- if (!blockNodes) {
2150
- blockNodes = jqLite(slice.call(nodes, 0, i));
2151
- }
2152
- blockNodes.push(node);
2153
- }
2154
- }
2155
-
2156
- return blockNodes || nodes;
2157
- }
2158
-
2159
-
2160
- /**
2161
- * Creates a new object without a prototype. This object is useful for lookup without having to
2162
- * guard against prototypically inherited properties via hasOwnProperty.
2163
- *
2164
- * Related micro-benchmarks:
2165
- * - http://jsperf.com/object-create2
2166
- * - http://jsperf.com/proto-map-lookup/2
2167
- * - http://jsperf.com/for-in-vs-object-keys2
2168
- *
2169
- * @returns {Object}
2170
- */
2171
- function createMap() {
2172
- return Object.create(null);
2173
- }
2174
-
2175
- function stringify(value) {
2176
- if (value == null) { // null || undefined
2177
- return '';
2178
- }
2179
- switch (typeof value) {
2180
- case 'string':
2181
- break;
2182
- case 'number':
2183
- value = '' + value;
2184
- break;
2185
- default:
2186
- if (hasCustomToString(value) && !isArray(value) && !isDate(value)) {
2187
- value = value.toString();
2188
- } else {
2189
- value = toJson(value);
2190
- }
2191
- }
2192
-
2193
- return value;
2194
- }
2195
-
2196
- var NODE_TYPE_ELEMENT = 1;
2197
- var NODE_TYPE_ATTRIBUTE = 2;
2198
- var NODE_TYPE_TEXT = 3;
2199
- var NODE_TYPE_COMMENT = 8;
2200
- var NODE_TYPE_DOCUMENT = 9;
2201
- var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
2202
-
2203
- /**
2204
- * @ngdoc type
2205
- * @name angular.Module
2206
- * @module ng
2207
- * @description
2208
- *
2209
- * Interface for configuring angular {@link angular.module modules}.
2210
- */
2211
-
2212
- function setupModuleLoader(window) {
2213
-
2214
- var $injectorMinErr = minErr('$injector');
2215
- var ngMinErr = minErr('ng');
2216
-
2217
- function ensure(obj, name, factory) {
2218
- return obj[name] || (obj[name] = factory());
2219
- }
2220
-
2221
- var angular = ensure(window, 'angular', Object);
2222
-
2223
- // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2224
- angular.$$minErr = angular.$$minErr || minErr;
2225
-
2226
- return ensure(angular, 'module', function() {
2227
- /** @type {Object.<string, angular.Module>} */
2228
- var modules = {};
2229
-
2230
- /**
2231
- * @ngdoc function
2232
- * @name angular.module
2233
- * @module ng
2234
- * @description
2235
- *
2236
- * The `angular.module` is a global place for creating, registering and retrieving Angular
2237
- * modules.
2238
- * All modules (angular core or 3rd party) that should be available to an application must be
2239
- * registered using this mechanism.
2240
- *
2241
- * Passing one argument retrieves an existing {@link angular.Module},
2242
- * whereas passing more than one argument creates a new {@link angular.Module}
2243
- *
2244
- *
2245
- * # Module
2246
- *
2247
- * A module is a collection of services, directives, controllers, filters, and configuration information.
2248
- * `angular.module` is used to configure the {@link auto.$injector $injector}.
2249
- *
2250
- * ```js
2251
- * // Create a new module
2252
- * var myModule = angular.module('myModule', []);
2253
- *
2254
- * // register a new service
2255
- * myModule.value('appName', 'MyCoolApp');
2256
- *
2257
- * // configure existing services inside initialization blocks.
2258
- * myModule.config(['$locationProvider', function($locationProvider) {
2259
- * // Configure existing providers
2260
- * $locationProvider.hashPrefix('!');
2261
- * }]);
2262
- * ```
2263
- *
2264
- * Then you can create an injector and load your modules like this:
2265
- *
2266
- * ```js
2267
- * var injector = angular.injector(['ng', 'myModule'])
2268
- * ```
2269
- *
2270
- * However it's more likely that you'll just use
2271
- * {@link ng.directive:ngApp ngApp} or
2272
- * {@link angular.bootstrap} to simplify this process for you.
2273
- *
2274
- * @param {!string} name The name of the module to create or retrieve.
2275
- * @param {!Array.<string>=} requires If specified then new module is being created. If
2276
- * unspecified then the module is being retrieved for further configuration.
2277
- * @param {Function=} configFn Optional configuration function for the module. Same as
2278
- * {@link angular.Module#config Module#config()}.
2279
- * @returns {angular.Module} new module with the {@link angular.Module} api.
2280
- */
2281
- return function module(name, requires, configFn) {
2282
-
2283
- var info = {};
2284
-
2285
- var assertNotHasOwnProperty = function(name, context) {
2286
- if (name === 'hasOwnProperty') {
2287
- throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2288
- }
2289
- };
2290
-
2291
- assertNotHasOwnProperty(name, 'module');
2292
- if (requires && modules.hasOwnProperty(name)) {
2293
- modules[name] = null;
2294
- }
2295
- return ensure(modules, name, function() {
2296
- if (!requires) {
2297
- throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' +
2298
- 'the module name or forgot to load it. If registering a module ensure that you ' +
2299
- 'specify the dependencies as the second argument.', name);
2300
- }
2301
-
2302
- /** @type {!Array.<Array.<*>>} */
2303
- var invokeQueue = [];
2304
-
2305
- /** @type {!Array.<Function>} */
2306
- var configBlocks = [];
2307
-
2308
- /** @type {!Array.<Function>} */
2309
- var runBlocks = [];
2310
-
2311
- var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2312
-
2313
- /** @type {angular.Module} */
2314
- var moduleInstance = {
2315
- // Private state
2316
- _invokeQueue: invokeQueue,
2317
- _configBlocks: configBlocks,
2318
- _runBlocks: runBlocks,
2319
-
2320
- /**
2321
- * @ngdoc method
2322
- * @name angular.Module#info
2323
- * @module ng
2324
- *
2325
- * @param {Object=} info Information about the module
2326
- * @returns {Object|Module} The current info object for this module if called as a getter,
2327
- * or `this` if called as a setter.
2328
- *
2329
- * @description
2330
- * Read and write custom information about this module.
2331
- * For example you could put the version of the module in here.
2332
- *
2333
- * ```js
2334
- * angular.module('myModule', []).info({ version: '1.0.0' });
2335
- * ```
2336
- *
2337
- * The version could then be read back out by accessing the module elsewhere:
2338
- *
2339
- * ```
2340
- * var version = angular.module('myModule').info().version;
2341
- * ```
2342
- *
2343
- * You can also retrieve this information during runtime via the
2344
- * {@link $injector#modules `$injector.modules`} property:
2345
- *
2346
- * ```js
2347
- * var version = $injector.modules['myModule'].info().version;
2348
- * ```
2349
- */
2350
- info: function(value) {
2351
- if (isDefined(value)) {
2352
- if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value');
2353
- info = value;
2354
- return this;
2355
- }
2356
- return info;
2357
- },
2358
-
2359
- /**
2360
- * @ngdoc property
2361
- * @name angular.Module#requires
2362
- * @module ng
2363
- *
2364
- * @description
2365
- * Holds the list of modules which the injector will load before the current module is
2366
- * loaded.
2367
- */
2368
- requires: requires,
2369
-
2370
- /**
2371
- * @ngdoc property
2372
- * @name angular.Module#name
2373
- * @module ng
2374
- *
2375
- * @description
2376
- * Name of the module.
2377
- */
2378
- name: name,
2379
-
2380
-
2381
- /**
2382
- * @ngdoc method
2383
- * @name angular.Module#provider
2384
- * @module ng
2385
- * @param {string} name service name
2386
- * @param {Function} providerType Construction function for creating new instance of the
2387
- * service.
2388
- * @description
2389
- * See {@link auto.$provide#provider $provide.provider()}.
2390
- */
2391
- provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2392
-
2393
- /**
2394
- * @ngdoc method
2395
- * @name angular.Module#factory
2396
- * @module ng
2397
- * @param {string} name service name
2398
- * @param {Function} providerFunction Function for creating new instance of the service.
2399
- * @description
2400
- * See {@link auto.$provide#factory $provide.factory()}.
2401
- */
2402
- factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2403
-
2404
- /**
2405
- * @ngdoc method
2406
- * @name angular.Module#service
2407
- * @module ng
2408
- * @param {string} name service name
2409
- * @param {Function} constructor A constructor function that will be instantiated.
2410
- * @description
2411
- * See {@link auto.$provide#service $provide.service()}.
2412
- */
2413
- service: invokeLaterAndSetModuleName('$provide', 'service'),
2414
-
2415
- /**
2416
- * @ngdoc method
2417
- * @name angular.Module#value
2418
- * @module ng
2419
- * @param {string} name service name
2420
- * @param {*} object Service instance object.
2421
- * @description
2422
- * See {@link auto.$provide#value $provide.value()}.
2423
- */
2424
- value: invokeLater('$provide', 'value'),
2425
-
2426
- /**
2427
- * @ngdoc method
2428
- * @name angular.Module#constant
2429
- * @module ng
2430
- * @param {string} name constant name
2431
- * @param {*} object Constant value.
2432
- * @description
2433
- * Because the constants are fixed, they get applied before other provide methods.
2434
- * See {@link auto.$provide#constant $provide.constant()}.
2435
- */
2436
- constant: invokeLater('$provide', 'constant', 'unshift'),
2437
-
2438
- /**
2439
- * @ngdoc method
2440
- * @name angular.Module#decorator
2441
- * @module ng
2442
- * @param {string} name The name of the service to decorate.
2443
- * @param {Function} decorFn This function will be invoked when the service needs to be
2444
- * instantiated and should return the decorated service instance.
2445
- * @description
2446
- * See {@link auto.$provide#decorator $provide.decorator()}.
2447
- */
2448
- decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),
2449
-
2450
- /**
2451
- * @ngdoc method
2452
- * @name angular.Module#animation
2453
- * @module ng
2454
- * @param {string} name animation name
2455
- * @param {Function} animationFactory Factory function for creating new instance of an
2456
- * animation.
2457
- * @description
2458
- *
2459
- * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2460
- *
2461
- *
2462
- * Defines an animation hook that can be later used with
2463
- * {@link $animate $animate} service and directives that use this service.
2464
- *
2465
- * ```js
2466
- * module.animation('.animation-name', function($inject1, $inject2) {
2467
- * return {
2468
- * eventName : function(element, done) {
2469
- * //code to run the animation
2470
- * //once complete, then run done()
2471
- * return function cancellationFunction(element) {
2472
- * //code to cancel the animation
2473
- * }
2474
- * }
2475
- * }
2476
- * })
2477
- * ```
2478
- *
2479
- * See {@link ng.$animateProvider#register $animateProvider.register()} and
2480
- * {@link ngAnimate ngAnimate module} for more information.
2481
- */
2482
- animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2483
-
2484
- /**
2485
- * @ngdoc method
2486
- * @name angular.Module#filter
2487
- * @module ng
2488
- * @param {string} name Filter name - this must be a valid angular expression identifier
2489
- * @param {Function} filterFactory Factory function for creating new instance of filter.
2490
- * @description
2491
- * See {@link ng.$filterProvider#register $filterProvider.register()}.
2492
- *
2493
- * <div class="alert alert-warning">
2494
- * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2495
- * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2496
- * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2497
- * (`myapp_subsection_filterx`).
2498
- * </div>
2499
- */
2500
- filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2501
-
2502
- /**
2503
- * @ngdoc method
2504
- * @name angular.Module#controller
2505
- * @module ng
2506
- * @param {string|Object} name Controller name, or an object map of controllers where the
2507
- * keys are the names and the values are the constructors.
2508
- * @param {Function} constructor Controller constructor function.
2509
- * @description
2510
- * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2511
- */
2512
- controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2513
-
2514
- /**
2515
- * @ngdoc method
2516
- * @name angular.Module#directive
2517
- * @module ng
2518
- * @param {string|Object} name Directive name, or an object map of directives where the
2519
- * keys are the names and the values are the factories.
2520
- * @param {Function} directiveFactory Factory function for creating new instance of
2521
- * directives.
2522
- * @description
2523
- * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2524
- */
2525
- directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2526
-
2527
- /**
2528
- * @ngdoc method
2529
- * @name angular.Module#component
2530
- * @module ng
2531
- * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
2532
- * @param {Object} options Component definition object (a simplified
2533
- * {@link ng.$compile#directive-definition-object directive definition object})
2534
- *
2535
- * @description
2536
- * See {@link ng.$compileProvider#component $compileProvider.component()}.
2537
- */
2538
- component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
2539
-
2540
- /**
2541
- * @ngdoc method
2542
- * @name angular.Module#config
2543
- * @module ng
2544
- * @param {Function} configFn Execute this function on module load. Useful for service
2545
- * configuration.
2546
- * @description
2547
- * Use this method to register work which needs to be performed on module loading.
2548
- * For more about how to configure services, see
2549
- * {@link providers#provider-recipe Provider Recipe}.
2550
- */
2551
- config: config,
2552
-
2553
- /**
2554
- * @ngdoc method
2555
- * @name angular.Module#run
2556
- * @module ng
2557
- * @param {Function} initializationFn Execute this function after injector creation.
2558
- * Useful for application initialization.
2559
- * @description
2560
- * Use this method to register work which should be performed when the injector is done
2561
- * loading all modules.
2562
- */
2563
- run: function(block) {
2564
- runBlocks.push(block);
2565
- return this;
2566
- }
2567
- };
2568
-
2569
- if (configFn) {
2570
- config(configFn);
2571
- }
2572
-
2573
- return moduleInstance;
2574
-
2575
- /**
2576
- * @param {string} provider
2577
- * @param {string} method
2578
- * @param {String=} insertMethod
2579
- * @returns {angular.Module}
2580
- */
2581
- function invokeLater(provider, method, insertMethod, queue) {
2582
- if (!queue) queue = invokeQueue;
2583
- return function() {
2584
- queue[insertMethod || 'push']([provider, method, arguments]);
2585
- return moduleInstance;
2586
- };
2587
- }
2588
-
2589
- /**
2590
- * @param {string} provider
2591
- * @param {string} method
2592
- * @returns {angular.Module}
2593
- */
2594
- function invokeLaterAndSetModuleName(provider, method, queue) {
2595
- if (!queue) queue = invokeQueue;
2596
- return function(recipeName, factoryFunction) {
2597
- if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2598
- queue.push([provider, method, arguments]);
2599
- return moduleInstance;
2600
- };
2601
- }
2602
- });
2603
- };
2604
- });
2605
-
2606
- }
2607
-
2608
- /* global shallowCopy: true */
2609
-
2610
- /**
2611
- * Creates a shallow copy of an object, an array or a primitive.
2612
- *
2613
- * Assumes that there are no proto properties for objects.
2614
- */
2615
- function shallowCopy(src, dst) {
2616
- if (isArray(src)) {
2617
- dst = dst || [];
2618
-
2619
- for (var i = 0, ii = src.length; i < ii; i++) {
2620
- dst[i] = src[i];
2621
- }
2622
- } else if (isObject(src)) {
2623
- dst = dst || {};
2624
-
2625
- for (var key in src) {
2626
- if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
2627
- dst[key] = src[key];
2628
- }
2629
- }
2630
- }
2631
-
2632
- return dst || src;
2633
- }
2634
-
2635
- /* exported toDebugString */
2636
-
2637
- function serializeObject(obj, maxDepth) {
2638
- var seen = [];
2639
-
2640
- // There is no direct way to stringify object until reaching a specific depth
2641
- // and a very deep object can cause a performance issue, so we copy the object
2642
- // based on this specific depth and then stringify it.
2643
- if (isValidObjectMaxDepth(maxDepth)) {
2644
- // This file is also included in `angular-loader`, so `copy()` might not always be available in
2645
- // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed.
2646
- obj = angular.copy(obj, null, maxDepth);
2647
- }
2648
- return JSON.stringify(obj, function(key, val) {
2649
- val = toJsonReplacer(key, val);
2650
- if (isObject(val)) {
2651
-
2652
- if (seen.indexOf(val) >= 0) return '...';
2653
-
2654
- seen.push(val);
2655
- }
2656
- return val;
2657
- });
2658
- }
2659
-
2660
- function toDebugString(obj, maxDepth) {
2661
- if (typeof obj === 'function') {
2662
- return obj.toString().replace(/ \{[\s\S]*$/, '');
2663
- } else if (isUndefined(obj)) {
2664
- return 'undefined';
2665
- } else if (typeof obj !== 'string') {
2666
- return serializeObject(obj, maxDepth);
2667
- }
2668
- return obj;
2669
- }
2670
-
2671
- /* global angularModule: true,
2672
- version: true,
2673
-
2674
- $CompileProvider,
2675
-
2676
- htmlAnchorDirective,
2677
- inputDirective,
2678
- inputDirective,
2679
- formDirective,
2680
- scriptDirective,
2681
- selectDirective,
2682
- optionDirective,
2683
- ngBindDirective,
2684
- ngBindHtmlDirective,
2685
- ngBindTemplateDirective,
2686
- ngClassDirective,
2687
- ngClassEvenDirective,
2688
- ngClassOddDirective,
2689
- ngCloakDirective,
2690
- ngControllerDirective,
2691
- ngFormDirective,
2692
- ngHideDirective,
2693
- ngIfDirective,
2694
- ngIncludeDirective,
2695
- ngIncludeFillContentDirective,
2696
- ngInitDirective,
2697
- ngNonBindableDirective,
2698
- ngPluralizeDirective,
2699
- ngRepeatDirective,
2700
- ngShowDirective,
2701
- ngStyleDirective,
2702
- ngSwitchDirective,
2703
- ngSwitchWhenDirective,
2704
- ngSwitchDefaultDirective,
2705
- ngOptionsDirective,
2706
- ngTranscludeDirective,
2707
- ngModelDirective,
2708
- ngListDirective,
2709
- ngChangeDirective,
2710
- patternDirective,
2711
- patternDirective,
2712
- requiredDirective,
2713
- requiredDirective,
2714
- minlengthDirective,
2715
- minlengthDirective,
2716
- maxlengthDirective,
2717
- maxlengthDirective,
2718
- ngValueDirective,
2719
- ngModelOptionsDirective,
2720
- ngAttributeAliasDirectives,
2721
- ngEventDirectives,
2722
-
2723
- $AnchorScrollProvider,
2724
- $AnimateProvider,
2725
- $CoreAnimateCssProvider,
2726
- $$CoreAnimateJsProvider,
2727
- $$CoreAnimateQueueProvider,
2728
- $$AnimateRunnerFactoryProvider,
2729
- $$AnimateAsyncRunFactoryProvider,
2730
- $BrowserProvider,
2731
- $CacheFactoryProvider,
2732
- $ControllerProvider,
2733
- $DateProvider,
2734
- $DocumentProvider,
2735
- $$IsDocumentHiddenProvider,
2736
- $ExceptionHandlerProvider,
2737
- $FilterProvider,
2738
- $$ForceReflowProvider,
2739
- $InterpolateProvider,
2740
- $IntervalProvider,
2741
- $HttpProvider,
2742
- $HttpParamSerializerProvider,
2743
- $HttpParamSerializerJQLikeProvider,
2744
- $HttpBackendProvider,
2745
- $xhrFactoryProvider,
2746
- $jsonpCallbacksProvider,
2747
- $LocationProvider,
2748
- $LogProvider,
2749
- $$MapProvider,
2750
- $ParseProvider,
2751
- $RootScopeProvider,
2752
- $QProvider,
2753
- $$QProvider,
2754
- $$SanitizeUriProvider,
2755
- $SceProvider,
2756
- $SceDelegateProvider,
2757
- $SnifferProvider,
2758
- $TemplateCacheProvider,
2759
- $TemplateRequestProvider,
2760
- $$TestabilityProvider,
2761
- $TimeoutProvider,
2762
- $$RAFProvider,
2763
- $WindowProvider,
2764
- $$jqLiteProvider,
2765
- $$CookieReaderProvider
2766
- */
2767
-
2768
-
2769
- /**
2770
- * @ngdoc object
2771
- * @name angular.version
2772
- * @module ng
2773
- * @description
2774
- * An object that contains information about the current AngularJS version.
2775
- *
2776
- * This object has the following properties:
2777
- *
2778
- * - `full` – `{string}` – Full version string, such as "0.9.18".
2779
- * - `major` – `{number}` – Major version number, such as "0".
2780
- * - `minor` – `{number}` – Minor version number, such as "9".
2781
- * - `dot` – `{number}` – Dot version number, such as "18".
2782
- * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2783
- */
2784
- var version = {
2785
- // These placeholder strings will be replaced by grunt's `build` task.
2786
- // They need to be double- or single-quoted.
2787
- full: '1.6.5',
2788
- major: 1,
2789
- minor: 6,
2790
- dot: 5,
2791
- codeName: 'toffee-salinization'
2792
- };
2793
-
2794
-
2795
- function publishExternalAPI(angular) {
2796
- extend(angular, {
2797
- 'errorHandlingConfig': errorHandlingConfig,
2798
- 'bootstrap': bootstrap,
2799
- 'copy': copy,
2800
- 'extend': extend,
2801
- 'merge': merge,
2802
- 'equals': equals,
2803
- 'element': jqLite,
2804
- 'forEach': forEach,
2805
- 'injector': createInjector,
2806
- 'noop': noop,
2807
- 'bind': bind,
2808
- 'toJson': toJson,
2809
- 'fromJson': fromJson,
2810
- 'identity': identity,
2811
- 'isUndefined': isUndefined,
2812
- 'isDefined': isDefined,
2813
- 'isString': isString,
2814
- 'isFunction': isFunction,
2815
- 'isObject': isObject,
2816
- 'isNumber': isNumber,
2817
- 'isElement': isElement,
2818
- 'isArray': isArray,
2819
- 'version': version,
2820
- 'isDate': isDate,
2821
- 'lowercase': lowercase,
2822
- 'uppercase': uppercase,
2823
- 'callbacks': {$$counter: 0},
2824
- 'getTestability': getTestability,
2825
- 'reloadWithDebugInfo': reloadWithDebugInfo,
2826
- '$$minErr': minErr,
2827
- '$$csp': csp,
2828
- '$$encodeUriSegment': encodeUriSegment,
2829
- '$$encodeUriQuery': encodeUriQuery,
2830
- '$$stringify': stringify
2831
- });
2832
-
2833
- angularModule = setupModuleLoader(window);
2834
-
2835
- angularModule('ng', ['ngLocale'], ['$provide',
2836
- function ngModule($provide) {
2837
- // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2838
- $provide.provider({
2839
- $$sanitizeUri: $$SanitizeUriProvider
2840
- });
2841
- $provide.provider('$compile', $CompileProvider).
2842
- directive({
2843
- a: htmlAnchorDirective,
2844
- input: inputDirective,
2845
- textarea: inputDirective,
2846
- form: formDirective,
2847
- script: scriptDirective,
2848
- select: selectDirective,
2849
- option: optionDirective,
2850
- ngBind: ngBindDirective,
2851
- ngBindHtml: ngBindHtmlDirective,
2852
- ngBindTemplate: ngBindTemplateDirective,
2853
- ngClass: ngClassDirective,
2854
- ngClassEven: ngClassEvenDirective,
2855
- ngClassOdd: ngClassOddDirective,
2856
- ngCloak: ngCloakDirective,
2857
- ngController: ngControllerDirective,
2858
- ngForm: ngFormDirective,
2859
- ngHide: ngHideDirective,
2860
- ngIf: ngIfDirective,
2861
- ngInclude: ngIncludeDirective,
2862
- ngInit: ngInitDirective,
2863
- ngNonBindable: ngNonBindableDirective,
2864
- ngPluralize: ngPluralizeDirective,
2865
- ngRepeat: ngRepeatDirective,
2866
- ngShow: ngShowDirective,
2867
- ngStyle: ngStyleDirective,
2868
- ngSwitch: ngSwitchDirective,
2869
- ngSwitchWhen: ngSwitchWhenDirective,
2870
- ngSwitchDefault: ngSwitchDefaultDirective,
2871
- ngOptions: ngOptionsDirective,
2872
- ngTransclude: ngTranscludeDirective,
2873
- ngModel: ngModelDirective,
2874
- ngList: ngListDirective,
2875
- ngChange: ngChangeDirective,
2876
- pattern: patternDirective,
2877
- ngPattern: patternDirective,
2878
- required: requiredDirective,
2879
- ngRequired: requiredDirective,
2880
- minlength: minlengthDirective,
2881
- ngMinlength: minlengthDirective,
2882
- maxlength: maxlengthDirective,
2883
- ngMaxlength: maxlengthDirective,
2884
- ngValue: ngValueDirective,
2885
- ngModelOptions: ngModelOptionsDirective
2886
- }).
2887
- directive({
2888
- ngInclude: ngIncludeFillContentDirective
2889
- }).
2890
- directive(ngAttributeAliasDirectives).
2891
- directive(ngEventDirectives);
2892
- $provide.provider({
2893
- $anchorScroll: $AnchorScrollProvider,
2894
- $animate: $AnimateProvider,
2895
- $animateCss: $CoreAnimateCssProvider,
2896
- $$animateJs: $$CoreAnimateJsProvider,
2897
- $$animateQueue: $$CoreAnimateQueueProvider,
2898
- $$AnimateRunner: $$AnimateRunnerFactoryProvider,
2899
- $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
2900
- $browser: $BrowserProvider,
2901
- $cacheFactory: $CacheFactoryProvider,
2902
- $controller: $ControllerProvider,
2903
- $document: $DocumentProvider,
2904
- $$isDocumentHidden: $$IsDocumentHiddenProvider,
2905
- $exceptionHandler: $ExceptionHandlerProvider,
2906
- $filter: $FilterProvider,
2907
- $$forceReflow: $$ForceReflowProvider,
2908
- $interpolate: $InterpolateProvider,
2909
- $interval: $IntervalProvider,
2910
- $http: $HttpProvider,
2911
- $httpParamSerializer: $HttpParamSerializerProvider,
2912
- $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2913
- $httpBackend: $HttpBackendProvider,
2914
- $xhrFactory: $xhrFactoryProvider,
2915
- $jsonpCallbacks: $jsonpCallbacksProvider,
2916
- $location: $LocationProvider,
2917
- $log: $LogProvider,
2918
- $parse: $ParseProvider,
2919
- $rootScope: $RootScopeProvider,
2920
- $q: $QProvider,
2921
- $$q: $$QProvider,
2922
- $sce: $SceProvider,
2923
- $sceDelegate: $SceDelegateProvider,
2924
- $sniffer: $SnifferProvider,
2925
- $templateCache: $TemplateCacheProvider,
2926
- $templateRequest: $TemplateRequestProvider,
2927
- $$testability: $$TestabilityProvider,
2928
- $timeout: $TimeoutProvider,
2929
- $window: $WindowProvider,
2930
- $$rAF: $$RAFProvider,
2931
- $$jqLite: $$jqLiteProvider,
2932
- $$Map: $$MapProvider,
2933
- $$cookieReader: $$CookieReaderProvider
2934
- });
2935
- }
2936
- ])
2937
- .info({ angularVersion: '1.6.5' });
2938
- }
2939
-
2940
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2941
- * Any commits to this file should be reviewed with security in mind. *
2942
- * Changes to this file can potentially create security vulnerabilities. *
2943
- * An approval from 2 Core members with history of modifying *
2944
- * this file is required. *
2945
- * *
2946
- * Does the change somehow allow for arbitrary javascript to be executed? *
2947
- * Or allows for someone to change the prototype of built-in objects? *
2948
- * Or gives undesired access to variables likes document or window? *
2949
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2950
-
2951
- /* global
2952
- JQLitePrototype: true,
2953
- BOOLEAN_ATTR: true,
2954
- ALIASED_ATTR: true
2955
- */
2956
-
2957
- //////////////////////////////////
2958
- //JQLite
2959
- //////////////////////////////////
2960
-
2961
- /**
2962
- * @ngdoc function
2963
- * @name angular.element
2964
- * @module ng
2965
- * @kind function
2966
- *
2967
- * @description
2968
- * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2969
- *
2970
- * If jQuery is available, `angular.element` is an alias for the
2971
- * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2972
- * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
2973
- *
2974
- * jqLite is a tiny, API-compatible subset of jQuery that allows
2975
- * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
2976
- * commonly needed functionality with the goal of having a very small footprint.
2977
- *
2978
- * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
2979
- * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
2980
- * specific version of jQuery if multiple versions exist on the page.
2981
- *
2982
- * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or
2983
- * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
2984
- *
2985
- * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
2986
- * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
2987
- * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
2988
- *
2989
- * ## Angular's jqLite
2990
- * jqLite provides only the following jQuery methods:
2991
- *
2992
- * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
2993
- * - [`after()`](http://api.jquery.com/after/)
2994
- * - [`append()`](http://api.jquery.com/append/)
2995
- * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2996
- * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
2997
- * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2998
- * - [`clone()`](http://api.jquery.com/clone/)
2999
- * - [`contents()`](http://api.jquery.com/contents/)
3000
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
3001
- * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
3002
- * - [`data()`](http://api.jquery.com/data/)
3003
- * - [`detach()`](http://api.jquery.com/detach/)
3004
- * - [`empty()`](http://api.jquery.com/empty/)
3005
- * - [`eq()`](http://api.jquery.com/eq/)
3006
- * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
3007
- * - [`hasClass()`](http://api.jquery.com/hasClass/)
3008
- * - [`html()`](http://api.jquery.com/html/)
3009
- * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
3010
- * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
3011
- * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
3012
- * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
3013
- * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
3014
- * - [`prepend()`](http://api.jquery.com/prepend/)
3015
- * - [`prop()`](http://api.jquery.com/prop/)
3016
- * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`)
3017
- * - [`remove()`](http://api.jquery.com/remove/)
3018
- * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes
3019
- * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
3020
- * - [`removeData()`](http://api.jquery.com/removeData/)
3021
- * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
3022
- * - [`text()`](http://api.jquery.com/text/)
3023
- * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument
3024
- * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers
3025
- * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter
3026
- * - [`val()`](http://api.jquery.com/val/)
3027
- * - [`wrap()`](http://api.jquery.com/wrap/)
3028
- *
3029
- * ## jQuery/jqLite Extras
3030
- * Angular also provides the following additional methods and events to both jQuery and jqLite:
3031
- *
3032
- * ### Events
3033
- * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
3034
- * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
3035
- * element before it is removed.
3036
- *
3037
- * ### Methods
3038
- * - `controller(name)` - retrieves the controller of the current element or its parent. By default
3039
- * retrieves controller associated with the `ngController` directive. If `name` is provided as
3040
- * camelCase directive name, then the controller for this directive will be retrieved (e.g.
3041
- * `'ngModel'`).
3042
- * - `injector()` - retrieves the injector of the current element or its parent.
3043
- * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
3044
- * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
3045
- * be enabled.
3046
- * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
3047
- * current element. This getter should be used only on elements that contain a directive which starts a new isolate
3048
- * scope. Calling `scope()` on this element always returns the original non-isolate scope.
3049
- * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
3050
- * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
3051
- * parent element is reached.
3052
- *
3053
- * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
3054
- * https://github.com/angular/angular.js/issues/14251 for more information.
3055
- *
3056
- * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
3057
- * @returns {Object} jQuery object.
3058
- */
3059
-
3060
- JQLite.expando = 'ng339';
3061
-
3062
- var jqCache = JQLite.cache = {},
3063
- jqId = 1;
3064
-
3065
- /*
3066
- * !!! This is an undocumented "private" function !!!
3067
- */
3068
- JQLite._data = function(node) {
3069
- //jQuery always returns an object on cache miss
3070
- return this.cache[node[this.expando]] || {};
3071
- };
3072
-
3073
- function jqNextId() { return ++jqId; }
3074
-
3075
-
3076
- var DASH_LOWERCASE_REGEXP = /-([a-z])/g;
3077
- var MS_HACK_REGEXP = /^-ms-/;
3078
- var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' };
3079
- var jqLiteMinErr = minErr('jqLite');
3080
-
3081
- /**
3082
- * Converts kebab-case to camelCase.
3083
- * There is also a special case for the ms prefix starting with a lowercase letter.
3084
- * @param name Name to normalize
3085
- */
3086
- function cssKebabToCamel(name) {
3087
- return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-'));
3088
- }
3089
-
3090
- function fnCamelCaseReplace(all, letter) {
3091
- return letter.toUpperCase();
3092
- }
3093
-
3094
- /**
3095
- * Converts kebab-case to camelCase.
3096
- * @param name Name to normalize
3097
- */
3098
- function kebabToCamel(name) {
3099
- return name
3100
- .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
3101
- }
3102
-
3103
- var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
3104
- var HTML_REGEXP = /<|&#?\w+;/;
3105
- var TAG_NAME_REGEXP = /<([\w:-]+)/;
3106
- var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
3107
-
3108
- var wrapMap = {
3109
- 'option': [1, '<select multiple="multiple">', '</select>'],
3110
-
3111
- 'thead': [1, '<table>', '</table>'],
3112
- 'col': [2, '<table><colgroup>', '</colgroup></table>'],
3113
- 'tr': [2, '<table><tbody>', '</tbody></table>'],
3114
- 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
3115
- '_default': [0, '', '']
3116
- };
3117
-
3118
- wrapMap.optgroup = wrapMap.option;
3119
- wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
3120
- wrapMap.th = wrapMap.td;
3121
-
3122
-
3123
- function jqLiteIsTextNode(html) {
3124
- return !HTML_REGEXP.test(html);
3125
- }
3126
-
3127
- function jqLiteAcceptsData(node) {
3128
- // The window object can accept data but has no nodeType
3129
- // Otherwise we are only interested in elements (1) and documents (9)
3130
- var nodeType = node.nodeType;
3131
- return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
3132
- }
3133
-
3134
- function jqLiteHasData(node) {
3135
- for (var key in jqCache[node.ng339]) {
3136
- return true;
3137
- }
3138
- return false;
3139
- }
3140
-
3141
- function jqLiteBuildFragment(html, context) {
3142
- var tmp, tag, wrap,
3143
- fragment = context.createDocumentFragment(),
3144
- nodes = [], i;
3145
-
3146
- if (jqLiteIsTextNode(html)) {
3147
- // Convert non-html into a text node
3148
- nodes.push(context.createTextNode(html));
3149
- } else {
3150
- // Convert html into DOM nodes
3151
- tmp = fragment.appendChild(context.createElement('div'));
3152
- tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase();
3153
- wrap = wrapMap[tag] || wrapMap._default;
3154
- tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1></$2>') + wrap[2];
3155
-
3156
- // Descend through wrappers to the right content
3157
- i = wrap[0];
3158
- while (i--) {
3159
- tmp = tmp.lastChild;
3160
- }
3161
-
3162
- nodes = concat(nodes, tmp.childNodes);
3163
-
3164
- tmp = fragment.firstChild;
3165
- tmp.textContent = '';
3166
- }
3167
-
3168
- // Remove wrapper from fragment
3169
- fragment.textContent = '';
3170
- fragment.innerHTML = ''; // Clear inner HTML
3171
- forEach(nodes, function(node) {
3172
- fragment.appendChild(node);
3173
- });
3174
-
3175
- return fragment;
3176
- }
3177
-
3178
- function jqLiteParseHTML(html, context) {
3179
- context = context || window.document;
3180
- var parsed;
3181
-
3182
- if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
3183
- return [context.createElement(parsed[1])];
3184
- }
3185
-
3186
- if ((parsed = jqLiteBuildFragment(html, context))) {
3187
- return parsed.childNodes;
3188
- }
3189
-
3190
- return [];
3191
- }
3192
-
3193
- function jqLiteWrapNode(node, wrapper) {
3194
- var parent = node.parentNode;
3195
-
3196
- if (parent) {
3197
- parent.replaceChild(wrapper, node);
3198
- }
3199
-
3200
- wrapper.appendChild(node);
3201
- }
3202
-
3203
-
3204
- // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
3205
- var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) {
3206
- // eslint-disable-next-line no-bitwise
3207
- return !!(this.compareDocumentPosition(arg) & 16);
3208
- };
3209
-
3210
- /////////////////////////////////////////////
3211
- function JQLite(element) {
3212
- if (element instanceof JQLite) {
3213
- return element;
3214
- }
3215
-
3216
- var argIsString;
3217
-
3218
- if (isString(element)) {
3219
- element = trim(element);
3220
- argIsString = true;
3221
- }
3222
- if (!(this instanceof JQLite)) {
3223
- if (argIsString && element.charAt(0) !== '<') {
3224
- throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
3225
- }
3226
- return new JQLite(element);
3227
- }
3228
-
3229
- if (argIsString) {
3230
- jqLiteAddNodes(this, jqLiteParseHTML(element));
3231
- } else if (isFunction(element)) {
3232
- jqLiteReady(element);
3233
- } else {
3234
- jqLiteAddNodes(this, element);
3235
- }
3236
- }
3237
-
3238
- function jqLiteClone(element) {
3239
- return element.cloneNode(true);
3240
- }
3241
-
3242
- function jqLiteDealoc(element, onlyDescendants) {
3243
- if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]);
3244
-
3245
- if (element.querySelectorAll) {
3246
- jqLite.cleanData(element.querySelectorAll('*'));
3247
- }
3248
- }
3249
-
3250
- function jqLiteOff(element, type, fn, unsupported) {
3251
- if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
3252
-
3253
- var expandoStore = jqLiteExpandoStore(element);
3254
- var events = expandoStore && expandoStore.events;
3255
- var handle = expandoStore && expandoStore.handle;
3256
-
3257
- if (!handle) return; //no listeners registered
3258
-
3259
- if (!type) {
3260
- for (type in events) {
3261
- if (type !== '$destroy') {
3262
- element.removeEventListener(type, handle);
3263
- }
3264
- delete events[type];
3265
- }
3266
- } else {
3267
-
3268
- var removeHandler = function(type) {
3269
- var listenerFns = events[type];
3270
- if (isDefined(fn)) {
3271
- arrayRemove(listenerFns || [], fn);
3272
- }
3273
- if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
3274
- element.removeEventListener(type, handle);
3275
- delete events[type];
3276
- }
3277
- };
3278
-
3279
- forEach(type.split(' '), function(type) {
3280
- removeHandler(type);
3281
- if (MOUSE_EVENT_MAP[type]) {
3282
- removeHandler(MOUSE_EVENT_MAP[type]);
3283
- }
3284
- });
3285
- }
3286
- }
3287
-
3288
- function jqLiteRemoveData(element, name) {
3289
- var expandoId = element.ng339;
3290
- var expandoStore = expandoId && jqCache[expandoId];
3291
-
3292
- if (expandoStore) {
3293
- if (name) {
3294
- delete expandoStore.data[name];
3295
- return;
3296
- }
3297
-
3298
- if (expandoStore.handle) {
3299
- if (expandoStore.events.$destroy) {
3300
- expandoStore.handle({}, '$destroy');
3301
- }
3302
- jqLiteOff(element);
3303
- }
3304
- delete jqCache[expandoId];
3305
- element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
3306
- }
3307
- }
3308
-
3309
-
3310
- function jqLiteExpandoStore(element, createIfNecessary) {
3311
- var expandoId = element.ng339,
3312
- expandoStore = expandoId && jqCache[expandoId];
3313
-
3314
- if (createIfNecessary && !expandoStore) {
3315
- element.ng339 = expandoId = jqNextId();
3316
- expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
3317
- }
3318
-
3319
- return expandoStore;
3320
- }
3321
-
3322
-
3323
- function jqLiteData(element, key, value) {
3324
- if (jqLiteAcceptsData(element)) {
3325
- var prop;
3326
-
3327
- var isSimpleSetter = isDefined(value);
3328
- var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
3329
- var massGetter = !key;
3330
- var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
3331
- var data = expandoStore && expandoStore.data;
3332
-
3333
- if (isSimpleSetter) { // data('key', value)
3334
- data[kebabToCamel(key)] = value;
3335
- } else {
3336
- if (massGetter) { // data()
3337
- return data;
3338
- } else {
3339
- if (isSimpleGetter) { // data('key')
3340
- // don't force creation of expandoStore if it doesn't exist yet
3341
- return data && data[kebabToCamel(key)];
3342
- } else { // mass-setter: data({key1: val1, key2: val2})
3343
- for (prop in key) {
3344
- data[kebabToCamel(prop)] = key[prop];
3345
- }
3346
- }
3347
- }
3348
- }
3349
- }
3350
- }
3351
-
3352
- function jqLiteHasClass(element, selector) {
3353
- if (!element.getAttribute) return false;
3354
- return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
3355
- indexOf(' ' + selector + ' ') > -1);
3356
- }
3357
-
3358
- function jqLiteRemoveClass(element, cssClasses) {
3359
- if (cssClasses && element.setAttribute) {
3360
- forEach(cssClasses.split(' '), function(cssClass) {
3361
- element.setAttribute('class', trim(
3362
- (' ' + (element.getAttribute('class') || '') + ' ')
3363
- .replace(/[\n\t]/g, ' ')
3364
- .replace(' ' + trim(cssClass) + ' ', ' '))
3365
- );
3366
- });
3367
- }
3368
- }
3369
-
3370
- function jqLiteAddClass(element, cssClasses) {
3371
- if (cssClasses && element.setAttribute) {
3372
- var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3373
- .replace(/[\n\t]/g, ' ');
3374
-
3375
- forEach(cssClasses.split(' '), function(cssClass) {
3376
- cssClass = trim(cssClass);
3377
- if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3378
- existingClasses += cssClass + ' ';
3379
- }
3380
- });
3381
-
3382
- element.setAttribute('class', trim(existingClasses));
3383
- }
3384
- }
3385
-
3386
-
3387
- function jqLiteAddNodes(root, elements) {
3388
- // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3389
-
3390
- if (elements) {
3391
-
3392
- // if a Node (the most common case)
3393
- if (elements.nodeType) {
3394
- root[root.length++] = elements;
3395
- } else {
3396
- var length = elements.length;
3397
-
3398
- // if an Array or NodeList and not a Window
3399
- if (typeof length === 'number' && elements.window !== elements) {
3400
- if (length) {
3401
- for (var i = 0; i < length; i++) {
3402
- root[root.length++] = elements[i];
3403
- }
3404
- }
3405
- } else {
3406
- root[root.length++] = elements;
3407
- }
3408
- }
3409
- }
3410
- }
3411
-
3412
-
3413
- function jqLiteController(element, name) {
3414
- return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3415
- }
3416
-
3417
- function jqLiteInheritedData(element, name, value) {
3418
- // if element is the document object work with the html element instead
3419
- // this makes $(document).scope() possible
3420
- if (element.nodeType === NODE_TYPE_DOCUMENT) {
3421
- element = element.documentElement;
3422
- }
3423
- var names = isArray(name) ? name : [name];
3424
-
3425
- while (element) {
3426
- for (var i = 0, ii = names.length; i < ii; i++) {
3427
- if (isDefined(value = jqLite.data(element, names[i]))) return value;
3428
- }
3429
-
3430
- // If dealing with a document fragment node with a host element, and no parent, use the host
3431
- // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3432
- // to lookup parent controllers.
3433
- element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3434
- }
3435
- }
3436
-
3437
- function jqLiteEmpty(element) {
3438
- jqLiteDealoc(element, true);
3439
- while (element.firstChild) {
3440
- element.removeChild(element.firstChild);
3441
- }
3442
- }
3443
-
3444
- function jqLiteRemove(element, keepData) {
3445
- if (!keepData) jqLiteDealoc(element);
3446
- var parent = element.parentNode;
3447
- if (parent) parent.removeChild(element);
3448
- }
3449
-
3450
-
3451
- function jqLiteDocumentLoaded(action, win) {
3452
- win = win || window;
3453
- if (win.document.readyState === 'complete') {
3454
- // Force the action to be run async for consistent behavior
3455
- // from the action's point of view
3456
- // i.e. it will definitely not be in a $apply
3457
- win.setTimeout(action);
3458
- } else {
3459
- // No need to unbind this handler as load is only ever called once
3460
- jqLite(win).on('load', action);
3461
- }
3462
- }
3463
-
3464
- function jqLiteReady(fn) {
3465
- function trigger() {
3466
- window.document.removeEventListener('DOMContentLoaded', trigger);
3467
- window.removeEventListener('load', trigger);
3468
- fn();
3469
- }
3470
-
3471
- // check if document is already loaded
3472
- if (window.document.readyState === 'complete') {
3473
- window.setTimeout(fn);
3474
- } else {
3475
- // We can not use jqLite since we are not done loading and jQuery could be loaded later.
3476
-
3477
- // Works for modern browsers and IE9
3478
- window.document.addEventListener('DOMContentLoaded', trigger);
3479
-
3480
- // Fallback to window.onload for others
3481
- window.addEventListener('load', trigger);
3482
- }
3483
- }
3484
-
3485
- //////////////////////////////////////////
3486
- // Functions which are declared directly.
3487
- //////////////////////////////////////////
3488
- var JQLitePrototype = JQLite.prototype = {
3489
- ready: jqLiteReady,
3490
- toString: function() {
3491
- var value = [];
3492
- forEach(this, function(e) { value.push('' + e);});
3493
- return '[' + value.join(', ') + ']';
3494
- },
3495
-
3496
- eq: function(index) {
3497
- return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3498
- },
3499
-
3500
- length: 0,
3501
- push: push,
3502
- sort: [].sort,
3503
- splice: [].splice
3504
- };
3505
-
3506
- //////////////////////////////////////////
3507
- // Functions iterating getter/setters.
3508
- // these functions return self on setter and
3509
- // value on get.
3510
- //////////////////////////////////////////
3511
- var BOOLEAN_ATTR = {};
3512
- forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3513
- BOOLEAN_ATTR[lowercase(value)] = value;
3514
- });
3515
- var BOOLEAN_ELEMENTS = {};
3516
- forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3517
- BOOLEAN_ELEMENTS[value] = true;
3518
- });
3519
- var ALIASED_ATTR = {
3520
- 'ngMinlength': 'minlength',
3521
- 'ngMaxlength': 'maxlength',
3522
- 'ngMin': 'min',
3523
- 'ngMax': 'max',
3524
- 'ngPattern': 'pattern',
3525
- 'ngStep': 'step'
3526
- };
3527
-
3528
- function getBooleanAttrName(element, name) {
3529
- // check dom last since we will most likely fail on name
3530
- var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3531
-
3532
- // booleanAttr is here twice to minimize DOM access
3533
- return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3534
- }
3535
-
3536
- function getAliasedAttrName(name) {
3537
- return ALIASED_ATTR[name];
3538
- }
3539
-
3540
- forEach({
3541
- data: jqLiteData,
3542
- removeData: jqLiteRemoveData,
3543
- hasData: jqLiteHasData,
3544
- cleanData: function jqLiteCleanData(nodes) {
3545
- for (var i = 0, ii = nodes.length; i < ii; i++) {
3546
- jqLiteRemoveData(nodes[i]);
3547
- }
3548
- }
3549
- }, function(fn, name) {
3550
- JQLite[name] = fn;
3551
- });
3552
-
3553
- forEach({
3554
- data: jqLiteData,
3555
- inheritedData: jqLiteInheritedData,
3556
-
3557
- scope: function(element) {
3558
- // Can't use jqLiteData here directly so we stay compatible with jQuery!
3559
- return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3560
- },
3561
-
3562
- isolateScope: function(element) {
3563
- // Can't use jqLiteData here directly so we stay compatible with jQuery!
3564
- return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3565
- },
3566
-
3567
- controller: jqLiteController,
3568
-
3569
- injector: function(element) {
3570
- return jqLiteInheritedData(element, '$injector');
3571
- },
3572
-
3573
- removeAttr: function(element, name) {
3574
- element.removeAttribute(name);
3575
- },
3576
-
3577
- hasClass: jqLiteHasClass,
3578
-
3579
- css: function(element, name, value) {
3580
- name = cssKebabToCamel(name);
3581
-
3582
- if (isDefined(value)) {
3583
- element.style[name] = value;
3584
- } else {
3585
- return element.style[name];
3586
- }
3587
- },
3588
-
3589
- attr: function(element, name, value) {
3590
- var ret;
3591
- var nodeType = element.nodeType;
3592
- if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT ||
3593
- !element.getAttribute) {
3594
- return;
3595
- }
3596
-
3597
- var lowercasedName = lowercase(name);
3598
- var isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
3599
-
3600
- if (isDefined(value)) {
3601
- // setter
3602
-
3603
- if (value === null || (value === false && isBooleanAttr)) {
3604
- element.removeAttribute(name);
3605
- } else {
3606
- element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
3607
- }
3608
- } else {
3609
- // getter
3610
-
3611
- ret = element.getAttribute(name);
3612
-
3613
- if (isBooleanAttr && ret !== null) {
3614
- ret = lowercasedName;
3615
- }
3616
- // Normalize non-existing attributes to undefined (as jQuery).
3617
- return ret === null ? undefined : ret;
3618
- }
3619
- },
3620
-
3621
- prop: function(element, name, value) {
3622
- if (isDefined(value)) {
3623
- element[name] = value;
3624
- } else {
3625
- return element[name];
3626
- }
3627
- },
3628
-
3629
- text: (function() {
3630
- getText.$dv = '';
3631
- return getText;
3632
-
3633
- function getText(element, value) {
3634
- if (isUndefined(value)) {
3635
- var nodeType = element.nodeType;
3636
- return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3637
- }
3638
- element.textContent = value;
3639
- }
3640
- })(),
3641
-
3642
- val: function(element, value) {
3643
- if (isUndefined(value)) {
3644
- if (element.multiple && nodeName_(element) === 'select') {
3645
- var result = [];
3646
- forEach(element.options, function(option) {
3647
- if (option.selected) {
3648
- result.push(option.value || option.text);
3649
- }
3650
- });
3651
- return result;
3652
- }
3653
- return element.value;
3654
- }
3655
- element.value = value;
3656
- },
3657
-
3658
- html: function(element, value) {
3659
- if (isUndefined(value)) {
3660
- return element.innerHTML;
3661
- }
3662
- jqLiteDealoc(element, true);
3663
- element.innerHTML = value;
3664
- },
3665
-
3666
- empty: jqLiteEmpty
3667
- }, function(fn, name) {
3668
- /**
3669
- * Properties: writes return selection, reads return first value
3670
- */
3671
- JQLite.prototype[name] = function(arg1, arg2) {
3672
- var i, key;
3673
- var nodeCount = this.length;
3674
-
3675
- // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3676
- // in a way that survives minification.
3677
- // jqLiteEmpty takes no arguments but is a setter.
3678
- if (fn !== jqLiteEmpty &&
3679
- (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3680
- if (isObject(arg1)) {
3681
-
3682
- // we are a write, but the object properties are the key/values
3683
- for (i = 0; i < nodeCount; i++) {
3684
- if (fn === jqLiteData) {
3685
- // data() takes the whole object in jQuery
3686
- fn(this[i], arg1);
3687
- } else {
3688
- for (key in arg1) {
3689
- fn(this[i], key, arg1[key]);
3690
- }
3691
- }
3692
- }
3693
- // return self for chaining
3694
- return this;
3695
- } else {
3696
- // we are a read, so read the first child.
3697
- // TODO: do we still need this?
3698
- var value = fn.$dv;
3699
- // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3700
- var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3701
- for (var j = 0; j < jj; j++) {
3702
- var nodeValue = fn(this[j], arg1, arg2);
3703
- value = value ? value + nodeValue : nodeValue;
3704
- }
3705
- return value;
3706
- }
3707
- } else {
3708
- // we are a write, so apply to all children
3709
- for (i = 0; i < nodeCount; i++) {
3710
- fn(this[i], arg1, arg2);
3711
- }
3712
- // return self for chaining
3713
- return this;
3714
- }
3715
- };
3716
- });
3717
-
3718
- function createEventHandler(element, events) {
3719
- var eventHandler = function(event, type) {
3720
- // jQuery specific api
3721
- event.isDefaultPrevented = function() {
3722
- return event.defaultPrevented;
3723
- };
3724
-
3725
- var eventFns = events[type || event.type];
3726
- var eventFnsLength = eventFns ? eventFns.length : 0;
3727
-
3728
- if (!eventFnsLength) return;
3729
-
3730
- if (isUndefined(event.immediatePropagationStopped)) {
3731
- var originalStopImmediatePropagation = event.stopImmediatePropagation;
3732
- event.stopImmediatePropagation = function() {
3733
- event.immediatePropagationStopped = true;
3734
-
3735
- if (event.stopPropagation) {
3736
- event.stopPropagation();
3737
- }
3738
-
3739
- if (originalStopImmediatePropagation) {
3740
- originalStopImmediatePropagation.call(event);
3741
- }
3742
- };
3743
- }
3744
-
3745
- event.isImmediatePropagationStopped = function() {
3746
- return event.immediatePropagationStopped === true;
3747
- };
3748
-
3749
- // Some events have special handlers that wrap the real handler
3750
- var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3751
-
3752
- // Copy event handlers in case event handlers array is modified during execution.
3753
- if ((eventFnsLength > 1)) {
3754
- eventFns = shallowCopy(eventFns);
3755
- }
3756
-
3757
- for (var i = 0; i < eventFnsLength; i++) {
3758
- if (!event.isImmediatePropagationStopped()) {
3759
- handlerWrapper(element, event, eventFns[i]);
3760
- }
3761
- }
3762
- };
3763
-
3764
- // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3765
- // events on `element`
3766
- eventHandler.elem = element;
3767
- return eventHandler;
3768
- }
3769
-
3770
- function defaultHandlerWrapper(element, event, handler) {
3771
- handler.call(element, event);
3772
- }
3773
-
3774
- function specialMouseHandlerWrapper(target, event, handler) {
3775
- // Refer to jQuery's implementation of mouseenter & mouseleave
3776
- // Read about mouseenter and mouseleave:
3777
- // http://www.quirksmode.org/js/events_mouse.html#link8
3778
- var related = event.relatedTarget;
3779
- // For mousenter/leave call the handler if related is outside the target.
3780
- // NB: No relatedTarget if the mouse left/entered the browser window
3781
- if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3782
- handler.call(target, event);
3783
- }
3784
- }
3785
-
3786
- //////////////////////////////////////////
3787
- // Functions iterating traversal.
3788
- // These functions chain results into a single
3789
- // selector.
3790
- //////////////////////////////////////////
3791
- forEach({
3792
- removeData: jqLiteRemoveData,
3793
-
3794
- on: function jqLiteOn(element, type, fn, unsupported) {
3795
- if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3796
-
3797
- // Do not add event handlers to non-elements because they will not be cleaned up.
3798
- if (!jqLiteAcceptsData(element)) {
3799
- return;
3800
- }
3801
-
3802
- var expandoStore = jqLiteExpandoStore(element, true);
3803
- var events = expandoStore.events;
3804
- var handle = expandoStore.handle;
3805
-
3806
- if (!handle) {
3807
- handle = expandoStore.handle = createEventHandler(element, events);
3808
- }
3809
-
3810
- // http://jsperf.com/string-indexof-vs-split
3811
- var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3812
- var i = types.length;
3813
-
3814
- var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3815
- var eventFns = events[type];
3816
-
3817
- if (!eventFns) {
3818
- eventFns = events[type] = [];
3819
- eventFns.specialHandlerWrapper = specialHandlerWrapper;
3820
- if (type !== '$destroy' && !noEventListener) {
3821
- element.addEventListener(type, handle);
3822
- }
3823
- }
3824
-
3825
- eventFns.push(fn);
3826
- };
3827
-
3828
- while (i--) {
3829
- type = types[i];
3830
- if (MOUSE_EVENT_MAP[type]) {
3831
- addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3832
- addHandler(type, undefined, true);
3833
- } else {
3834
- addHandler(type);
3835
- }
3836
- }
3837
- },
3838
-
3839
- off: jqLiteOff,
3840
-
3841
- one: function(element, type, fn) {
3842
- element = jqLite(element);
3843
-
3844
- //add the listener twice so that when it is called
3845
- //you can remove the original function and still be
3846
- //able to call element.off(ev, fn) normally
3847
- element.on(type, function onFn() {
3848
- element.off(type, fn);
3849
- element.off(type, onFn);
3850
- });
3851
- element.on(type, fn);
3852
- },
3853
-
3854
- replaceWith: function(element, replaceNode) {
3855
- var index, parent = element.parentNode;
3856
- jqLiteDealoc(element);
3857
- forEach(new JQLite(replaceNode), function(node) {
3858
- if (index) {
3859
- parent.insertBefore(node, index.nextSibling);
3860
- } else {
3861
- parent.replaceChild(node, element);
3862
- }
3863
- index = node;
3864
- });
3865
- },
3866
-
3867
- children: function(element) {
3868
- var children = [];
3869
- forEach(element.childNodes, function(element) {
3870
- if (element.nodeType === NODE_TYPE_ELEMENT) {
3871
- children.push(element);
3872
- }
3873
- });
3874
- return children;
3875
- },
3876
-
3877
- contents: function(element) {
3878
- return element.contentDocument || element.childNodes || [];
3879
- },
3880
-
3881
- append: function(element, node) {
3882
- var nodeType = element.nodeType;
3883
- if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3884
-
3885
- node = new JQLite(node);
3886
-
3887
- for (var i = 0, ii = node.length; i < ii; i++) {
3888
- var child = node[i];
3889
- element.appendChild(child);
3890
- }
3891
- },
3892
-
3893
- prepend: function(element, node) {
3894
- if (element.nodeType === NODE_TYPE_ELEMENT) {
3895
- var index = element.firstChild;
3896
- forEach(new JQLite(node), function(child) {
3897
- element.insertBefore(child, index);
3898
- });
3899
- }
3900
- },
3901
-
3902
- wrap: function(element, wrapNode) {
3903
- jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
3904
- },
3905
-
3906
- remove: jqLiteRemove,
3907
-
3908
- detach: function(element) {
3909
- jqLiteRemove(element, true);
3910
- },
3911
-
3912
- after: function(element, newElement) {
3913
- var index = element, parent = element.parentNode;
3914
-
3915
- if (parent) {
3916
- newElement = new JQLite(newElement);
3917
-
3918
- for (var i = 0, ii = newElement.length; i < ii; i++) {
3919
- var node = newElement[i];
3920
- parent.insertBefore(node, index.nextSibling);
3921
- index = node;
3922
- }
3923
- }
3924
- },
3925
-
3926
- addClass: jqLiteAddClass,
3927
- removeClass: jqLiteRemoveClass,
3928
-
3929
- toggleClass: function(element, selector, condition) {
3930
- if (selector) {
3931
- forEach(selector.split(' '), function(className) {
3932
- var classCondition = condition;
3933
- if (isUndefined(classCondition)) {
3934
- classCondition = !jqLiteHasClass(element, className);
3935
- }
3936
- (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3937
- });
3938
- }
3939
- },
3940
-
3941
- parent: function(element) {
3942
- var parent = element.parentNode;
3943
- return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3944
- },
3945
-
3946
- next: function(element) {
3947
- return element.nextElementSibling;
3948
- },
3949
-
3950
- find: function(element, selector) {
3951
- if (element.getElementsByTagName) {
3952
- return element.getElementsByTagName(selector);
3953
- } else {
3954
- return [];
3955
- }
3956
- },
3957
-
3958
- clone: jqLiteClone,
3959
-
3960
- triggerHandler: function(element, event, extraParameters) {
3961
-
3962
- var dummyEvent, eventFnsCopy, handlerArgs;
3963
- var eventName = event.type || event;
3964
- var expandoStore = jqLiteExpandoStore(element);
3965
- var events = expandoStore && expandoStore.events;
3966
- var eventFns = events && events[eventName];
3967
-
3968
- if (eventFns) {
3969
- // Create a dummy event to pass to the handlers
3970
- dummyEvent = {
3971
- preventDefault: function() { this.defaultPrevented = true; },
3972
- isDefaultPrevented: function() { return this.defaultPrevented === true; },
3973
- stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3974
- isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3975
- stopPropagation: noop,
3976
- type: eventName,
3977
- target: element
3978
- };
3979
-
3980
- // If a custom event was provided then extend our dummy event with it
3981
- if (event.type) {
3982
- dummyEvent = extend(dummyEvent, event);
3983
- }
3984
-
3985
- // Copy event handlers in case event handlers array is modified during execution.
3986
- eventFnsCopy = shallowCopy(eventFns);
3987
- handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3988
-
3989
- forEach(eventFnsCopy, function(fn) {
3990
- if (!dummyEvent.isImmediatePropagationStopped()) {
3991
- fn.apply(element, handlerArgs);
3992
- }
3993
- });
3994
- }
3995
- }
3996
- }, function(fn, name) {
3997
- /**
3998
- * chaining functions
3999
- */
4000
- JQLite.prototype[name] = function(arg1, arg2, arg3) {
4001
- var value;
4002
-
4003
- for (var i = 0, ii = this.length; i < ii; i++) {
4004
- if (isUndefined(value)) {
4005
- value = fn(this[i], arg1, arg2, arg3);
4006
- if (isDefined(value)) {
4007
- // any function which returns a value needs to be wrapped
4008
- value = jqLite(value);
4009
- }
4010
- } else {
4011
- jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
4012
- }
4013
- }
4014
- return isDefined(value) ? value : this;
4015
- };
4016
- });
4017
-
4018
- // bind legacy bind/unbind to on/off
4019
- JQLite.prototype.bind = JQLite.prototype.on;
4020
- JQLite.prototype.unbind = JQLite.prototype.off;
4021
-
4022
-
4023
- // Provider for private $$jqLite service
4024
- /** @this */
4025
- function $$jqLiteProvider() {
4026
- this.$get = function $$jqLite() {
4027
- return extend(JQLite, {
4028
- hasClass: function(node, classes) {
4029
- if (node.attr) node = node[0];
4030
- return jqLiteHasClass(node, classes);
4031
- },
4032
- addClass: function(node, classes) {
4033
- if (node.attr) node = node[0];
4034
- return jqLiteAddClass(node, classes);
4035
- },
4036
- removeClass: function(node, classes) {
4037
- if (node.attr) node = node[0];
4038
- return jqLiteRemoveClass(node, classes);
4039
- }
4040
- });
4041
- };
4042
- }
4043
-
4044
- /**
4045
- * Computes a hash of an 'obj'.
4046
- * Hash of a:
4047
- * string is string
4048
- * number is number as string
4049
- * object is either result of calling $$hashKey function on the object or uniquely generated id,
4050
- * that is also assigned to the $$hashKey property of the object.
4051
- *
4052
- * @param obj
4053
- * @returns {string} hash string such that the same input will have the same hash string.
4054
- * The resulting string key is in 'type:hashKey' format.
4055
- */
4056
- function hashKey(obj, nextUidFn) {
4057
- var key = obj && obj.$$hashKey;
4058
-
4059
- if (key) {
4060
- if (typeof key === 'function') {
4061
- key = obj.$$hashKey();
4062
- }
4063
- return key;
4064
- }
4065
-
4066
- var objType = typeof obj;
4067
- if (objType === 'function' || (objType === 'object' && obj !== null)) {
4068
- key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
4069
- } else {
4070
- key = objType + ':' + obj;
4071
- }
4072
-
4073
- return key;
4074
- }
4075
-
4076
- // A minimal ES2015 Map implementation.
4077
- // Should be bug/feature equivalent to the native implementations of supported browsers
4078
- // (for the features required in Angular).
4079
- // See https://kangax.github.io/compat-table/es6/#test-Map
4080
- var nanKey = Object.create(null);
4081
- function NgMapShim() {
4082
- this._keys = [];
4083
- this._values = [];
4084
- this._lastKey = NaN;
4085
- this._lastIndex = -1;
4086
- }
4087
- NgMapShim.prototype = {
4088
- _idx: function(key) {
4089
- if (key === this._lastKey) {
4090
- return this._lastIndex;
4091
- }
4092
- this._lastKey = key;
4093
- this._lastIndex = this._keys.indexOf(key);
4094
- return this._lastIndex;
4095
- },
4096
- _transformKey: function(key) {
4097
- return isNumberNaN(key) ? nanKey : key;
4098
- },
4099
- get: function(key) {
4100
- key = this._transformKey(key);
4101
- var idx = this._idx(key);
4102
- if (idx !== -1) {
4103
- return this._values[idx];
4104
- }
4105
- },
4106
- set: function(key, value) {
4107
- key = this._transformKey(key);
4108
- var idx = this._idx(key);
4109
- if (idx === -1) {
4110
- idx = this._lastIndex = this._keys.length;
4111
- }
4112
- this._keys[idx] = key;
4113
- this._values[idx] = value;
4114
-
4115
- // Support: IE11
4116
- // Do not `return this` to simulate the partial IE11 implementation
4117
- },
4118
- delete: function(key) {
4119
- key = this._transformKey(key);
4120
- var idx = this._idx(key);
4121
- if (idx === -1) {
4122
- return false;
4123
- }
4124
- this._keys.splice(idx, 1);
4125
- this._values.splice(idx, 1);
4126
- this._lastKey = NaN;
4127
- this._lastIndex = -1;
4128
- return true;
4129
- }
4130
- };
4131
-
4132
- // For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
4133
- // are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
4134
- // implementations get more stable, we can reconsider switching to `window.Map` (when available).
4135
- var NgMap = NgMapShim;
4136
-
4137
- var $$MapProvider = [/** @this */function() {
4138
- this.$get = [function() {
4139
- return NgMap;
4140
- }];
4141
- }];
4142
-
4143
- /**
4144
- * @ngdoc function
4145
- * @module ng
4146
- * @name angular.injector
4147
- * @kind function
4148
- *
4149
- * @description
4150
- * Creates an injector object that can be used for retrieving services as well as for
4151
- * dependency injection (see {@link guide/di dependency injection}).
4152
- *
4153
- * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
4154
- * {@link angular.module}. The `ng` module must be explicitly added.
4155
- * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
4156
- * disallows argument name annotation inference.
4157
- * @returns {injector} Injector object. See {@link auto.$injector $injector}.
4158
- *
4159
- * @example
4160
- * Typical usage
4161
- * ```js
4162
- * // create an injector
4163
- * var $injector = angular.injector(['ng']);
4164
- *
4165
- * // use the injector to kick off your application
4166
- * // use the type inference to auto inject arguments, or use implicit injection
4167
- * $injector.invoke(function($rootScope, $compile, $document) {
4168
- * $compile($document)($rootScope);
4169
- * $rootScope.$digest();
4170
- * });
4171
- * ```
4172
- *
4173
- * Sometimes you want to get access to the injector of a currently running Angular app
4174
- * from outside Angular. Perhaps, you want to inject and compile some markup after the
4175
- * application has been bootstrapped. You can do this using the extra `injector()` added
4176
- * to JQuery/jqLite elements. See {@link angular.element}.
4177
- *
4178
- * *This is fairly rare but could be the case if a third party library is injecting the
4179
- * markup.*
4180
- *
4181
- * In the following example a new block of HTML containing a `ng-controller`
4182
- * directive is added to the end of the document body by JQuery. We then compile and link
4183
- * it into the current AngularJS scope.
4184
- *
4185
- * ```js
4186
- * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
4187
- * $(document.body).append($div);
4188
- *
4189
- * angular.element(document).injector().invoke(function($compile) {
4190
- * var scope = angular.element($div).scope();
4191
- * $compile($div)(scope);
4192
- * });
4193
- * ```
4194
- */
4195
-
4196
-
4197
- /**
4198
- * @ngdoc module
4199
- * @name auto
4200
- * @installation
4201
- * @description
4202
- *
4203
- * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
4204
- */
4205
-
4206
- var ARROW_ARG = /^([^(]+?)=>/;
4207
- var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m;
4208
- var FN_ARG_SPLIT = /,/;
4209
- var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
4210
- var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
4211
- var $injectorMinErr = minErr('$injector');
4212
-
4213
- function stringifyFn(fn) {
4214
- return Function.prototype.toString.call(fn);
4215
- }
4216
-
4217
- function extractArgs(fn) {
4218
- var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
4219
- args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
4220
- return args;
4221
- }
4222
-
4223
- function anonFn(fn) {
4224
- // For anonymous functions, showing at the very least the function signature can help in
4225
- // debugging.
4226
- var args = extractArgs(fn);
4227
- if (args) {
4228
- return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
4229
- }
4230
- return 'fn';
4231
- }
4232
-
4233
- function annotate(fn, strictDi, name) {
4234
- var $inject,
4235
- argDecl,
4236
- last;
4237
-
4238
- if (typeof fn === 'function') {
4239
- if (!($inject = fn.$inject)) {
4240
- $inject = [];
4241
- if (fn.length) {
4242
- if (strictDi) {
4243
- if (!isString(name) || !name) {
4244
- name = fn.name || anonFn(fn);
4245
- }
4246
- throw $injectorMinErr('strictdi',
4247
- '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
4248
- }
4249
- argDecl = extractArgs(fn);
4250
- forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
4251
- arg.replace(FN_ARG, function(all, underscore, name) {
4252
- $inject.push(name);
4253
- });
4254
- });
4255
- }
4256
- fn.$inject = $inject;
4257
- }
4258
- } else if (isArray(fn)) {
4259
- last = fn.length - 1;
4260
- assertArgFn(fn[last], 'fn');
4261
- $inject = fn.slice(0, last);
4262
- } else {
4263
- assertArgFn(fn, 'fn', true);
4264
- }
4265
- return $inject;
4266
- }
4267
-
4268
- ///////////////////////////////////////
4269
-
4270
- /**
4271
- * @ngdoc service
4272
- * @name $injector
4273
- *
4274
- * @description
4275
- *
4276
- * `$injector` is used to retrieve object instances as defined by
4277
- * {@link auto.$provide provider}, instantiate types, invoke methods,
4278
- * and load modules.
4279
- *
4280
- * The following always holds true:
4281
- *
4282
- * ```js
4283
- * var $injector = angular.injector();
4284
- * expect($injector.get('$injector')).toBe($injector);
4285
- * expect($injector.invoke(function($injector) {
4286
- * return $injector;
4287
- * })).toBe($injector);
4288
- * ```
4289
- *
4290
- * # Injection Function Annotation
4291
- *
4292
- * JavaScript does not have annotations, and annotations are needed for dependency injection. The
4293
- * following are all valid ways of annotating function with injection arguments and are equivalent.
4294
- *
4295
- * ```js
4296
- * // inferred (only works if code not minified/obfuscated)
4297
- * $injector.invoke(function(serviceA){});
4298
- *
4299
- * // annotated
4300
- * function explicit(serviceA) {};
4301
- * explicit.$inject = ['serviceA'];
4302
- * $injector.invoke(explicit);
4303
- *
4304
- * // inline
4305
- * $injector.invoke(['serviceA', function(serviceA){}]);
4306
- * ```
4307
- *
4308
- * ## Inference
4309
- *
4310
- * In JavaScript calling `toString()` on a function returns the function definition. The definition
4311
- * can then be parsed and the function arguments can be extracted. This method of discovering
4312
- * annotations is disallowed when the injector is in strict mode.
4313
- * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
4314
- * argument names.
4315
- *
4316
- * ## `$inject` Annotation
4317
- * By adding an `$inject` property onto a function the injection parameters can be specified.
4318
- *
4319
- * ## Inline
4320
- * As an array of injection names, where the last item in the array is the function to call.
4321
- */
4322
-
4323
- /**
4324
- * @ngdoc property
4325
- * @name $injector#modules
4326
- * @type {Object}
4327
- * @description
4328
- * A hash containing all the modules that have been loaded into the
4329
- * $injector.
4330
- *
4331
- * You can use this property to find out information about a module via the
4332
- * {@link angular.Module#info `myModule.info(...)`} method.
4333
- *
4334
- * For example:
4335
- *
4336
- * ```
4337
- * var info = $injector.modules['ngAnimate'].info();
4338
- * ```
4339
- *
4340
- * **Do not use this property to attempt to modify the modules after the application
4341
- * has been bootstrapped.**
4342
- */
4343
-
4344
-
4345
- /**
4346
- * @ngdoc method
4347
- * @name $injector#get
4348
- *
4349
- * @description
4350
- * Return an instance of the service.
4351
- *
4352
- * @param {string} name The name of the instance to retrieve.
4353
- * @param {string=} caller An optional string to provide the origin of the function call for error messages.
4354
- * @return {*} The instance.
4355
- */
4356
-
4357
- /**
4358
- * @ngdoc method
4359
- * @name $injector#invoke
4360
- *
4361
- * @description
4362
- * Invoke the method and supply the method arguments from the `$injector`.
4363
- *
4364
- * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
4365
- * injected according to the {@link guide/di $inject Annotation} rules.
4366
- * @param {Object=} self The `this` for the invoked method.
4367
- * @param {Object=} locals Optional object. If preset then any argument names are read from this
4368
- * object first, before the `$injector` is consulted.
4369
- * @returns {*} the value returned by the invoked `fn` function.
4370
- */
4371
-
4372
- /**
4373
- * @ngdoc method
4374
- * @name $injector#has
4375
- *
4376
- * @description
4377
- * Allows the user to query if the particular service exists.
4378
- *
4379
- * @param {string} name Name of the service to query.
4380
- * @returns {boolean} `true` if injector has given service.
4381
- */
4382
-
4383
- /**
4384
- * @ngdoc method
4385
- * @name $injector#instantiate
4386
- * @description
4387
- * Create a new instance of JS type. The method takes a constructor function, invokes the new
4388
- * operator, and supplies all of the arguments to the constructor function as specified by the
4389
- * constructor annotation.
4390
- *
4391
- * @param {Function} Type Annotated constructor function.
4392
- * @param {Object=} locals Optional object. If preset then any argument names are read from this
4393
- * object first, before the `$injector` is consulted.
4394
- * @returns {Object} new instance of `Type`.
4395
- */
4396
-
4397
- /**
4398
- * @ngdoc method
4399
- * @name $injector#annotate
4400
- *
4401
- * @description
4402
- * Returns an array of service names which the function is requesting for injection. This API is
4403
- * used by the injector to determine which services need to be injected into the function when the
4404
- * function is invoked. There are three ways in which the function can be annotated with the needed
4405
- * dependencies.
4406
- *
4407
- * # Argument names
4408
- *
4409
- * The simplest form is to extract the dependencies from the arguments of the function. This is done
4410
- * by converting the function into a string using `toString()` method and extracting the argument
4411
- * names.
4412
- * ```js
4413
- * // Given
4414
- * function MyController($scope, $route) {
4415
- * // ...
4416
- * }
4417
- *
4418
- * // Then
4419
- * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4420
- * ```
4421
- *
4422
- * You can disallow this method by using strict injection mode.
4423
- *
4424
- * This method does not work with code minification / obfuscation. For this reason the following
4425
- * annotation strategies are supported.
4426
- *
4427
- * # The `$inject` property
4428
- *
4429
- * If a function has an `$inject` property and its value is an array of strings, then the strings
4430
- * represent names of services to be injected into the function.
4431
- * ```js
4432
- * // Given
4433
- * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4434
- * // ...
4435
- * }
4436
- * // Define function dependencies
4437
- * MyController['$inject'] = ['$scope', '$route'];
4438
- *
4439
- * // Then
4440
- * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4441
- * ```
4442
- *
4443
- * # The array notation
4444
- *
4445
- * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4446
- * is very inconvenient. In these situations using the array notation to specify the dependencies in
4447
- * a way that survives minification is a better choice:
4448
- *
4449
- * ```js
4450
- * // We wish to write this (not minification / obfuscation safe)
4451
- * injector.invoke(function($compile, $rootScope) {
4452
- * // ...
4453
- * });
4454
- *
4455
- * // We are forced to write break inlining
4456
- * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4457
- * // ...
4458
- * };
4459
- * tmpFn.$inject = ['$compile', '$rootScope'];
4460
- * injector.invoke(tmpFn);
4461
- *
4462
- * // To better support inline function the inline annotation is supported
4463
- * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4464
- * // ...
4465
- * }]);
4466
- *
4467
- * // Therefore
4468
- * expect(injector.annotate(
4469
- * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4470
- * ).toEqual(['$compile', '$rootScope']);
4471
- * ```
4472
- *
4473
- * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4474
- * be retrieved as described above.
4475
- *
4476
- * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4477
- *
4478
- * @returns {Array.<string>} The names of the services which the function requires.
4479
- */
4480
-
4481
-
4482
-
4483
- /**
4484
- * @ngdoc service
4485
- * @name $provide
4486
- *
4487
- * @description
4488
- *
4489
- * The {@link auto.$provide $provide} service has a number of methods for registering components
4490
- * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4491
- * {@link angular.Module}.
4492
- *
4493
- * An Angular **service** is a singleton object created by a **service factory**. These **service
4494
- * factories** are functions which, in turn, are created by a **service provider**.
4495
- * The **service providers** are constructor functions. When instantiated they must contain a
4496
- * property called `$get`, which holds the **service factory** function.
4497
- *
4498
- * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4499
- * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4500
- * function to get the instance of the **service**.
4501
- *
4502
- * Often services have no configuration options and there is no need to add methods to the service
4503
- * provider. The provider will be no more than a constructor function with a `$get` property. For
4504
- * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4505
- * services without specifying a provider.
4506
- *
4507
- * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the
4508
- * {@link auto.$injector $injector}
4509
- * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by
4510
- * providers and services.
4511
- * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by
4512
- * services, not providers.
4513
- * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function**
4514
- * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4515
- * given factory function.
4516
- * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function**
4517
- * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4518
- * a new object using the given constructor function.
4519
- * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that
4520
- * will be able to modify or replace the implementation of another service.
4521
- *
4522
- * See the individual methods for more information and examples.
4523
- */
4524
-
4525
- /**
4526
- * @ngdoc method
4527
- * @name $provide#provider
4528
- * @description
4529
- *
4530
- * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4531
- * are constructor functions, whose instances are responsible for "providing" a factory for a
4532
- * service.
4533
- *
4534
- * Service provider names start with the name of the service they provide followed by `Provider`.
4535
- * For example, the {@link ng.$log $log} service has a provider called
4536
- * {@link ng.$logProvider $logProvider}.
4537
- *
4538
- * Service provider objects can have additional methods which allow configuration of the provider
4539
- * and its service. Importantly, you can configure what kind of service is created by the `$get`
4540
- * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4541
- * method {@link ng.$logProvider#debugEnabled debugEnabled}
4542
- * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4543
- * console or not.
4544
- *
4545
- * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4546
- 'Provider'` key.
4547
- * @param {(Object|function())} provider If the provider is:
4548
- *
4549
- * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4550
- * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4551
- * - `Constructor`: a new instance of the provider will be created using
4552
- * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4553
- *
4554
- * @returns {Object} registered provider instance
4555
-
4556
- * @example
4557
- *
4558
- * The following example shows how to create a simple event tracking service and register it using
4559
- * {@link auto.$provide#provider $provide.provider()}.
4560
- *
4561
- * ```js
4562
- * // Define the eventTracker provider
4563
- * function EventTrackerProvider() {
4564
- * var trackingUrl = '/track';
4565
- *
4566
- * // A provider method for configuring where the tracked events should been saved
4567
- * this.setTrackingUrl = function(url) {
4568
- * trackingUrl = url;
4569
- * };
4570
- *
4571
- * // The service factory function
4572
- * this.$get = ['$http', function($http) {
4573
- * var trackedEvents = {};
4574
- * return {
4575
- * // Call this to track an event
4576
- * event: function(event) {
4577
- * var count = trackedEvents[event] || 0;
4578
- * count += 1;
4579
- * trackedEvents[event] = count;
4580
- * return count;
4581
- * },
4582
- * // Call this to save the tracked events to the trackingUrl
4583
- * save: function() {
4584
- * $http.post(trackingUrl, trackedEvents);
4585
- * }
4586
- * };
4587
- * }];
4588
- * }
4589
- *
4590
- * describe('eventTracker', function() {
4591
- * var postSpy;
4592
- *
4593
- * beforeEach(module(function($provide) {
4594
- * // Register the eventTracker provider
4595
- * $provide.provider('eventTracker', EventTrackerProvider);
4596
- * }));
4597
- *
4598
- * beforeEach(module(function(eventTrackerProvider) {
4599
- * // Configure eventTracker provider
4600
- * eventTrackerProvider.setTrackingUrl('/custom-track');
4601
- * }));
4602
- *
4603
- * it('tracks events', inject(function(eventTracker) {
4604
- * expect(eventTracker.event('login')).toEqual(1);
4605
- * expect(eventTracker.event('login')).toEqual(2);
4606
- * }));
4607
- *
4608
- * it('saves to the tracking url', inject(function(eventTracker, $http) {
4609
- * postSpy = spyOn($http, 'post');
4610
- * eventTracker.event('login');
4611
- * eventTracker.save();
4612
- * expect(postSpy).toHaveBeenCalled();
4613
- * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4614
- * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4615
- * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4616
- * }));
4617
- * });
4618
- * ```
4619
- */
4620
-
4621
- /**
4622
- * @ngdoc method
4623
- * @name $provide#factory
4624
- * @description
4625
- *
4626
- * Register a **service factory**, which will be called to return the service instance.
4627
- * This is short for registering a service where its provider consists of only a `$get` property,
4628
- * which is the given service factory function.
4629
- * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4630
- * configure your service in a provider.
4631
- *
4632
- * @param {string} name The name of the instance.
4633
- * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4634
- * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4635
- * @returns {Object} registered provider instance
4636
- *
4637
- * @example
4638
- * Here is an example of registering a service
4639
- * ```js
4640
- * $provide.factory('ping', ['$http', function($http) {
4641
- * return function ping() {
4642
- * return $http.send('/ping');
4643
- * };
4644
- * }]);
4645
- * ```
4646
- * You would then inject and use this service like this:
4647
- * ```js
4648
- * someModule.controller('Ctrl', ['ping', function(ping) {
4649
- * ping();
4650
- * }]);
4651
- * ```
4652
- */
4653
-
4654
-
4655
- /**
4656
- * @ngdoc method
4657
- * @name $provide#service
4658
- * @description
4659
- *
4660
- * Register a **service constructor**, which will be invoked with `new` to create the service
4661
- * instance.
4662
- * This is short for registering a service where its provider's `$get` property is a factory
4663
- * function that returns an instance instantiated by the injector from the service constructor
4664
- * function.
4665
- *
4666
- * Internally it looks a bit like this:
4667
- *
4668
- * ```
4669
- * {
4670
- * $get: function() {
4671
- * return $injector.instantiate(constructor);
4672
- * }
4673
- * }
4674
- * ```
4675
- *
4676
- *
4677
- * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4678
- * as a type/class.
4679
- *
4680
- * @param {string} name The name of the instance.
4681
- * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4682
- * that will be instantiated.
4683
- * @returns {Object} registered provider instance
4684
- *
4685
- * @example
4686
- * Here is an example of registering a service using
4687
- * {@link auto.$provide#service $provide.service(class)}.
4688
- * ```js
4689
- * var Ping = function($http) {
4690
- * this.$http = $http;
4691
- * };
4692
- *
4693
- * Ping.$inject = ['$http'];
4694
- *
4695
- * Ping.prototype.send = function() {
4696
- * return this.$http.get('/ping');
4697
- * };
4698
- * $provide.service('ping', Ping);
4699
- * ```
4700
- * You would then inject and use this service like this:
4701
- * ```js
4702
- * someModule.controller('Ctrl', ['ping', function(ping) {
4703
- * ping.send();
4704
- * }]);
4705
- * ```
4706
- */
4707
-
4708
-
4709
- /**
4710
- * @ngdoc method
4711
- * @name $provide#value
4712
- * @description
4713
- *
4714
- * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4715
- * number, an array, an object or a function. This is short for registering a service where its
4716
- * provider's `$get` property is a factory function that takes no arguments and returns the **value
4717
- * service**. That also means it is not possible to inject other services into a value service.
4718
- *
4719
- * Value services are similar to constant services, except that they cannot be injected into a
4720
- * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4721
- * an Angular {@link auto.$provide#decorator decorator}.
4722
- *
4723
- * @param {string} name The name of the instance.
4724
- * @param {*} value The value.
4725
- * @returns {Object} registered provider instance
4726
- *
4727
- * @example
4728
- * Here are some examples of creating value services.
4729
- * ```js
4730
- * $provide.value('ADMIN_USER', 'admin');
4731
- *
4732
- * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4733
- *
4734
- * $provide.value('halfOf', function(value) {
4735
- * return value / 2;
4736
- * });
4737
- * ```
4738
- */
4739
-
4740
-
4741
- /**
4742
- * @ngdoc method
4743
- * @name $provide#constant
4744
- * @description
4745
- *
4746
- * Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
4747
- * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
4748
- * possible to inject other services into a constant.
4749
- *
4750
- * But unlike {@link auto.$provide#value value}, a constant can be
4751
- * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4752
- * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4753
- *
4754
- * @param {string} name The name of the constant.
4755
- * @param {*} value The constant value.
4756
- * @returns {Object} registered instance
4757
- *
4758
- * @example
4759
- * Here a some examples of creating constants:
4760
- * ```js
4761
- * $provide.constant('SHARD_HEIGHT', 306);
4762
- *
4763
- * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4764
- *
4765
- * $provide.constant('double', function(value) {
4766
- * return value * 2;
4767
- * });
4768
- * ```
4769
- */
4770
-
4771
-
4772
- /**
4773
- * @ngdoc method
4774
- * @name $provide#decorator
4775
- * @description
4776
- *
4777
- * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function
4778
- * intercepts the creation of a service, allowing it to override or modify the behavior of the
4779
- * service. The return value of the decorator function may be the original service, or a new service
4780
- * that replaces (or wraps and delegates to) the original service.
4781
- *
4782
- * You can find out more about using decorators in the {@link guide/decorators} guide.
4783
- *
4784
- * @param {string} name The name of the service to decorate.
4785
- * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4786
- * provided and should return the decorated service instance. The function is called using
4787
- * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4788
- * Local injection arguments:
4789
- *
4790
- * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured,
4791
- * decorated or delegated to.
4792
- *
4793
- * @example
4794
- * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4795
- * calls to {@link ng.$log#error $log.warn()}.
4796
- * ```js
4797
- * $provide.decorator('$log', ['$delegate', function($delegate) {
4798
- * $delegate.warn = $delegate.error;
4799
- * return $delegate;
4800
- * }]);
4801
- * ```
4802
- */
4803
-
4804
-
4805
- function createInjector(modulesToLoad, strictDi) {
4806
- strictDi = (strictDi === true);
4807
- var INSTANTIATING = {},
4808
- providerSuffix = 'Provider',
4809
- path = [],
4810
- loadedModules = new NgMap(),
4811
- providerCache = {
4812
- $provide: {
4813
- provider: supportObject(provider),
4814
- factory: supportObject(factory),
4815
- service: supportObject(service),
4816
- value: supportObject(value),
4817
- constant: supportObject(constant),
4818
- decorator: decorator
4819
- }
4820
- },
4821
- providerInjector = (providerCache.$injector =
4822
- createInternalInjector(providerCache, function(serviceName, caller) {
4823
- if (angular.isString(caller)) {
4824
- path.push(caller);
4825
- }
4826
- throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- '));
4827
- })),
4828
- instanceCache = {},
4829
- protoInstanceInjector =
4830
- createInternalInjector(instanceCache, function(serviceName, caller) {
4831
- var provider = providerInjector.get(serviceName + providerSuffix, caller);
4832
- return instanceInjector.invoke(
4833
- provider.$get, provider, undefined, serviceName);
4834
- }),
4835
- instanceInjector = protoInstanceInjector;
4836
-
4837
- providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
4838
- instanceInjector.modules = providerInjector.modules = createMap();
4839
- var runBlocks = loadModules(modulesToLoad);
4840
- instanceInjector = protoInstanceInjector.get('$injector');
4841
- instanceInjector.strictDi = strictDi;
4842
- forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
4843
-
4844
- return instanceInjector;
4845
-
4846
- ////////////////////////////////////
4847
- // $provider
4848
- ////////////////////////////////////
4849
-
4850
- function supportObject(delegate) {
4851
- return function(key, value) {
4852
- if (isObject(key)) {
4853
- forEach(key, reverseParams(delegate));
4854
- } else {
4855
- return delegate(key, value);
4856
- }
4857
- };
4858
- }
4859
-
4860
- function provider(name, provider_) {
4861
- assertNotHasOwnProperty(name, 'service');
4862
- if (isFunction(provider_) || isArray(provider_)) {
4863
- provider_ = providerInjector.instantiate(provider_);
4864
- }
4865
- if (!provider_.$get) {
4866
- throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name);
4867
- }
4868
- return (providerCache[name + providerSuffix] = provider_);
4869
- }
4870
-
4871
- function enforceReturnValue(name, factory) {
4872
- return /** @this */ function enforcedReturnValue() {
4873
- var result = instanceInjector.invoke(factory, this);
4874
- if (isUndefined(result)) {
4875
- throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name);
4876
- }
4877
- return result;
4878
- };
4879
- }
4880
-
4881
- function factory(name, factoryFn, enforce) {
4882
- return provider(name, {
4883
- $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4884
- });
4885
- }
4886
-
4887
- function service(name, constructor) {
4888
- return factory(name, ['$injector', function($injector) {
4889
- return $injector.instantiate(constructor);
4890
- }]);
4891
- }
4892
-
4893
- function value(name, val) { return factory(name, valueFn(val), false); }
4894
-
4895
- function constant(name, value) {
4896
- assertNotHasOwnProperty(name, 'constant');
4897
- providerCache[name] = value;
4898
- instanceCache[name] = value;
4899
- }
4900
-
4901
- function decorator(serviceName, decorFn) {
4902
- var origProvider = providerInjector.get(serviceName + providerSuffix),
4903
- orig$get = origProvider.$get;
4904
-
4905
- origProvider.$get = function() {
4906
- var origInstance = instanceInjector.invoke(orig$get, origProvider);
4907
- return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4908
- };
4909
- }
4910
-
4911
- ////////////////////////////////////
4912
- // Module Loading
4913
- ////////////////////////////////////
4914
- function loadModules(modulesToLoad) {
4915
- assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4916
- var runBlocks = [], moduleFn;
4917
- forEach(modulesToLoad, function(module) {
4918
- if (loadedModules.get(module)) return;
4919
- loadedModules.set(module, true);
4920
-
4921
- function runInvokeQueue(queue) {
4922
- var i, ii;
4923
- for (i = 0, ii = queue.length; i < ii; i++) {
4924
- var invokeArgs = queue[i],
4925
- provider = providerInjector.get(invokeArgs[0]);
4926
-
4927
- provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4928
- }
4929
- }
4930
-
4931
- try {
4932
- if (isString(module)) {
4933
- moduleFn = angularModule(module);
4934
- instanceInjector.modules[module] = moduleFn;
4935
- runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4936
- runInvokeQueue(moduleFn._invokeQueue);
4937
- runInvokeQueue(moduleFn._configBlocks);
4938
- } else if (isFunction(module)) {
4939
- runBlocks.push(providerInjector.invoke(module));
4940
- } else if (isArray(module)) {
4941
- runBlocks.push(providerInjector.invoke(module));
4942
- } else {
4943
- assertArgFn(module, 'module');
4944
- }
4945
- } catch (e) {
4946
- if (isArray(module)) {
4947
- module = module[module.length - 1];
4948
- }
4949
- if (e.message && e.stack && e.stack.indexOf(e.message) === -1) {
4950
- // Safari & FF's stack traces don't contain error.message content
4951
- // unlike those of Chrome and IE
4952
- // So if stack doesn't contain message, we create a new string that contains both.
4953
- // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4954
- // eslint-disable-next-line no-ex-assign
4955
- e = e.message + '\n' + e.stack;
4956
- }
4957
- throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}',
4958
- module, e.stack || e.message || e);
4959
- }
4960
- });
4961
- return runBlocks;
4962
- }
4963
-
4964
- ////////////////////////////////////
4965
- // internal Injector
4966
- ////////////////////////////////////
4967
-
4968
- function createInternalInjector(cache, factory) {
4969
-
4970
- function getService(serviceName, caller) {
4971
- if (cache.hasOwnProperty(serviceName)) {
4972
- if (cache[serviceName] === INSTANTIATING) {
4973
- throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4974
- serviceName + ' <- ' + path.join(' <- '));
4975
- }
4976
- return cache[serviceName];
4977
- } else {
4978
- try {
4979
- path.unshift(serviceName);
4980
- cache[serviceName] = INSTANTIATING;
4981
- cache[serviceName] = factory(serviceName, caller);
4982
- return cache[serviceName];
4983
- } catch (err) {
4984
- if (cache[serviceName] === INSTANTIATING) {
4985
- delete cache[serviceName];
4986
- }
4987
- throw err;
4988
- } finally {
4989
- path.shift();
4990
- }
4991
- }
4992
- }
4993
-
4994
-
4995
- function injectionArgs(fn, locals, serviceName) {
4996
- var args = [],
4997
- $inject = createInjector.$$annotate(fn, strictDi, serviceName);
4998
-
4999
- for (var i = 0, length = $inject.length; i < length; i++) {
5000
- var key = $inject[i];
5001
- if (typeof key !== 'string') {
5002
- throw $injectorMinErr('itkn',
5003
- 'Incorrect injection token! Expected service name as string, got {0}', key);
5004
- }
5005
- args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
5006
- getService(key, serviceName));
5007
- }
5008
- return args;
5009
- }
5010
-
5011
- function isClass(func) {
5012
- // Support: IE 9-11 only
5013
- // IE 9-11 do not support classes and IE9 leaks with the code below.
5014
- if (msie || typeof func !== 'function') {
5015
- return false;
5016
- }
5017
- var result = func.$$ngIsClass;
5018
- if (!isBoolean(result)) {
5019
- // Support: Edge 12-13 only
5020
- // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/
5021
- result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func));
5022
- }
5023
- return result;
5024
- }
5025
-
5026
- function invoke(fn, self, locals, serviceName) {
5027
- if (typeof locals === 'string') {
5028
- serviceName = locals;
5029
- locals = null;
5030
- }
5031
-
5032
- var args = injectionArgs(fn, locals, serviceName);
5033
- if (isArray(fn)) {
5034
- fn = fn[fn.length - 1];
5035
- }
5036
-
5037
- if (!isClass(fn)) {
5038
- // http://jsperf.com/angularjs-invoke-apply-vs-switch
5039
- // #5388
5040
- return fn.apply(self, args);
5041
- } else {
5042
- args.unshift(null);
5043
- return new (Function.prototype.bind.apply(fn, args))();
5044
- }
5045
- }
5046
-
5047
-
5048
- function instantiate(Type, locals, serviceName) {
5049
- // Check if Type is annotated and use just the given function at n-1 as parameter
5050
- // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
5051
- var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
5052
- var args = injectionArgs(Type, locals, serviceName);
5053
- // Empty object at position 0 is ignored for invocation with `new`, but required.
5054
- args.unshift(null);
5055
- return new (Function.prototype.bind.apply(ctor, args))();
5056
- }
5057
-
5058
-
5059
- return {
5060
- invoke: invoke,
5061
- instantiate: instantiate,
5062
- get: getService,
5063
- annotate: createInjector.$$annotate,
5064
- has: function(name) {
5065
- return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
5066
- }
5067
- };
5068
- }
5069
- }
5070
-
5071
- createInjector.$$annotate = annotate;
5072
-
5073
- /**
5074
- * @ngdoc provider
5075
- * @name $anchorScrollProvider
5076
- * @this
5077
- *
5078
- * @description
5079
- * Use `$anchorScrollProvider` to disable automatic scrolling whenever
5080
- * {@link ng.$location#hash $location.hash()} changes.
5081
- */
5082
- function $AnchorScrollProvider() {
5083
-
5084
- var autoScrollingEnabled = true;
5085
-
5086
- /**
5087
- * @ngdoc method
5088
- * @name $anchorScrollProvider#disableAutoScrolling
5089
- *
5090
- * @description
5091
- * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
5092
- * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
5093
- * Use this method to disable automatic scrolling.
5094
- *
5095
- * If automatic scrolling is disabled, one must explicitly call
5096
- * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
5097
- * current hash.
5098
- */
5099
- this.disableAutoScrolling = function() {
5100
- autoScrollingEnabled = false;
5101
- };
5102
-
5103
- /**
5104
- * @ngdoc service
5105
- * @name $anchorScroll
5106
- * @kind function
5107
- * @requires $window
5108
- * @requires $location
5109
- * @requires $rootScope
5110
- *
5111
- * @description
5112
- * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
5113
- * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
5114
- * in the
5115
- * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
5116
- *
5117
- * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
5118
- * match any anchor whenever it changes. This can be disabled by calling
5119
- * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
5120
- *
5121
- * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
5122
- * vertical scroll-offset (either fixed or dynamic).
5123
- *
5124
- * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
5125
- * {@link ng.$location#hash $location.hash()} will be used.
5126
- *
5127
- * @property {(number|function|jqLite)} yOffset
5128
- * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
5129
- * positioned elements at the top of the page, such as navbars, headers etc.
5130
- *
5131
- * `yOffset` can be specified in various ways:
5132
- * - **number**: A fixed number of pixels to be used as offset.<br /><br />
5133
- * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
5134
- * a number representing the offset (in pixels).<br /><br />
5135
- * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
5136
- * the top of the page to the element's bottom will be used as offset.<br />
5137
- * **Note**: The element will be taken into account only as long as its `position` is set to
5138
- * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
5139
- * their height and/or positioning according to the viewport's size.
5140
- *
5141
- * <br />
5142
- * <div class="alert alert-warning">
5143
- * In order for `yOffset` to work properly, scrolling should take place on the document's root and
5144
- * not some child element.
5145
- * </div>
5146
- *
5147
- * @example
5148
- <example module="anchorScrollExample" name="anchor-scroll">
5149
- <file name="index.html">
5150
- <div id="scrollArea" ng-controller="ScrollController">
5151
- <a ng-click="gotoBottom()">Go to bottom</a>
5152
- <a id="bottom"></a> You're at the bottom!
5153
- </div>
5154
- </file>
5155
- <file name="script.js">
5156
- angular.module('anchorScrollExample', [])
5157
- .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
5158
- function($scope, $location, $anchorScroll) {
5159
- $scope.gotoBottom = function() {
5160
- // set the location.hash to the id of
5161
- // the element you wish to scroll to.
5162
- $location.hash('bottom');
5163
-
5164
- // call $anchorScroll()
5165
- $anchorScroll();
5166
- };
5167
- }]);
5168
- </file>
5169
- <file name="style.css">
5170
- #scrollArea {
5171
- height: 280px;
5172
- overflow: auto;
5173
- }
5174
-
5175
- #bottom {
5176
- display: block;
5177
- margin-top: 2000px;
5178
- }
5179
- </file>
5180
- </example>
5181
- *
5182
- * <hr />
5183
- * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
5184
- * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
5185
- *
5186
- * @example
5187
- <example module="anchorScrollOffsetExample" name="anchor-scroll-offset">
5188
- <file name="index.html">
5189
- <div class="fixed-header" ng-controller="headerCtrl">
5190
- <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
5191
- Go to anchor {{x}}
5192
- </a>
5193
- </div>
5194
- <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
5195
- Anchor {{x}} of 5
5196
- </div>
5197
- </file>
5198
- <file name="script.js">
5199
- angular.module('anchorScrollOffsetExample', [])
5200
- .run(['$anchorScroll', function($anchorScroll) {
5201
- $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
5202
- }])
5203
- .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
5204
- function($anchorScroll, $location, $scope) {
5205
- $scope.gotoAnchor = function(x) {
5206
- var newHash = 'anchor' + x;
5207
- if ($location.hash() !== newHash) {
5208
- // set the $location.hash to `newHash` and
5209
- // $anchorScroll will automatically scroll to it
5210
- $location.hash('anchor' + x);
5211
- } else {
5212
- // call $anchorScroll() explicitly,
5213
- // since $location.hash hasn't changed
5214
- $anchorScroll();
5215
- }
5216
- };
5217
- }
5218
- ]);
5219
- </file>
5220
- <file name="style.css">
5221
- body {
5222
- padding-top: 50px;
5223
- }
5224
-
5225
- .anchor {
5226
- border: 2px dashed DarkOrchid;
5227
- padding: 10px 10px 200px 10px;
5228
- }
5229
-
5230
- .fixed-header {
5231
- background-color: rgba(0, 0, 0, 0.2);
5232
- height: 50px;
5233
- position: fixed;
5234
- top: 0; left: 0; right: 0;
5235
- }
5236
-
5237
- .fixed-header > a {
5238
- display: inline-block;
5239
- margin: 5px 15px;
5240
- }
5241
- </file>
5242
- </example>
5243
- */
5244
- this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
5245
- var document = $window.document;
5246
-
5247
- // Helper function to get first anchor from a NodeList
5248
- // (using `Array#some()` instead of `angular#forEach()` since it's more performant
5249
- // and working in all supported browsers.)
5250
- function getFirstAnchor(list) {
5251
- var result = null;
5252
- Array.prototype.some.call(list, function(element) {
5253
- if (nodeName_(element) === 'a') {
5254
- result = element;
5255
- return true;
5256
- }
5257
- });
5258
- return result;
5259
- }
5260
-
5261
- function getYOffset() {
5262
-
5263
- var offset = scroll.yOffset;
5264
-
5265
- if (isFunction(offset)) {
5266
- offset = offset();
5267
- } else if (isElement(offset)) {
5268
- var elem = offset[0];
5269
- var style = $window.getComputedStyle(elem);
5270
- if (style.position !== 'fixed') {
5271
- offset = 0;
5272
- } else {
5273
- offset = elem.getBoundingClientRect().bottom;
5274
- }
5275
- } else if (!isNumber(offset)) {
5276
- offset = 0;
5277
- }
5278
-
5279
- return offset;
5280
- }
5281
-
5282
- function scrollTo(elem) {
5283
- if (elem) {
5284
- elem.scrollIntoView();
5285
-
5286
- var offset = getYOffset();
5287
-
5288
- if (offset) {
5289
- // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
5290
- // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
5291
- // top of the viewport.
5292
- //
5293
- // IF the number of pixels from the top of `elem` to the end of the page's content is less
5294
- // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
5295
- // way down the page.
5296
- //
5297
- // This is often the case for elements near the bottom of the page.
5298
- //
5299
- // In such cases we do not need to scroll the whole `offset` up, just the difference between
5300
- // the top of the element and the offset, which is enough to align the top of `elem` at the
5301
- // desired position.
5302
- var elemTop = elem.getBoundingClientRect().top;
5303
- $window.scrollBy(0, elemTop - offset);
5304
- }
5305
- } else {
5306
- $window.scrollTo(0, 0);
5307
- }
5308
- }
5309
-
5310
- function scroll(hash) {
5311
- // Allow numeric hashes
5312
- hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash();
5313
- var elm;
5314
-
5315
- // empty hash, scroll to the top of the page
5316
- if (!hash) scrollTo(null);
5317
-
5318
- // element with given id
5319
- else if ((elm = document.getElementById(hash))) scrollTo(elm);
5320
-
5321
- // first anchor with given name :-D
5322
- else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
5323
-
5324
- // no element and hash === 'top', scroll to the top of the page
5325
- else if (hash === 'top') scrollTo(null);
5326
- }
5327
-
5328
- // does not scroll when user clicks on anchor link that is currently on
5329
- // (no url change, no $location.hash() change), browser native does scroll
5330
- if (autoScrollingEnabled) {
5331
- $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
5332
- function autoScrollWatchAction(newVal, oldVal) {
5333
- // skip the initial scroll if $location.hash is empty
5334
- if (newVal === oldVal && newVal === '') return;
5335
-
5336
- jqLiteDocumentLoaded(function() {
5337
- $rootScope.$evalAsync(scroll);
5338
- });
5339
- });
5340
- }
5341
-
5342
- return scroll;
5343
- }];
5344
- }
5345
-
5346
- var $animateMinErr = minErr('$animate');
5347
- var ELEMENT_NODE = 1;
5348
- var NG_ANIMATE_CLASSNAME = 'ng-animate';
5349
-
5350
- function mergeClasses(a,b) {
5351
- if (!a && !b) return '';
5352
- if (!a) return b;
5353
- if (!b) return a;
5354
- if (isArray(a)) a = a.join(' ');
5355
- if (isArray(b)) b = b.join(' ');
5356
- return a + ' ' + b;
5357
- }
5358
-
5359
- function extractElementNode(element) {
5360
- for (var i = 0; i < element.length; i++) {
5361
- var elm = element[i];
5362
- if (elm.nodeType === ELEMENT_NODE) {
5363
- return elm;
5364
- }
5365
- }
5366
- }
5367
-
5368
- function splitClasses(classes) {
5369
- if (isString(classes)) {
5370
- classes = classes.split(' ');
5371
- }
5372
-
5373
- // Use createMap() to prevent class assumptions involving property names in
5374
- // Object.prototype
5375
- var obj = createMap();
5376
- forEach(classes, function(klass) {
5377
- // sometimes the split leaves empty string values
5378
- // incase extra spaces were applied to the options
5379
- if (klass.length) {
5380
- obj[klass] = true;
5381
- }
5382
- });
5383
- return obj;
5384
- }
5385
-
5386
- // if any other type of options value besides an Object value is
5387
- // passed into the $animate.method() animation then this helper code
5388
- // will be run which will ignore it. While this patch is not the
5389
- // greatest solution to this, a lot of existing plugins depend on
5390
- // $animate to either call the callback (< 1.2) or return a promise
5391
- // that can be changed. This helper function ensures that the options
5392
- // are wiped clean incase a callback function is provided.
5393
- function prepareAnimateOptions(options) {
5394
- return isObject(options)
5395
- ? options
5396
- : {};
5397
- }
5398
-
5399
- var $$CoreAnimateJsProvider = /** @this */ function() {
5400
- this.$get = noop;
5401
- };
5402
-
5403
- // this is prefixed with Core since it conflicts with
5404
- // the animateQueueProvider defined in ngAnimate/animateQueue.js
5405
- var $$CoreAnimateQueueProvider = /** @this */ function() {
5406
- var postDigestQueue = new NgMap();
5407
- var postDigestElements = [];
5408
-
5409
- this.$get = ['$$AnimateRunner', '$rootScope',
5410
- function($$AnimateRunner, $rootScope) {
5411
- return {
5412
- enabled: noop,
5413
- on: noop,
5414
- off: noop,
5415
- pin: noop,
5416
-
5417
- push: function(element, event, options, domOperation) {
5418
- if (domOperation) {
5419
- domOperation();
5420
- }
5421
-
5422
- options = options || {};
5423
- if (options.from) {
5424
- element.css(options.from);
5425
- }
5426
- if (options.to) {
5427
- element.css(options.to);
5428
- }
5429
-
5430
- if (options.addClass || options.removeClass) {
5431
- addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5432
- }
5433
-
5434
- var runner = new $$AnimateRunner();
5435
-
5436
- // since there are no animations to run the runner needs to be
5437
- // notified that the animation call is complete.
5438
- runner.complete();
5439
- return runner;
5440
- }
5441
- };
5442
-
5443
-
5444
- function updateData(data, classes, value) {
5445
- var changed = false;
5446
- if (classes) {
5447
- classes = isString(classes) ? classes.split(' ') :
5448
- isArray(classes) ? classes : [];
5449
- forEach(classes, function(className) {
5450
- if (className) {
5451
- changed = true;
5452
- data[className] = value;
5453
- }
5454
- });
5455
- }
5456
- return changed;
5457
- }
5458
-
5459
- function handleCSSClassChanges() {
5460
- forEach(postDigestElements, function(element) {
5461
- var data = postDigestQueue.get(element);
5462
- if (data) {
5463
- var existing = splitClasses(element.attr('class'));
5464
- var toAdd = '';
5465
- var toRemove = '';
5466
- forEach(data, function(status, className) {
5467
- var hasClass = !!existing[className];
5468
- if (status !== hasClass) {
5469
- if (status) {
5470
- toAdd += (toAdd.length ? ' ' : '') + className;
5471
- } else {
5472
- toRemove += (toRemove.length ? ' ' : '') + className;
5473
- }
5474
- }
5475
- });
5476
-
5477
- forEach(element, function(elm) {
5478
- if (toAdd) {
5479
- jqLiteAddClass(elm, toAdd);
5480
- }
5481
- if (toRemove) {
5482
- jqLiteRemoveClass(elm, toRemove);
5483
- }
5484
- });
5485
- postDigestQueue.delete(element);
5486
- }
5487
- });
5488
- postDigestElements.length = 0;
5489
- }
5490
-
5491
-
5492
- function addRemoveClassesPostDigest(element, add, remove) {
5493
- var data = postDigestQueue.get(element) || {};
5494
-
5495
- var classesAdded = updateData(data, add, true);
5496
- var classesRemoved = updateData(data, remove, false);
5497
-
5498
- if (classesAdded || classesRemoved) {
5499
-
5500
- postDigestQueue.set(element, data);
5501
- postDigestElements.push(element);
5502
-
5503
- if (postDigestElements.length === 1) {
5504
- $rootScope.$$postDigest(handleCSSClassChanges);
5505
- }
5506
- }
5507
- }
5508
- }];
5509
- };
5510
-
5511
- /**
5512
- * @ngdoc provider
5513
- * @name $animateProvider
5514
- *
5515
- * @description
5516
- * Default implementation of $animate that doesn't perform any animations, instead just
5517
- * synchronously performs DOM updates and resolves the returned runner promise.
5518
- *
5519
- * In order to enable animations the `ngAnimate` module has to be loaded.
5520
- *
5521
- * To see the functional implementation check out `src/ngAnimate/animate.js`.
5522
- */
5523
- var $AnimateProvider = ['$provide', /** @this */ function($provide) {
5524
- var provider = this;
5525
- var classNameFilter = null;
5526
- var customFilter = null;
5527
-
5528
- this.$$registeredAnimations = Object.create(null);
5529
-
5530
- /**
5531
- * @ngdoc method
5532
- * @name $animateProvider#register
5533
- *
5534
- * @description
5535
- * Registers a new injectable animation factory function. The factory function produces the
5536
- * animation object which contains callback functions for each event that is expected to be
5537
- * animated.
5538
- *
5539
- * * `eventFn`: `function(element, ... , doneFunction, options)`
5540
- * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5541
- * on the type of animation additional arguments will be injected into the animation function. The
5542
- * list below explains the function signatures for the different animation methods:
5543
- *
5544
- * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5545
- * - addClass: function(element, addedClasses, doneFunction, options)
5546
- * - removeClass: function(element, removedClasses, doneFunction, options)
5547
- * - enter, leave, move: function(element, doneFunction, options)
5548
- * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5549
- *
5550
- * Make sure to trigger the `doneFunction` once the animation is fully complete.
5551
- *
5552
- * ```js
5553
- * return {
5554
- * //enter, leave, move signature
5555
- * eventFn : function(element, done, options) {
5556
- * //code to run the animation
5557
- * //once complete, then run done()
5558
- * return function endFunction(wasCancelled) {
5559
- * //code to cancel the animation
5560
- * }
5561
- * }
5562
- * }
5563
- * ```
5564
- *
5565
- * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5566
- * @param {Function} factory The factory function that will be executed to return the animation
5567
- * object.
5568
- */
5569
- this.register = function(name, factory) {
5570
- if (name && name.charAt(0) !== '.') {
5571
- throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name);
5572
- }
5573
-
5574
- var key = name + '-animation';
5575
- provider.$$registeredAnimations[name.substr(1)] = key;
5576
- $provide.factory(key, factory);
5577
- };
5578
-
5579
- /**
5580
- * @ngdoc method
5581
- * @name $animateProvider#customFilter
5582
- *
5583
- * @description
5584
- * Sets and/or returns the custom filter function that is used to "filter" animations, i.e.
5585
- * determine if an animation is allowed or not. When no filter is specified (the default), no
5586
- * animation will be blocked. Setting the `customFilter` value will only allow animations for
5587
- * which the filter function's return value is truthy.
5588
- *
5589
- * This allows to easily create arbitrarily complex rules for filtering animations, such as
5590
- * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc.
5591
- * Filtering animations can also boost performance for low-powered devices, as well as
5592
- * applications containing a lot of structural operations.
5593
- *
5594
- * <div class="alert alert-success">
5595
- * **Best Practice:**
5596
- * Keep the filtering function as lean as possible, because it will be called for each DOM
5597
- * action (e.g. insertion, removal, class change) performed by "animation-aware" directives.
5598
- * See {@link guide/animations#which-directives-support-animations- here} for a list of built-in
5599
- * directives that support animations.
5600
- * Performing computationally expensive or time-consuming operations on each call of the
5601
- * filtering function can make your animations sluggish.
5602
- * </div>
5603
- *
5604
- * **Note:** If present, `customFilter` will be checked before
5605
- * {@link $animateProvider#classNameFilter classNameFilter}.
5606
- *
5607
- * @param {Function=} filterFn - The filter function which will be used to filter all animations.
5608
- * If a falsy value is returned, no animation will be performed. The function will be called
5609
- * with the following arguments:
5610
- * - **node** `{DOMElement}` - The DOM element to be animated.
5611
- * - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass`
5612
- * etc).
5613
- * - **options** `{Object}` - A collection of options/styles used for the animation.
5614
- * @return {Function} The current filter function or `null` if there is none set.
5615
- */
5616
- this.customFilter = function(filterFn) {
5617
- if (arguments.length === 1) {
5618
- customFilter = isFunction(filterFn) ? filterFn : null;
5619
- }
5620
-
5621
- return customFilter;
5622
- };
5623
-
5624
- /**
5625
- * @ngdoc method
5626
- * @name $animateProvider#classNameFilter
5627
- *
5628
- * @description
5629
- * Sets and/or returns the CSS class regular expression that is checked when performing
5630
- * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5631
- * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5632
- * When setting the `classNameFilter` value, animations will only be performed on elements
5633
- * that successfully match the filter expression. This in turn can boost performance
5634
- * for low-powered devices as well as applications containing a lot of structural operations.
5635
- *
5636
- * **Note:** If present, `classNameFilter` will be checked after
5637
- * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns
5638
- * false, `classNameFilter` will not be checked.
5639
- *
5640
- * @param {RegExp=} expression The className expression which will be checked against all animations
5641
- * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5642
- */
5643
- this.classNameFilter = function(expression) {
5644
- if (arguments.length === 1) {
5645
- classNameFilter = (expression instanceof RegExp) ? expression : null;
5646
- if (classNameFilter) {
5647
- var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]');
5648
- if (reservedRegex.test(classNameFilter.toString())) {
5649
- classNameFilter = null;
5650
- throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5651
- }
5652
- }
5653
- }
5654
- return classNameFilter;
5655
- };
5656
-
5657
- this.$get = ['$$animateQueue', function($$animateQueue) {
5658
- function domInsert(element, parentElement, afterElement) {
5659
- // if for some reason the previous element was removed
5660
- // from the dom sometime before this code runs then let's
5661
- // just stick to using the parent element as the anchor
5662
- if (afterElement) {
5663
- var afterNode = extractElementNode(afterElement);
5664
- if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5665
- afterElement = null;
5666
- }
5667
- }
5668
- if (afterElement) {
5669
- afterElement.after(element);
5670
- } else {
5671
- parentElement.prepend(element);
5672
- }
5673
- }
5674
-
5675
- /**
5676
- * @ngdoc service
5677
- * @name $animate
5678
- * @description The $animate service exposes a series of DOM utility methods that provide support
5679
- * for animation hooks. The default behavior is the application of DOM operations, however,
5680
- * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5681
- * to ensure that animation runs with the triggered DOM operation.
5682
- *
5683
- * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5684
- * included and only when it is active then the animation hooks that `$animate` triggers will be
5685
- * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5686
- * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5687
- * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5688
- *
5689
- * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5690
- *
5691
- * To learn more about enabling animation support, click here to visit the
5692
- * {@link ngAnimate ngAnimate module page}.
5693
- */
5694
- return {
5695
- // we don't call it directly since non-existant arguments may
5696
- // be interpreted as null within the sub enabled function
5697
-
5698
- /**
5699
- *
5700
- * @ngdoc method
5701
- * @name $animate#on
5702
- * @kind function
5703
- * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5704
- * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5705
- * is fired with the following params:
5706
- *
5707
- * ```js
5708
- * $animate.on('enter', container,
5709
- * function callback(element, phase) {
5710
- * // cool we detected an enter animation within the container
5711
- * }
5712
- * );
5713
- * ```
5714
- *
5715
- * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5716
- * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5717
- * as well as among its children
5718
- * @param {Function} callback the callback function that will be fired when the listener is triggered
5719
- *
5720
- * The arguments present in the callback function are:
5721
- * * `element` - The captured DOM element that the animation was fired on.
5722
- * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5723
- */
5724
- on: $$animateQueue.on,
5725
-
5726
- /**
5727
- *
5728
- * @ngdoc method
5729
- * @name $animate#off
5730
- * @kind function
5731
- * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5732
- * can be used in three different ways depending on the arguments:
5733
- *
5734
- * ```js
5735
- * // remove all the animation event listeners listening for `enter`
5736
- * $animate.off('enter');
5737
- *
5738
- * // remove listeners for all animation events from the container element
5739
- * $animate.off(container);
5740
- *
5741
- * // remove all the animation event listeners listening for `enter` on the given element and its children
5742
- * $animate.off('enter', container);
5743
- *
5744
- * // remove the event listener function provided by `callback` that is set
5745
- * // to listen for `enter` on the given `container` as well as its children
5746
- * $animate.off('enter', container, callback);
5747
- * ```
5748
- *
5749
- * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
5750
- * addClass, removeClass, etc...), or the container element. If it is the element, all other
5751
- * arguments are ignored.
5752
- * @param {DOMElement=} container the container element the event listener was placed on
5753
- * @param {Function=} callback the callback function that was registered as the listener
5754
- */
5755
- off: $$animateQueue.off,
5756
-
5757
- /**
5758
- * @ngdoc method
5759
- * @name $animate#pin
5760
- * @kind function
5761
- * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5762
- * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5763
- * element despite being outside the realm of the application or within another application. Say for example if the application
5764
- * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5765
- * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5766
- * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5767
- *
5768
- * Note that this feature is only active when the `ngAnimate` module is used.
5769
- *
5770
- * @param {DOMElement} element the external element that will be pinned
5771
- * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5772
- */
5773
- pin: $$animateQueue.pin,
5774
-
5775
- /**
5776
- *
5777
- * @ngdoc method
5778
- * @name $animate#enabled
5779
- * @kind function
5780
- * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5781
- * function can be called in four ways:
5782
- *
5783
- * ```js
5784
- * // returns true or false
5785
- * $animate.enabled();
5786
- *
5787
- * // changes the enabled state for all animations
5788
- * $animate.enabled(false);
5789
- * $animate.enabled(true);
5790
- *
5791
- * // returns true or false if animations are enabled for an element
5792
- * $animate.enabled(element);
5793
- *
5794
- * // changes the enabled state for an element and its children
5795
- * $animate.enabled(element, true);
5796
- * $animate.enabled(element, false);
5797
- * ```
5798
- *
5799
- * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5800
- * @param {boolean=} enabled whether or not the animations will be enabled for the element
5801
- *
5802
- * @return {boolean} whether or not animations are enabled
5803
- */
5804
- enabled: $$animateQueue.enabled,
5805
-
5806
- /**
5807
- * @ngdoc method
5808
- * @name $animate#cancel
5809
- * @kind function
5810
- * @description Cancels the provided animation.
5811
- *
5812
- * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5813
- */
5814
- cancel: function(runner) {
5815
- if (runner.end) {
5816
- runner.end();
5817
- }
5818
- },
5819
-
5820
- /**
5821
- *
5822
- * @ngdoc method
5823
- * @name $animate#enter
5824
- * @kind function
5825
- * @description Inserts the element into the DOM either after the `after` element (if provided) or
5826
- * as the first child within the `parent` element and then triggers an animation.
5827
- * A promise is returned that will be resolved during the next digest once the animation
5828
- * has completed.
5829
- *
5830
- * @param {DOMElement} element the element which will be inserted into the DOM
5831
- * @param {DOMElement} parent the parent element which will append the element as
5832
- * a child (so long as the after element is not present)
5833
- * @param {DOMElement=} after the sibling element after which the element will be appended
5834
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
5835
- * The object can have the following properties:
5836
- *
5837
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
5838
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5839
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5840
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5841
- *
5842
- * @return {Promise} the animation callback promise
5843
- */
5844
- enter: function(element, parent, after, options) {
5845
- parent = parent && jqLite(parent);
5846
- after = after && jqLite(after);
5847
- parent = parent || after.parent();
5848
- domInsert(element, parent, after);
5849
- return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5850
- },
5851
-
5852
- /**
5853
- *
5854
- * @ngdoc method
5855
- * @name $animate#move
5856
- * @kind function
5857
- * @description Inserts (moves) the element into its new position in the DOM either after
5858
- * the `after` element (if provided) or as the first child within the `parent` element
5859
- * and then triggers an animation. A promise is returned that will be resolved
5860
- * during the next digest once the animation has completed.
5861
- *
5862
- * @param {DOMElement} element the element which will be moved into the new DOM position
5863
- * @param {DOMElement} parent the parent element which will append the element as
5864
- * a child (so long as the after element is not present)
5865
- * @param {DOMElement=} after the sibling element after which the element will be appended
5866
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
5867
- * The object can have the following properties:
5868
- *
5869
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
5870
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5871
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5872
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5873
- *
5874
- * @return {Promise} the animation callback promise
5875
- */
5876
- move: function(element, parent, after, options) {
5877
- parent = parent && jqLite(parent);
5878
- after = after && jqLite(after);
5879
- parent = parent || after.parent();
5880
- domInsert(element, parent, after);
5881
- return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5882
- },
5883
-
5884
- /**
5885
- * @ngdoc method
5886
- * @name $animate#leave
5887
- * @kind function
5888
- * @description Triggers an animation and then removes the element from the DOM.
5889
- * When the function is called a promise is returned that will be resolved during the next
5890
- * digest once the animation has completed.
5891
- *
5892
- * @param {DOMElement} element the element which will be removed from the DOM
5893
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
5894
- * The object can have the following properties:
5895
- *
5896
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
5897
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5898
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5899
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5900
- *
5901
- * @return {Promise} the animation callback promise
5902
- */
5903
- leave: function(element, options) {
5904
- return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5905
- element.remove();
5906
- });
5907
- },
5908
-
5909
- /**
5910
- * @ngdoc method
5911
- * @name $animate#addClass
5912
- * @kind function
5913
- *
5914
- * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5915
- * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5916
- * animation if element already contains the CSS class or if the class is removed at a later step.
5917
- * Note that class-based animations are treated differently compared to structural animations
5918
- * (like enter, move and leave) since the CSS classes may be added/removed at different points
5919
- * depending if CSS or JavaScript animations are used.
5920
- *
5921
- * @param {DOMElement} element the element which the CSS classes will be applied to
5922
- * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5923
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
5924
- * The object can have the following properties:
5925
- *
5926
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
5927
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5928
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5929
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5930
- *
5931
- * @return {Promise} the animation callback promise
5932
- */
5933
- addClass: function(element, className, options) {
5934
- options = prepareAnimateOptions(options);
5935
- options.addClass = mergeClasses(options.addclass, className);
5936
- return $$animateQueue.push(element, 'addClass', options);
5937
- },
5938
-
5939
- /**
5940
- * @ngdoc method
5941
- * @name $animate#removeClass
5942
- * @kind function
5943
- *
5944
- * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5945
- * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5946
- * animation if element does not contain the CSS class or if the class is added at a later step.
5947
- * Note that class-based animations are treated differently compared to structural animations
5948
- * (like enter, move and leave) since the CSS classes may be added/removed at different points
5949
- * depending if CSS or JavaScript animations are used.
5950
- *
5951
- * @param {DOMElement} element the element which the CSS classes will be applied to
5952
- * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5953
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
5954
- * The object can have the following properties:
5955
- *
5956
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
5957
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5958
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5959
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5960
- *
5961
- * @return {Promise} the animation callback promise
5962
- */
5963
- removeClass: function(element, className, options) {
5964
- options = prepareAnimateOptions(options);
5965
- options.removeClass = mergeClasses(options.removeClass, className);
5966
- return $$animateQueue.push(element, 'removeClass', options);
5967
- },
5968
-
5969
- /**
5970
- * @ngdoc method
5971
- * @name $animate#setClass
5972
- * @kind function
5973
- *
5974
- * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5975
- * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5976
- * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5977
- * passed. Note that class-based animations are treated differently compared to structural animations
5978
- * (like enter, move and leave) since the CSS classes may be added/removed at different points
5979
- * depending if CSS or JavaScript animations are used.
5980
- *
5981
- * @param {DOMElement} element the element which the CSS classes will be applied to
5982
- * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5983
- * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5984
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
5985
- * The object can have the following properties:
5986
- *
5987
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
5988
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
5989
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
5990
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
5991
- *
5992
- * @return {Promise} the animation callback promise
5993
- */
5994
- setClass: function(element, add, remove, options) {
5995
- options = prepareAnimateOptions(options);
5996
- options.addClass = mergeClasses(options.addClass, add);
5997
- options.removeClass = mergeClasses(options.removeClass, remove);
5998
- return $$animateQueue.push(element, 'setClass', options);
5999
- },
6000
-
6001
- /**
6002
- * @ngdoc method
6003
- * @name $animate#animate
6004
- * @kind function
6005
- *
6006
- * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
6007
- * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
6008
- * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
6009
- * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
6010
- * style in `to`, the style in `from` is applied immediately, and no animation is run.
6011
- * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
6012
- * method (or as part of the `options` parameter):
6013
- *
6014
- * ```js
6015
- * ngModule.animation('.my-inline-animation', function() {
6016
- * return {
6017
- * animate : function(element, from, to, done, options) {
6018
- * //animation
6019
- * done();
6020
- * }
6021
- * }
6022
- * });
6023
- * ```
6024
- *
6025
- * @param {DOMElement} element the element which the CSS styles will be applied to
6026
- * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
6027
- * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
6028
- * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
6029
- * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
6030
- * (Note that if no animation is detected then this value will not be applied to the element.)
6031
- * @param {object=} options an optional collection of options/styles that will be applied to the element.
6032
- * The object can have the following properties:
6033
- *
6034
- * - **addClass** - `{string}` - space-separated CSS classes to add to element
6035
- * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
6036
- * - **removeClass** - `{string}` - space-separated CSS classes to remove from element
6037
- * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
6038
- *
6039
- * @return {Promise} the animation callback promise
6040
- */
6041
- animate: function(element, from, to, className, options) {
6042
- options = prepareAnimateOptions(options);
6043
- options.from = options.from ? extend(options.from, from) : from;
6044
- options.to = options.to ? extend(options.to, to) : to;
6045
-
6046
- className = className || 'ng-inline-animate';
6047
- options.tempClasses = mergeClasses(options.tempClasses, className);
6048
- return $$animateQueue.push(element, 'animate', options);
6049
- }
6050
- };
6051
- }];
6052
- }];
6053
-
6054
- var $$AnimateAsyncRunFactoryProvider = /** @this */ function() {
6055
- this.$get = ['$$rAF', function($$rAF) {
6056
- var waitQueue = [];
6057
-
6058
- function waitForTick(fn) {
6059
- waitQueue.push(fn);
6060
- if (waitQueue.length > 1) return;
6061
- $$rAF(function() {
6062
- for (var i = 0; i < waitQueue.length; i++) {
6063
- waitQueue[i]();
6064
- }
6065
- waitQueue = [];
6066
- });
6067
- }
6068
-
6069
- return function() {
6070
- var passed = false;
6071
- waitForTick(function() {
6072
- passed = true;
6073
- });
6074
- return function(callback) {
6075
- if (passed) {
6076
- callback();
6077
- } else {
6078
- waitForTick(callback);
6079
- }
6080
- };
6081
- };
6082
- }];
6083
- };
6084
-
6085
- var $$AnimateRunnerFactoryProvider = /** @this */ function() {
6086
- this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout',
6087
- function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) {
6088
-
6089
- var INITIAL_STATE = 0;
6090
- var DONE_PENDING_STATE = 1;
6091
- var DONE_COMPLETE_STATE = 2;
6092
-
6093
- AnimateRunner.chain = function(chain, callback) {
6094
- var index = 0;
6095
-
6096
- next();
6097
- function next() {
6098
- if (index === chain.length) {
6099
- callback(true);
6100
- return;
6101
- }
6102
-
6103
- chain[index](function(response) {
6104
- if (response === false) {
6105
- callback(false);
6106
- return;
6107
- }
6108
- index++;
6109
- next();
6110
- });
6111
- }
6112
- };
6113
-
6114
- AnimateRunner.all = function(runners, callback) {
6115
- var count = 0;
6116
- var status = true;
6117
- forEach(runners, function(runner) {
6118
- runner.done(onProgress);
6119
- });
6120
-
6121
- function onProgress(response) {
6122
- status = status && response;
6123
- if (++count === runners.length) {
6124
- callback(status);
6125
- }
6126
- }
6127
- };
6128
-
6129
- function AnimateRunner(host) {
6130
- this.setHost(host);
6131
-
6132
- var rafTick = $$animateAsyncRun();
6133
- var timeoutTick = function(fn) {
6134
- $timeout(fn, 0, false);
6135
- };
6136
-
6137
- this._doneCallbacks = [];
6138
- this._tick = function(fn) {
6139
- if ($$isDocumentHidden()) {
6140
- timeoutTick(fn);
6141
- } else {
6142
- rafTick(fn);
6143
- }
6144
- };
6145
- this._state = 0;
6146
- }
6147
-
6148
- AnimateRunner.prototype = {
6149
- setHost: function(host) {
6150
- this.host = host || {};
6151
- },
6152
-
6153
- done: function(fn) {
6154
- if (this._state === DONE_COMPLETE_STATE) {
6155
- fn();
6156
- } else {
6157
- this._doneCallbacks.push(fn);
6158
- }
6159
- },
6160
-
6161
- progress: noop,
6162
-
6163
- getPromise: function() {
6164
- if (!this.promise) {
6165
- var self = this;
6166
- this.promise = $q(function(resolve, reject) {
6167
- self.done(function(status) {
6168
- if (status === false) {
6169
- reject();
6170
- } else {
6171
- resolve();
6172
- }
6173
- });
6174
- });
6175
- }
6176
- return this.promise;
6177
- },
6178
-
6179
- then: function(resolveHandler, rejectHandler) {
6180
- return this.getPromise().then(resolveHandler, rejectHandler);
6181
- },
6182
-
6183
- 'catch': function(handler) {
6184
- return this.getPromise()['catch'](handler);
6185
- },
6186
-
6187
- 'finally': function(handler) {
6188
- return this.getPromise()['finally'](handler);
6189
- },
6190
-
6191
- pause: function() {
6192
- if (this.host.pause) {
6193
- this.host.pause();
6194
- }
6195
- },
6196
-
6197
- resume: function() {
6198
- if (this.host.resume) {
6199
- this.host.resume();
6200
- }
6201
- },
6202
-
6203
- end: function() {
6204
- if (this.host.end) {
6205
- this.host.end();
6206
- }
6207
- this._resolve(true);
6208
- },
6209
-
6210
- cancel: function() {
6211
- if (this.host.cancel) {
6212
- this.host.cancel();
6213
- }
6214
- this._resolve(false);
6215
- },
6216
-
6217
- complete: function(response) {
6218
- var self = this;
6219
- if (self._state === INITIAL_STATE) {
6220
- self._state = DONE_PENDING_STATE;
6221
- self._tick(function() {
6222
- self._resolve(response);
6223
- });
6224
- }
6225
- },
6226
-
6227
- _resolve: function(response) {
6228
- if (this._state !== DONE_COMPLETE_STATE) {
6229
- forEach(this._doneCallbacks, function(fn) {
6230
- fn(response);
6231
- });
6232
- this._doneCallbacks.length = 0;
6233
- this._state = DONE_COMPLETE_STATE;
6234
- }
6235
- }
6236
- };
6237
-
6238
- return AnimateRunner;
6239
- }];
6240
- };
6241
-
6242
- /* exported $CoreAnimateCssProvider */
6243
-
6244
- /**
6245
- * @ngdoc service
6246
- * @name $animateCss
6247
- * @kind object
6248
- * @this
6249
- *
6250
- * @description
6251
- * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
6252
- * then the `$animateCss` service will actually perform animations.
6253
- *
6254
- * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
6255
- */
6256
- var $CoreAnimateCssProvider = function() {
6257
- this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
6258
-
6259
- return function(element, initialOptions) {
6260
- // all of the animation functions should create
6261
- // a copy of the options data, however, if a
6262
- // parent service has already created a copy then
6263
- // we should stick to using that
6264
- var options = initialOptions || {};
6265
- if (!options.$$prepared) {
6266
- options = copy(options);
6267
- }
6268
-
6269
- // there is no point in applying the styles since
6270
- // there is no animation that goes on at all in
6271
- // this version of $animateCss.
6272
- if (options.cleanupStyles) {
6273
- options.from = options.to = null;
6274
- }
6275
-
6276
- if (options.from) {
6277
- element.css(options.from);
6278
- options.from = null;
6279
- }
6280
-
6281
- var closed, runner = new $$AnimateRunner();
6282
- return {
6283
- start: run,
6284
- end: run
6285
- };
6286
-
6287
- function run() {
6288
- $$rAF(function() {
6289
- applyAnimationContents();
6290
- if (!closed) {
6291
- runner.complete();
6292
- }
6293
- closed = true;
6294
- });
6295
- return runner;
6296
- }
6297
-
6298
- function applyAnimationContents() {
6299
- if (options.addClass) {
6300
- element.addClass(options.addClass);
6301
- options.addClass = null;
6302
- }
6303
- if (options.removeClass) {
6304
- element.removeClass(options.removeClass);
6305
- options.removeClass = null;
6306
- }
6307
- if (options.to) {
6308
- element.css(options.to);
6309
- options.to = null;
6310
- }
6311
- }
6312
- };
6313
- }];
6314
- };
6315
-
6316
- /* global stripHash: true */
6317
-
6318
- /**
6319
- * ! This is a private undocumented service !
6320
- *
6321
- * @name $browser
6322
- * @requires $log
6323
- * @description
6324
- * This object has two goals:
6325
- *
6326
- * - hide all the global state in the browser caused by the window object
6327
- * - abstract away all the browser specific features and inconsistencies
6328
- *
6329
- * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
6330
- * service, which can be used for convenient testing of the application without the interaction with
6331
- * the real browser apis.
6332
- */
6333
- /**
6334
- * @param {object} window The global window object.
6335
- * @param {object} document jQuery wrapped document.
6336
- * @param {object} $log window.console or an object with the same interface.
6337
- * @param {object} $sniffer $sniffer service
6338
- */
6339
- function Browser(window, document, $log, $sniffer) {
6340
- var self = this,
6341
- location = window.location,
6342
- history = window.history,
6343
- setTimeout = window.setTimeout,
6344
- clearTimeout = window.clearTimeout,
6345
- pendingDeferIds = {};
6346
-
6347
- self.isMock = false;
6348
-
6349
- var outstandingRequestCount = 0;
6350
- var outstandingRequestCallbacks = [];
6351
-
6352
- // TODO(vojta): remove this temporary api
6353
- self.$$completeOutstandingRequest = completeOutstandingRequest;
6354
- self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
6355
-
6356
- /**
6357
- * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
6358
- * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
6359
- */
6360
- function completeOutstandingRequest(fn) {
6361
- try {
6362
- fn.apply(null, sliceArgs(arguments, 1));
6363
- } finally {
6364
- outstandingRequestCount--;
6365
- if (outstandingRequestCount === 0) {
6366
- while (outstandingRequestCallbacks.length) {
6367
- try {
6368
- outstandingRequestCallbacks.pop()();
6369
- } catch (e) {
6370
- $log.error(e);
6371
- }
6372
- }
6373
- }
6374
- }
6375
- }
6376
-
6377
- function getHash(url) {
6378
- var index = url.indexOf('#');
6379
- return index === -1 ? '' : url.substr(index);
6380
- }
6381
-
6382
- /**
6383
- * @private
6384
- * Note: this method is used only by scenario runner
6385
- * TODO(vojta): prefix this method with $$ ?
6386
- * @param {function()} callback Function that will be called when no outstanding request
6387
- */
6388
- self.notifyWhenNoOutstandingRequests = function(callback) {
6389
- if (outstandingRequestCount === 0) {
6390
- callback();
6391
- } else {
6392
- outstandingRequestCallbacks.push(callback);
6393
- }
6394
- };
6395
-
6396
- //////////////////////////////////////////////////////////////
6397
- // URL API
6398
- //////////////////////////////////////////////////////////////
6399
-
6400
- var cachedState, lastHistoryState,
6401
- lastBrowserUrl = location.href,
6402
- baseElement = document.find('base'),
6403
- pendingLocation = null,
6404
- getCurrentState = !$sniffer.history ? noop : function getCurrentState() {
6405
- try {
6406
- return history.state;
6407
- } catch (e) {
6408
- // MSIE can reportedly throw when there is no state (UNCONFIRMED).
6409
- }
6410
- };
6411
-
6412
- cacheState();
6413
-
6414
- /**
6415
- * @name $browser#url
6416
- *
6417
- * @description
6418
- * GETTER:
6419
- * Without any argument, this method just returns current value of location.href.
6420
- *
6421
- * SETTER:
6422
- * With at least one argument, this method sets url to new value.
6423
- * If html5 history api supported, pushState/replaceState is used, otherwise
6424
- * location.href/location.replace is used.
6425
- * Returns its own instance to allow chaining
6426
- *
6427
- * NOTE: this api is intended for use only by the $location service. Please use the
6428
- * {@link ng.$location $location service} to change url.
6429
- *
6430
- * @param {string} url New url (when used as setter)
6431
- * @param {boolean=} replace Should new url replace current history record?
6432
- * @param {object=} state object to use with pushState/replaceState
6433
- */
6434
- self.url = function(url, replace, state) {
6435
- // In modern browsers `history.state` is `null` by default; treating it separately
6436
- // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
6437
- // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
6438
- if (isUndefined(state)) {
6439
- state = null;
6440
- }
6441
-
6442
- // Android Browser BFCache causes location, history reference to become stale.
6443
- if (location !== window.location) location = window.location;
6444
- if (history !== window.history) history = window.history;
6445
-
6446
- // setter
6447
- if (url) {
6448
- var sameState = lastHistoryState === state;
6449
-
6450
- // Don't change anything if previous and current URLs and states match. This also prevents
6451
- // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
6452
- // See https://github.com/angular/angular.js/commit/ffb2701
6453
- if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
6454
- return self;
6455
- }
6456
- var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
6457
- lastBrowserUrl = url;
6458
- lastHistoryState = state;
6459
- // Don't use history API if only the hash changed
6460
- // due to a bug in IE10/IE11 which leads
6461
- // to not firing a `hashchange` nor `popstate` event
6462
- // in some cases (see #9143).
6463
- if ($sniffer.history && (!sameBase || !sameState)) {
6464
- history[replace ? 'replaceState' : 'pushState'](state, '', url);
6465
- cacheState();
6466
- } else {
6467
- if (!sameBase) {
6468
- pendingLocation = url;
6469
- }
6470
- if (replace) {
6471
- location.replace(url);
6472
- } else if (!sameBase) {
6473
- location.href = url;
6474
- } else {
6475
- location.hash = getHash(url);
6476
- }
6477
- if (location.href !== url) {
6478
- pendingLocation = url;
6479
- }
6480
- }
6481
- if (pendingLocation) {
6482
- pendingLocation = url;
6483
- }
6484
- return self;
6485
- // getter
6486
- } else {
6487
- // - pendingLocation is needed as browsers don't allow to read out
6488
- // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
6489
- // https://openradar.appspot.com/22186109).
6490
- // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
6491
- return pendingLocation || location.href.replace(/%27/g,'\'');
6492
- }
6493
- };
6494
-
6495
- /**
6496
- * @name $browser#state
6497
- *
6498
- * @description
6499
- * This method is a getter.
6500
- *
6501
- * Return history.state or null if history.state is undefined.
6502
- *
6503
- * @returns {object} state
6504
- */
6505
- self.state = function() {
6506
- return cachedState;
6507
- };
6508
-
6509
- var urlChangeListeners = [],
6510
- urlChangeInit = false;
6511
-
6512
- function cacheStateAndFireUrlChange() {
6513
- pendingLocation = null;
6514
- fireStateOrUrlChange();
6515
- }
6516
-
6517
- // This variable should be used *only* inside the cacheState function.
6518
- var lastCachedState = null;
6519
- function cacheState() {
6520
- // This should be the only place in $browser where `history.state` is read.
6521
- cachedState = getCurrentState();
6522
- cachedState = isUndefined(cachedState) ? null : cachedState;
6523
-
6524
- // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
6525
- if (equals(cachedState, lastCachedState)) {
6526
- cachedState = lastCachedState;
6527
- }
6528
-
6529
- lastCachedState = cachedState;
6530
- lastHistoryState = cachedState;
6531
- }
6532
-
6533
- function fireStateOrUrlChange() {
6534
- var prevLastHistoryState = lastHistoryState;
6535
- cacheState();
6536
-
6537
- if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
6538
- return;
6539
- }
6540
-
6541
- lastBrowserUrl = self.url();
6542
- lastHistoryState = cachedState;
6543
- forEach(urlChangeListeners, function(listener) {
6544
- listener(self.url(), cachedState);
6545
- });
6546
- }
6547
-
6548
- /**
6549
- * @name $browser#onUrlChange
6550
- *
6551
- * @description
6552
- * Register callback function that will be called, when url changes.
6553
- *
6554
- * It's only called when the url is changed from outside of angular:
6555
- * - user types different url into address bar
6556
- * - user clicks on history (forward/back) button
6557
- * - user clicks on a link
6558
- *
6559
- * It's not called when url is changed by $browser.url() method
6560
- *
6561
- * The listener gets called with new url as parameter.
6562
- *
6563
- * NOTE: this api is intended for use only by the $location service. Please use the
6564
- * {@link ng.$location $location service} to monitor url changes in angular apps.
6565
- *
6566
- * @param {function(string)} listener Listener function to be called when url changes.
6567
- * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
6568
- */
6569
- self.onUrlChange = function(callback) {
6570
- // TODO(vojta): refactor to use node's syntax for events
6571
- if (!urlChangeInit) {
6572
- // We listen on both (hashchange/popstate) when available, as some browsers don't
6573
- // fire popstate when user changes the address bar and don't fire hashchange when url
6574
- // changed by push/replaceState
6575
-
6576
- // html5 history api - popstate event
6577
- if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
6578
- // hashchange event
6579
- jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
6580
-
6581
- urlChangeInit = true;
6582
- }
6583
-
6584
- urlChangeListeners.push(callback);
6585
- return callback;
6586
- };
6587
-
6588
- /**
6589
- * @private
6590
- * Remove popstate and hashchange handler from window.
6591
- *
6592
- * NOTE: this api is intended for use only by $rootScope.
6593
- */
6594
- self.$$applicationDestroyed = function() {
6595
- jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
6596
- };
6597
-
6598
- /**
6599
- * Checks whether the url has changed outside of Angular.
6600
- * Needs to be exported to be able to check for changes that have been done in sync,
6601
- * as hashchange/popstate events fire in async.
6602
- */
6603
- self.$$checkUrlChange = fireStateOrUrlChange;
6604
-
6605
- //////////////////////////////////////////////////////////////
6606
- // Misc API
6607
- //////////////////////////////////////////////////////////////
6608
-
6609
- /**
6610
- * @name $browser#baseHref
6611
- *
6612
- * @description
6613
- * Returns current <base href>
6614
- * (always relative - without domain)
6615
- *
6616
- * @returns {string} The current base href
6617
- */
6618
- self.baseHref = function() {
6619
- var href = baseElement.attr('href');
6620
- return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : '';
6621
- };
6622
-
6623
- /**
6624
- * @name $browser#defer
6625
- * @param {function()} fn A function, who's execution should be deferred.
6626
- * @param {number=} [delay=0] of milliseconds to defer the function execution.
6627
- * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
6628
- *
6629
- * @description
6630
- * Executes a fn asynchronously via `setTimeout(fn, delay)`.
6631
- *
6632
- * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
6633
- * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
6634
- * via `$browser.defer.flush()`.
6635
- *
6636
- */
6637
- self.defer = function(fn, delay) {
6638
- var timeoutId;
6639
- outstandingRequestCount++;
6640
- timeoutId = setTimeout(function() {
6641
- delete pendingDeferIds[timeoutId];
6642
- completeOutstandingRequest(fn);
6643
- }, delay || 0);
6644
- pendingDeferIds[timeoutId] = true;
6645
- return timeoutId;
6646
- };
6647
-
6648
-
6649
- /**
6650
- * @name $browser#defer.cancel
6651
- *
6652
- * @description
6653
- * Cancels a deferred task identified with `deferId`.
6654
- *
6655
- * @param {*} deferId Token returned by the `$browser.defer` function.
6656
- * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
6657
- * canceled.
6658
- */
6659
- self.defer.cancel = function(deferId) {
6660
- if (pendingDeferIds[deferId]) {
6661
- delete pendingDeferIds[deferId];
6662
- clearTimeout(deferId);
6663
- completeOutstandingRequest(noop);
6664
- return true;
6665
- }
6666
- return false;
6667
- };
6668
-
6669
- }
6670
-
6671
- /** @this */
6672
- function $BrowserProvider() {
6673
- this.$get = ['$window', '$log', '$sniffer', '$document',
6674
- function($window, $log, $sniffer, $document) {
6675
- return new Browser($window, $document, $log, $sniffer);
6676
- }];
6677
- }
6678
-
6679
- /**
6680
- * @ngdoc service
6681
- * @name $cacheFactory
6682
- * @this
6683
- *
6684
- * @description
6685
- * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
6686
- * them.
6687
- *
6688
- * ```js
6689
- *
6690
- * var cache = $cacheFactory('cacheId');
6691
- * expect($cacheFactory.get('cacheId')).toBe(cache);
6692
- * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
6693
- *
6694
- * cache.put("key", "value");
6695
- * cache.put("another key", "another value");
6696
- *
6697
- * // We've specified no options on creation
6698
- * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
6699
- *
6700
- * ```
6701
- *
6702
- *
6703
- * @param {string} cacheId Name or id of the newly created cache.
6704
- * @param {object=} options Options object that specifies the cache behavior. Properties:
6705
- *
6706
- * - `{number=}` `capacity` — turns the cache into LRU cache.
6707
- *
6708
- * @returns {object} Newly created cache object with the following set of methods:
6709
- *
6710
- * - `{object}` `info()` — Returns id, size, and options of cache.
6711
- * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
6712
- * it.
6713
- * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
6714
- * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
6715
- * - `{void}` `removeAll()` — Removes all cached values.
6716
- * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
6717
- *
6718
- * @example
6719
- <example module="cacheExampleApp" name="cache-factory">
6720
- <file name="index.html">
6721
- <div ng-controller="CacheController">
6722
- <input ng-model="newCacheKey" placeholder="Key">
6723
- <input ng-model="newCacheValue" placeholder="Value">
6724
- <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
6725
-
6726
- <p ng-if="keys.length">Cached Values</p>
6727
- <div ng-repeat="key in keys">
6728
- <span ng-bind="key"></span>
6729
- <span>: </span>
6730
- <b ng-bind="cache.get(key)"></b>
6731
- </div>
6732
-
6733
- <p>Cache Info</p>
6734
- <div ng-repeat="(key, value) in cache.info()">
6735
- <span ng-bind="key"></span>
6736
- <span>: </span>
6737
- <b ng-bind="value"></b>
6738
- </div>
6739
- </div>
6740
- </file>
6741
- <file name="script.js">
6742
- angular.module('cacheExampleApp', []).
6743
- controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6744
- $scope.keys = [];
6745
- $scope.cache = $cacheFactory('cacheId');
6746
- $scope.put = function(key, value) {
6747
- if (angular.isUndefined($scope.cache.get(key))) {
6748
- $scope.keys.push(key);
6749
- }
6750
- $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6751
- };
6752
- }]);
6753
- </file>
6754
- <file name="style.css">
6755
- p {
6756
- margin: 10px 0 3px;
6757
- }
6758
- </file>
6759
- </example>
6760
- */
6761
- function $CacheFactoryProvider() {
6762
-
6763
- this.$get = function() {
6764
- var caches = {};
6765
-
6766
- function cacheFactory(cacheId, options) {
6767
- if (cacheId in caches) {
6768
- throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId);
6769
- }
6770
-
6771
- var size = 0,
6772
- stats = extend({}, options, {id: cacheId}),
6773
- data = createMap(),
6774
- capacity = (options && options.capacity) || Number.MAX_VALUE,
6775
- lruHash = createMap(),
6776
- freshEnd = null,
6777
- staleEnd = null;
6778
-
6779
- /**
6780
- * @ngdoc type
6781
- * @name $cacheFactory.Cache
6782
- *
6783
- * @description
6784
- * A cache object used to store and retrieve data, primarily used by
6785
- * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6786
- * templates and other data.
6787
- *
6788
- * ```js
6789
- * angular.module('superCache')
6790
- * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6791
- * return $cacheFactory('super-cache');
6792
- * }]);
6793
- * ```
6794
- *
6795
- * Example test:
6796
- *
6797
- * ```js
6798
- * it('should behave like a cache', inject(function(superCache) {
6799
- * superCache.put('key', 'value');
6800
- * superCache.put('another key', 'another value');
6801
- *
6802
- * expect(superCache.info()).toEqual({
6803
- * id: 'super-cache',
6804
- * size: 2
6805
- * });
6806
- *
6807
- * superCache.remove('another key');
6808
- * expect(superCache.get('another key')).toBeUndefined();
6809
- *
6810
- * superCache.removeAll();
6811
- * expect(superCache.info()).toEqual({
6812
- * id: 'super-cache',
6813
- * size: 0
6814
- * });
6815
- * }));
6816
- * ```
6817
- */
6818
- return (caches[cacheId] = {
6819
-
6820
- /**
6821
- * @ngdoc method
6822
- * @name $cacheFactory.Cache#put
6823
- * @kind function
6824
- *
6825
- * @description
6826
- * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6827
- * retrieved later, and incrementing the size of the cache if the key was not already
6828
- * present in the cache. If behaving like an LRU cache, it will also remove stale
6829
- * entries from the set.
6830
- *
6831
- * It will not insert undefined values into the cache.
6832
- *
6833
- * @param {string} key the key under which the cached data is stored.
6834
- * @param {*} value the value to store alongside the key. If it is undefined, the key
6835
- * will not be stored.
6836
- * @returns {*} the value stored.
6837
- */
6838
- put: function(key, value) {
6839
- if (isUndefined(value)) return;
6840
- if (capacity < Number.MAX_VALUE) {
6841
- var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6842
-
6843
- refresh(lruEntry);
6844
- }
6845
-
6846
- if (!(key in data)) size++;
6847
- data[key] = value;
6848
-
6849
- if (size > capacity) {
6850
- this.remove(staleEnd.key);
6851
- }
6852
-
6853
- return value;
6854
- },
6855
-
6856
- /**
6857
- * @ngdoc method
6858
- * @name $cacheFactory.Cache#get
6859
- * @kind function
6860
- *
6861
- * @description
6862
- * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6863
- *
6864
- * @param {string} key the key of the data to be retrieved
6865
- * @returns {*} the value stored.
6866
- */
6867
- get: function(key) {
6868
- if (capacity < Number.MAX_VALUE) {
6869
- var lruEntry = lruHash[key];
6870
-
6871
- if (!lruEntry) return;
6872
-
6873
- refresh(lruEntry);
6874
- }
6875
-
6876
- return data[key];
6877
- },
6878
-
6879
-
6880
- /**
6881
- * @ngdoc method
6882
- * @name $cacheFactory.Cache#remove
6883
- * @kind function
6884
- *
6885
- * @description
6886
- * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6887
- *
6888
- * @param {string} key the key of the entry to be removed
6889
- */
6890
- remove: function(key) {
6891
- if (capacity < Number.MAX_VALUE) {
6892
- var lruEntry = lruHash[key];
6893
-
6894
- if (!lruEntry) return;
6895
-
6896
- if (lruEntry === freshEnd) freshEnd = lruEntry.p;
6897
- if (lruEntry === staleEnd) staleEnd = lruEntry.n;
6898
- link(lruEntry.n,lruEntry.p);
6899
-
6900
- delete lruHash[key];
6901
- }
6902
-
6903
- if (!(key in data)) return;
6904
-
6905
- delete data[key];
6906
- size--;
6907
- },
6908
-
6909
-
6910
- /**
6911
- * @ngdoc method
6912
- * @name $cacheFactory.Cache#removeAll
6913
- * @kind function
6914
- *
6915
- * @description
6916
- * Clears the cache object of any entries.
6917
- */
6918
- removeAll: function() {
6919
- data = createMap();
6920
- size = 0;
6921
- lruHash = createMap();
6922
- freshEnd = staleEnd = null;
6923
- },
6924
-
6925
-
6926
- /**
6927
- * @ngdoc method
6928
- * @name $cacheFactory.Cache#destroy
6929
- * @kind function
6930
- *
6931
- * @description
6932
- * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6933
- * removing it from the {@link $cacheFactory $cacheFactory} set.
6934
- */
6935
- destroy: function() {
6936
- data = null;
6937
- stats = null;
6938
- lruHash = null;
6939
- delete caches[cacheId];
6940
- },
6941
-
6942
-
6943
- /**
6944
- * @ngdoc method
6945
- * @name $cacheFactory.Cache#info
6946
- * @kind function
6947
- *
6948
- * @description
6949
- * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6950
- *
6951
- * @returns {object} an object with the following properties:
6952
- * <ul>
6953
- * <li>**id**: the id of the cache instance</li>
6954
- * <li>**size**: the number of entries kept in the cache instance</li>
6955
- * <li>**...**: any additional properties from the options object when creating the
6956
- * cache.</li>
6957
- * </ul>
6958
- */
6959
- info: function() {
6960
- return extend({}, stats, {size: size});
6961
- }
6962
- });
6963
-
6964
-
6965
- /**
6966
- * makes the `entry` the freshEnd of the LRU linked list
6967
- */
6968
- function refresh(entry) {
6969
- if (entry !== freshEnd) {
6970
- if (!staleEnd) {
6971
- staleEnd = entry;
6972
- } else if (staleEnd === entry) {
6973
- staleEnd = entry.n;
6974
- }
6975
-
6976
- link(entry.n, entry.p);
6977
- link(entry, freshEnd);
6978
- freshEnd = entry;
6979
- freshEnd.n = null;
6980
- }
6981
- }
6982
-
6983
-
6984
- /**
6985
- * bidirectionally links two entries of the LRU linked list
6986
- */
6987
- function link(nextEntry, prevEntry) {
6988
- if (nextEntry !== prevEntry) {
6989
- if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6990
- if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6991
- }
6992
- }
6993
- }
6994
-
6995
-
6996
- /**
6997
- * @ngdoc method
6998
- * @name $cacheFactory#info
6999
- *
7000
- * @description
7001
- * Get information about all the caches that have been created
7002
- *
7003
- * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
7004
- */
7005
- cacheFactory.info = function() {
7006
- var info = {};
7007
- forEach(caches, function(cache, cacheId) {
7008
- info[cacheId] = cache.info();
7009
- });
7010
- return info;
7011
- };
7012
-
7013
-
7014
- /**
7015
- * @ngdoc method
7016
- * @name $cacheFactory#get
7017
- *
7018
- * @description
7019
- * Get access to a cache object by the `cacheId` used when it was created.
7020
- *
7021
- * @param {string} cacheId Name or id of a cache to access.
7022
- * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
7023
- */
7024
- cacheFactory.get = function(cacheId) {
7025
- return caches[cacheId];
7026
- };
7027
-
7028
-
7029
- return cacheFactory;
7030
- };
7031
- }
7032
-
7033
- /**
7034
- * @ngdoc service
7035
- * @name $templateCache
7036
- * @this
7037
- *
7038
- * @description
7039
- * The first time a template is used, it is loaded in the template cache for quick retrieval. You
7040
- * can load templates directly into the cache in a `script` tag, or by consuming the
7041
- * `$templateCache` service directly.
7042
- *
7043
- * Adding via the `script` tag:
7044
- *
7045
- * ```html
7046
- * <script type="text/ng-template" id="templateId.html">
7047
- * <p>This is the content of the template</p>
7048
- * </script>
7049
- * ```
7050
- *
7051
- * **Note:** the `script` tag containing the template does not need to be included in the `head` of
7052
- * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
7053
- * element with ng-app attribute), otherwise the template will be ignored.
7054
- *
7055
- * Adding via the `$templateCache` service:
7056
- *
7057
- * ```js
7058
- * var myApp = angular.module('myApp', []);
7059
- * myApp.run(function($templateCache) {
7060
- * $templateCache.put('templateId.html', 'This is the content of the template');
7061
- * });
7062
- * ```
7063
- *
7064
- * To retrieve the template later, simply use it in your component:
7065
- * ```js
7066
- * myApp.component('myComponent', {
7067
- * templateUrl: 'templateId.html'
7068
- * });
7069
- * ```
7070
- *
7071
- * or get it via the `$templateCache` service:
7072
- * ```js
7073
- * $templateCache.get('templateId.html')
7074
- * ```
7075
- *
7076
- * See {@link ng.$cacheFactory $cacheFactory}.
7077
- *
7078
- */
7079
- function $TemplateCacheProvider() {
7080
- this.$get = ['$cacheFactory', function($cacheFactory) {
7081
- return $cacheFactory('templates');
7082
- }];
7083
- }
7084
-
7085
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
7086
- * Any commits to this file should be reviewed with security in mind. *
7087
- * Changes to this file can potentially create security vulnerabilities. *
7088
- * An approval from 2 Core members with history of modifying *
7089
- * this file is required. *
7090
- * *
7091
- * Does the change somehow allow for arbitrary javascript to be executed? *
7092
- * Or allows for someone to change the prototype of built-in objects? *
7093
- * Or gives undesired access to variables like document or window? *
7094
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
7095
-
7096
- /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
7097
- *
7098
- * DOM-related variables:
7099
- *
7100
- * - "node" - DOM Node
7101
- * - "element" - DOM Element or Node
7102
- * - "$node" or "$element" - jqLite-wrapped node or element
7103
- *
7104
- *
7105
- * Compiler related stuff:
7106
- *
7107
- * - "linkFn" - linking fn of a single directive
7108
- * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
7109
- * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
7110
- * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
7111
- */
7112
-
7113
-
7114
- /**
7115
- * @ngdoc service
7116
- * @name $compile
7117
- * @kind function
7118
- *
7119
- * @description
7120
- * Compiles an HTML string or DOM into a template and produces a template function, which
7121
- * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
7122
- *
7123
- * The compilation is a process of walking the DOM tree and matching DOM elements to
7124
- * {@link ng.$compileProvider#directive directives}.
7125
- *
7126
- * <div class="alert alert-warning">
7127
- * **Note:** This document is an in-depth reference of all directive options.
7128
- * For a gentle introduction to directives with examples of common use cases,
7129
- * see the {@link guide/directive directive guide}.
7130
- * </div>
7131
- *
7132
- * ## Comprehensive Directive API
7133
- *
7134
- * There are many different options for a directive.
7135
- *
7136
- * The difference resides in the return value of the factory function.
7137
- * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
7138
- * that defines the directive properties, or just the `postLink` function (all other properties will have
7139
- * the default values).
7140
- *
7141
- * <div class="alert alert-success">
7142
- * **Best Practice:** It's recommended to use the "directive definition object" form.
7143
- * </div>
7144
- *
7145
- * Here's an example directive declared with a Directive Definition Object:
7146
- *
7147
- * ```js
7148
- * var myModule = angular.module(...);
7149
- *
7150
- * myModule.directive('directiveName', function factory(injectables) {
7151
- * var directiveDefinitionObject = {
7152
- * {@link $compile#-priority- priority}: 0,
7153
- * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... },
7154
- * // or
7155
- * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... },
7156
- * {@link $compile#-transclude- transclude}: false,
7157
- * {@link $compile#-restrict- restrict}: 'A',
7158
- * {@link $compile#-templatenamespace- templateNamespace}: 'html',
7159
- * {@link $compile#-scope- scope}: false,
7160
- * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
7161
- * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier',
7162
- * {@link $compile#-bindtocontroller- bindToController}: false,
7163
- * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
7164
- * {@link $compile#-multielement- multiElement}: false,
7165
- * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) {
7166
- * return {
7167
- * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7168
- * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
7169
- * }
7170
- * // or
7171
- * // return function postLink( ... ) { ... }
7172
- * },
7173
- * // or
7174
- * // {@link $compile#-link- link}: {
7175
- * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
7176
- * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
7177
- * // }
7178
- * // or
7179
- * // {@link $compile#-link- link}: function postLink( ... ) { ... }
7180
- * };
7181
- * return directiveDefinitionObject;
7182
- * });
7183
- * ```
7184
- *
7185
- * <div class="alert alert-warning">
7186
- * **Note:** Any unspecified options will use the default value. You can see the default values below.
7187
- * </div>
7188
- *
7189
- * Therefore the above can be simplified as:
7190
- *
7191
- * ```js
7192
- * var myModule = angular.module(...);
7193
- *
7194
- * myModule.directive('directiveName', function factory(injectables) {
7195
- * var directiveDefinitionObject = {
7196
- * link: function postLink(scope, iElement, iAttrs) { ... }
7197
- * };
7198
- * return directiveDefinitionObject;
7199
- * // or
7200
- * // return function postLink(scope, iElement, iAttrs) { ... }
7201
- * });
7202
- * ```
7203
- *
7204
- * ### Life-cycle hooks
7205
- * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the
7206
- * directive:
7207
- * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
7208
- * had their bindings initialized (and before the pre &amp; post linking functions for the directives on
7209
- * this element). This is a good place to put initialization code for your controller.
7210
- * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
7211
- * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
7212
- * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
7213
- * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
7214
- * also be called when your bindings are initialized.
7215
- * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
7216
- * changes. Any actions that you wish to take in response to the changes that you detect must be
7217
- * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
7218
- * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
7219
- * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
7220
- * if detecting changes, you must store the previous value(s) for comparison to the current values.
7221
- * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
7222
- * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
7223
- * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
7224
- * components will have their `$onDestroy()` hook called before child components.
7225
- * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
7226
- * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
7227
- * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
7228
- * they are waiting for their template to load asynchronously and their own compilation and linking has been
7229
- * suspended until that occurs.
7230
- *
7231
- * #### Comparison with Angular 2 life-cycle hooks
7232
- * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are
7233
- * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2:
7234
- *
7235
- * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`.
7236
- * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor.
7237
- * In Angular 2 you can only define hooks on the prototype of the Component class.
7238
- * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to
7239
- * `ngDoCheck` in Angular 2
7240
- * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
7241
- * propagated throughout the application.
7242
- * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
7243
- * error or do nothing depending upon the state of `enableProdMode()`.
7244
- *
7245
- * #### Life-cycle hook examples
7246
- *
7247
- * This example shows how you can check for mutations to a Date object even though the identity of the object
7248
- * has not changed.
7249
- *
7250
- * <example name="doCheckDateExample" module="do-check-module">
7251
- * <file name="app.js">
7252
- * angular.module('do-check-module', [])
7253
- * .component('app', {
7254
- * template:
7255
- * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
7256
- * 'Date: {{ $ctrl.date }}' +
7257
- * '<test date="$ctrl.date"></test>',
7258
- * controller: function() {
7259
- * this.date = new Date();
7260
- * this.month = this.date.getMonth();
7261
- * this.updateDate = function() {
7262
- * this.date.setMonth(this.month);
7263
- * };
7264
- * }
7265
- * })
7266
- * .component('test', {
7267
- * bindings: { date: '<' },
7268
- * template:
7269
- * '<pre>{{ $ctrl.log | json }}</pre>',
7270
- * controller: function() {
7271
- * var previousValue;
7272
- * this.log = [];
7273
- * this.$doCheck = function() {
7274
- * var currentValue = this.date && this.date.valueOf();
7275
- * if (previousValue !== currentValue) {
7276
- * this.log.push('doCheck: date mutated: ' + this.date);
7277
- * previousValue = currentValue;
7278
- * }
7279
- * };
7280
- * }
7281
- * });
7282
- * </file>
7283
- * <file name="index.html">
7284
- * <app></app>
7285
- * </file>
7286
- * </example>
7287
- *
7288
- * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
7289
- * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
7290
- * arrays or objects can have a negative impact on your application performance)
7291
- *
7292
- * <example name="doCheckArrayExample" module="do-check-module">
7293
- * <file name="index.html">
7294
- * <div ng-init="items = []">
7295
- * <button ng-click="items.push(items.length)">Add Item</button>
7296
- * <button ng-click="items = []">Reset Items</button>
7297
- * <pre>{{ items }}</pre>
7298
- * <test items="items"></test>
7299
- * </div>
7300
- * </file>
7301
- * <file name="app.js">
7302
- * angular.module('do-check-module', [])
7303
- * .component('test', {
7304
- * bindings: { items: '<' },
7305
- * template:
7306
- * '<pre>{{ $ctrl.log | json }}</pre>',
7307
- * controller: function() {
7308
- * this.log = [];
7309
- *
7310
- * this.$doCheck = function() {
7311
- * if (this.items_ref !== this.items) {
7312
- * this.log.push('doCheck: items changed');
7313
- * this.items_ref = this.items;
7314
- * }
7315
- * if (!angular.equals(this.items_clone, this.items)) {
7316
- * this.log.push('doCheck: items mutated');
7317
- * this.items_clone = angular.copy(this.items);
7318
- * }
7319
- * };
7320
- * }
7321
- * });
7322
- * </file>
7323
- * </example>
7324
- *
7325
- *
7326
- * ### Directive Definition Object
7327
- *
7328
- * The directive definition object provides instructions to the {@link ng.$compile
7329
- * compiler}. The attributes are:
7330
- *
7331
- * #### `multiElement`
7332
- * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between
7333
- * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
7334
- * together as the directive elements. It is recommended that this feature be used on directives
7335
- * which are not strictly behavioral (such as {@link ngClick}), and which
7336
- * do not manipulate or replace child nodes (such as {@link ngInclude}).
7337
- *
7338
- * #### `priority`
7339
- * When there are multiple directives defined on a single DOM element, sometimes it
7340
- * is necessary to specify the order in which the directives are applied. The `priority` is used
7341
- * to sort the directives before their `compile` functions get called. Priority is defined as a
7342
- * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
7343
- * are also run in priority order, but post-link functions are run in reverse order. The order
7344
- * of directives with the same priority is undefined. The default priority is `0`.
7345
- *
7346
- * #### `terminal`
7347
- * If set to true then the current `priority` will be the last set of directives
7348
- * which will execute (any directives at the current priority will still execute
7349
- * as the order of execution on same `priority` is undefined). Note that expressions
7350
- * and other directives used in the directive's template will also be excluded from execution.
7351
- *
7352
- * #### `scope`
7353
- * The scope property can be `false`, `true`, or an object:
7354
- *
7355
- * * **`false` (default):** No scope will be created for the directive. The directive will use its
7356
- * parent's scope.
7357
- *
7358
- * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
7359
- * the directive's element. If multiple directives on the same element request a new scope,
7360
- * only one new scope is created.
7361
- *
7362
- * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template.
7363
- * The 'isolate' scope differs from normal scope in that it does not prototypically
7364
- * inherit from its parent scope. This is useful when creating reusable components, which should not
7365
- * accidentally read or modify data in the parent scope. Note that an isolate scope
7366
- * directive without a `template` or `templateUrl` will not apply the isolate scope
7367
- * to its children elements.
7368
- *
7369
- * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
7370
- * directive's element. These local properties are useful for aliasing values for templates. The keys in
7371
- * the object hash map to the name of the property on the isolate scope; the values define how the property
7372
- * is bound to the parent scope, via matching attributes on the directive's element:
7373
- *
7374
- * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
7375
- * always a string since DOM attributes are strings. If no `attr` name is specified then the
7376
- * attribute name is assumed to be the same as the local name. Given `<my-component
7377
- * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
7378
- * the directive's scope property `localName` will reflect the interpolated value of `hello
7379
- * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
7380
- * scope. The `name` is read from the parent scope (not the directive's scope).
7381
- *
7382
- * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
7383
- * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
7384
- * If no `attr` name is specified then the attribute name is assumed to be the same as the local
7385
- * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
7386
- * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
7387
- * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
7388
- * `localModel` and vice versa. Optional attributes should be marked as such with a question mark:
7389
- * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't
7390
- * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`})
7391
- * will be thrown upon discovering changes to the local value, since it will be impossible to sync
7392
- * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
7393
- * method is used for tracking changes, and the equality check is based on object identity.
7394
- * However, if an object literal or an array literal is passed as the binding expression, the
7395
- * equality check is done by value (using the {@link angular.equals} function). It's also possible
7396
- * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
7397
- * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
7398
- *
7399
- * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
7400
- * expression passed via the attribute `attr`. The expression is evaluated in the context of the
7401
- * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
7402
- * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
7403
- *
7404
- * For example, given `<my-component my-attr="parentModel">` and directive definition of
7405
- * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
7406
- * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
7407
- * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
7408
- * two caveats:
7409
- * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
7410
- * sets the same value. That means if your bound value is an object, changes to its properties
7411
- * in the isolated scope will be reflected in the parent scope (because both reference the same object).
7412
- * 2. one-way binding watches changes to the **identity** of the parent value. That means the
7413
- * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
7414
- * to the value has changed. In most cases, this should not be of concern, but can be important
7415
- * to know if you one-way bind to an object, and then replace that object in the isolated scope.
7416
- * If you now change a property of the object in your parent scope, the change will not be
7417
- * propagated to the isolated scope, because the identity of the object on the parent scope
7418
- * has not changed. Instead you must assign a new object.
7419
- *
7420
- * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
7421
- * back to the parent. However, it does not make this completely impossible.
7422
- *
7423
- * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
7424
- * no `attr` name is specified then the attribute name is assumed to be the same as the local name.
7425
- * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
7426
- * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
7427
- * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
7428
- * via an expression to the parent scope. This can be done by passing a map of local variable names
7429
- * and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
7430
- * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
7431
- *
7432
- * In general it's possible to apply more than one directive to one element, but there might be limitations
7433
- * depending on the type of scope required by the directives. The following points will help explain these limitations.
7434
- * For simplicity only two directives are taken into account, but it is also applicable for several directives:
7435
- *
7436
- * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
7437
- * * **child scope** + **no scope** => Both directives will share one single child scope
7438
- * * **child scope** + **child scope** => Both directives will share one single child scope
7439
- * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
7440
- * its parent's scope
7441
- * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
7442
- * be applied to the same element.
7443
- * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
7444
- * cannot be applied to the same element.
7445
- *
7446
- *
7447
- * #### `bindToController`
7448
- * This property is used to bind scope properties directly to the controller. It can be either
7449
- * `true` or an object hash with the same format as the `scope` property.
7450
- *
7451
- * When an isolate scope is used for a directive (see above), `bindToController: true` will
7452
- * allow a component to have its properties bound to the controller, rather than to scope.
7453
- *
7454
- * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
7455
- * properties. You can access these bindings once they have been initialized by providing a controller method called
7456
- * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
7457
- * initialized.
7458
- *
7459
- * <div class="alert alert-warning">
7460
- * **Deprecation warning:** if `$compileProcvider.preAssignBindingsEnabled(true)` was called, bindings for non-ES6 class
7461
- * controllers are bound to `this` before the controller constructor is called but this use is now deprecated. Please
7462
- * place initialization code that relies upon bindings inside a `$onInit` method on the controller, instead.
7463
- * </div>
7464
- *
7465
- * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
7466
- * This will set up the scope bindings to the controller directly. Note that `scope` can still be used
7467
- * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
7468
- * scope (useful for component directives).
7469
- *
7470
- * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
7471
- *
7472
- *
7473
- * #### `controller`
7474
- * Controller constructor function. The controller is instantiated before the
7475
- * pre-linking phase and can be accessed by other directives (see
7476
- * `require` attribute). This allows the directives to communicate with each other and augment
7477
- * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
7478
- *
7479
- * * `$scope` - Current scope associated with the element
7480
- * * `$element` - Current element
7481
- * * `$attrs` - Current attributes object for the element
7482
- * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
7483
- * `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
7484
- * * `scope`: (optional) override the scope.
7485
- * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
7486
- * * `futureParentElement` (optional):
7487
- * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
7488
- * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
7489
- * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
7490
- * and when the `cloneLinkingFn` is passed,
7491
- * as those elements need to created and cloned in a special way when they are defined outside their
7492
- * usual containers (e.g. like `<svg>`).
7493
- * * See also the `directive.templateNamespace` property.
7494
- * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
7495
- * then the default transclusion is provided.
7496
- * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
7497
- * `true` if the specified slot contains content (i.e. one or more DOM nodes).
7498
- *
7499
- * #### `require`
7500
- * Require another directive and inject its controller as the fourth argument to the linking function. The
7501
- * `require` property can be a string, an array or an object:
7502
- * * a **string** containing the name of the directive to pass to the linking function
7503
- * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
7504
- * linking function will be an array of controllers in the same order as the names in the `require` property
7505
- * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
7506
- * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
7507
- * controllers.
7508
- *
7509
- * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
7510
- * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
7511
- * have been constructed but before `$onInit` is called.
7512
- * If the name of the required controller is the same as the local name (the key), the name can be
7513
- * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
7514
- * See the {@link $compileProvider#component} helper for an example of how this can be used.
7515
- * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
7516
- * raised (unless no link function is specified and the required controllers are not being bound to the directive
7517
- * controller, in which case error checking is skipped). The name can be prefixed with:
7518
- *
7519
- * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
7520
- * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
7521
- * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
7522
- * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
7523
- * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
7524
- * `null` to the `link` fn if not found.
7525
- * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
7526
- * `null` to the `link` fn if not found.
7527
- *
7528
- *
7529
- * #### `controllerAs`
7530
- * Identifier name for a reference to the controller in the directive's scope.
7531
- * This allows the controller to be referenced from the directive template. This is especially
7532
- * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
7533
- * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
7534
- * `controllerAs` reference might overwrite a property that already exists on the parent scope.
7535
- *
7536
- *
7537
- * #### `restrict`
7538
- * String of subset of `EACM` which restricts the directive to a specific directive
7539
- * declaration style. If omitted, the defaults (elements and attributes) are used.
7540
- *
7541
- * * `E` - Element name (default): `<my-directive></my-directive>`
7542
- * * `A` - Attribute (default): `<div my-directive="exp"></div>`
7543
- * * `C` - Class: `<div class="my-directive: exp;"></div>`
7544
- * * `M` - Comment: `<!-- directive: my-directive exp -->`
7545
- *
7546
- *
7547
- * #### `templateNamespace`
7548
- * String representing the document type used by the markup in the template.
7549
- * AngularJS needs this information as those elements need to be created and cloned
7550
- * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
7551
- *
7552
- * * `html` - All root nodes in the template are HTML. Root nodes may also be
7553
- * top-level elements such as `<svg>` or `<math>`.
7554
- * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
7555
- * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
7556
- *
7557
- * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
7558
- *
7559
- * #### `template`
7560
- * HTML markup that may:
7561
- * * Replace the contents of the directive's element (default).
7562
- * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
7563
- * * Wrap the contents of the directive's element (if `transclude` is true).
7564
- *
7565
- * Value may be:
7566
- *
7567
- * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
7568
- * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
7569
- * function api below) and returns a string value.
7570
- *
7571
- *
7572
- * #### `templateUrl`
7573
- * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
7574
- *
7575
- * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
7576
- * for later when the template has been resolved. In the meantime it will continue to compile and link
7577
- * sibling and parent elements as though this element had not contained any directives.
7578
- *
7579
- * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
7580
- * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
7581
- * case when only one deeply nested directive has `templateUrl`.
7582
- *
7583
- * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
7584
- *
7585
- * You can specify `templateUrl` as a string representing the URL or as a function which takes two
7586
- * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
7587
- * a string value representing the url. In either case, the template URL is passed through {@link
7588
- * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
7589
- *
7590
- *
7591
- * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
7592
- * specify what the template should replace. Defaults to `false`.
7593
- *
7594
- * * `true` - the template will replace the directive's element.
7595
- * * `false` - the template will replace the contents of the directive's element.
7596
- *
7597
- * The replacement process migrates all of the attributes / classes from the old element to the new
7598
- * one. See the {@link guide/directive#template-expanding-directive
7599
- * Directives Guide} for an example.
7600
- *
7601
- * There are very few scenarios where element replacement is required for the application function,
7602
- * the main one being reusable custom components that are used within SVG contexts
7603
- * (because SVG doesn't work with custom elements in the DOM tree).
7604
- *
7605
- * #### `transclude`
7606
- * Extract the contents of the element where the directive appears and make it available to the directive.
7607
- * The contents are compiled and provided to the directive as a **transclusion function**. See the
7608
- * {@link $compile#transclusion Transclusion} section below.
7609
- *
7610
- *
7611
- * #### `compile`
7612
- *
7613
- * ```js
7614
- * function compile(tElement, tAttrs, transclude) { ... }
7615
- * ```
7616
- *
7617
- * The compile function deals with transforming the template DOM. Since most directives do not do
7618
- * template transformation, it is not used often. The compile function takes the following arguments:
7619
- *
7620
- * * `tElement` - template element - The element where the directive has been declared. It is
7621
- * safe to do template transformation on the element and child elements only.
7622
- *
7623
- * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
7624
- * between all directive compile functions.
7625
- *
7626
- * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
7627
- *
7628
- * <div class="alert alert-warning">
7629
- * **Note:** The template instance and the link instance may be different objects if the template has
7630
- * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
7631
- * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
7632
- * should be done in a linking function rather than in a compile function.
7633
- * </div>
7634
-
7635
- * <div class="alert alert-warning">
7636
- * **Note:** The compile function cannot handle directives that recursively use themselves in their
7637
- * own templates or compile functions. Compiling these directives results in an infinite loop and
7638
- * stack overflow errors.
7639
- *
7640
- * This can be avoided by manually using $compile in the postLink function to imperatively compile
7641
- * a directive's template instead of relying on automatic template compilation via `template` or
7642
- * `templateUrl` declaration or manual compilation inside the compile function.
7643
- * </div>
7644
- *
7645
- * <div class="alert alert-danger">
7646
- * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
7647
- * e.g. does not know about the right outer scope. Please use the transclude function that is passed
7648
- * to the link function instead.
7649
- * </div>
7650
-
7651
- * A compile function can have a return value which can be either a function or an object.
7652
- *
7653
- * * returning a (post-link) function - is equivalent to registering the linking function via the
7654
- * `link` property of the config object when the compile function is empty.
7655
- *
7656
- * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
7657
- * control when a linking function should be called during the linking phase. See info about
7658
- * pre-linking and post-linking functions below.
7659
- *
7660
- *
7661
- * #### `link`
7662
- * This property is used only if the `compile` property is not defined.
7663
- *
7664
- * ```js
7665
- * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
7666
- * ```
7667
- *
7668
- * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
7669
- * executed after the template has been cloned. This is where most of the directive logic will be
7670
- * put.
7671
- *
7672
- * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
7673
- * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
7674
- *
7675
- * * `iElement` - instance element - The element where the directive is to be used. It is safe to
7676
- * manipulate the children of the element only in `postLink` function since the children have
7677
- * already been linked.
7678
- *
7679
- * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
7680
- * between all directive linking functions.
7681
- *
7682
- * * `controller` - the directive's required controller instance(s) - Instances are shared
7683
- * among all directives, which allows the directives to use the controllers as a communication
7684
- * channel. The exact value depends on the directive's `require` property:
7685
- * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
7686
- * * `string`: the controller instance
7687
- * * `array`: array of controller instances
7688
- *
7689
- * If a required controller cannot be found, and it is optional, the instance is `null`,
7690
- * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
7691
- *
7692
- * Note that you can also require the directive's own controller - it will be made available like
7693
- * any other controller.
7694
- *
7695
- * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
7696
- * This is the same as the `$transclude` parameter of directive controllers,
7697
- * see {@link ng.$compile#-controller- the controller section for details}.
7698
- * `function([scope], cloneLinkingFn, futureParentElement)`.
7699
- *
7700
- * #### Pre-linking function
7701
- *
7702
- * Executed before the child elements are linked. Not safe to do DOM transformation since the
7703
- * compiler linking function will fail to locate the correct elements for linking.
7704
- *
7705
- * #### Post-linking function
7706
- *
7707
- * Executed after the child elements are linked.
7708
- *
7709
- * Note that child elements that contain `templateUrl` directives will not have been compiled
7710
- * and linked since they are waiting for their template to load asynchronously and their own
7711
- * compilation and linking has been suspended until that occurs.
7712
- *
7713
- * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
7714
- * for their async templates to be resolved.
7715
- *
7716
- *
7717
- * ### Transclusion
7718
- *
7719
- * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
7720
- * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
7721
- * scope from where they were taken.
7722
- *
7723
- * Transclusion is used (often with {@link ngTransclude}) to insert the
7724
- * original contents of a directive's element into a specified place in the template of the directive.
7725
- * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
7726
- * content has access to the properties on the scope from which it was taken, even if the directive
7727
- * has isolated scope.
7728
- * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
7729
- *
7730
- * This makes it possible for the widget to have private state for its template, while the transcluded
7731
- * content has access to its originating scope.
7732
- *
7733
- * <div class="alert alert-warning">
7734
- * **Note:** When testing an element transclude directive you must not place the directive at the root of the
7735
- * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
7736
- * Testing Transclusion Directives}.
7737
- * </div>
7738
- *
7739
- * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
7740
- * directive's element, the entire element or multiple parts of the element contents:
7741
- *
7742
- * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
7743
- * * `'element'` - transclude the whole of the directive's element including any directives on this
7744
- * element that defined at a lower priority than this directive. When used, the `template`
7745
- * property is ignored.
7746
- * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
7747
- *
7748
- * **Mult-slot transclusion** is declared by providing an object for the `transclude` property.
7749
- *
7750
- * This object is a map where the keys are the name of the slot to fill and the value is an element selector
7751
- * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
7752
- * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
7753
- *
7754
- * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7755
- *
7756
- * If the element selector is prefixed with a `?` then that slot is optional.
7757
- *
7758
- * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
7759
- * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
7760
- *
7761
- * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
7762
- * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
7763
- * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
7764
- * injectable into the directive's controller.
7765
- *
7766
- *
7767
- * #### Transclusion Functions
7768
- *
7769
- * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
7770
- * function** to the directive's `link` function and `controller`. This transclusion function is a special
7771
- * **linking function** that will return the compiled contents linked to a new transclusion scope.
7772
- *
7773
- * <div class="alert alert-info">
7774
- * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
7775
- * ngTransclude will deal with it for us.
7776
- * </div>
7777
- *
7778
- * If you want to manually control the insertion and removal of the transcluded content in your directive
7779
- * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
7780
- * object that contains the compiled DOM, which is linked to the correct transclusion scope.
7781
- *
7782
- * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
7783
- * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
7784
- * content and the `scope` is the newly created transclusion scope, which the clone will be linked to.
7785
- *
7786
- * <div class="alert alert-info">
7787
- * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
7788
- * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
7789
- * </div>
7790
- *
7791
- * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
7792
- * attach function**:
7793
- *
7794
- * ```js
7795
- * var transcludedContent, transclusionScope;
7796
- *
7797
- * $transclude(function(clone, scope) {
7798
- * element.append(clone);
7799
- * transcludedContent = clone;
7800
- * transclusionScope = scope;
7801
- * });
7802
- * ```
7803
- *
7804
- * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
7805
- * associated transclusion scope:
7806
- *
7807
- * ```js
7808
- * transcludedContent.remove();
7809
- * transclusionScope.$destroy();
7810
- * ```
7811
- *
7812
- * <div class="alert alert-info">
7813
- * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
7814
- * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
7815
- * then you are also responsible for calling `$destroy` on the transclusion scope.
7816
- * </div>
7817
- *
7818
- * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
7819
- * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
7820
- * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
7821
- *
7822
- *
7823
- * #### Transclusion Scopes
7824
- *
7825
- * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
7826
- * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
7827
- * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
7828
- * was taken.
7829
- *
7830
- * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
7831
- * like this:
7832
- *
7833
- * ```html
7834
- * <div ng-app>
7835
- * <div isolate>
7836
- * <div transclusion>
7837
- * </div>
7838
- * </div>
7839
- * </div>
7840
- * ```
7841
- *
7842
- * The `$parent` scope hierarchy will look like this:
7843
- *
7844
- ```
7845
- - $rootScope
7846
- - isolate
7847
- - transclusion
7848
- ```
7849
- *
7850
- * but the scopes will inherit prototypically from different scopes to their `$parent`.
7851
- *
7852
- ```
7853
- - $rootScope
7854
- - transclusion
7855
- - isolate
7856
- ```
7857
- *
7858
- *
7859
- * ### Attributes
7860
- *
7861
- * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
7862
- * `link()` or `compile()` functions. It has a variety of uses.
7863
- *
7864
- * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways:
7865
- * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access
7866
- * to the attributes.
7867
- *
7868
- * * *Directive inter-communication:* All directives share the same instance of the attributes
7869
- * object which allows the directives to use the attributes object as inter directive
7870
- * communication.
7871
- *
7872
- * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
7873
- * allowing other directives to read the interpolated value.
7874
- *
7875
- * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
7876
- * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
7877
- * the only way to easily get the actual value because during the linking phase the interpolation
7878
- * hasn't been evaluated yet and so the value is at this time set to `undefined`.
7879
- *
7880
- * ```js
7881
- * function linkingFn(scope, elm, attrs, ctrl) {
7882
- * // get the attribute value
7883
- * console.log(attrs.ngModel);
7884
- *
7885
- * // change the attribute
7886
- * attrs.$set('ngModel', 'new value');
7887
- *
7888
- * // observe changes to interpolated attribute
7889
- * attrs.$observe('ngModel', function(value) {
7890
- * console.log('ngModel has changed value to ' + value);
7891
- * });
7892
- * }
7893
- * ```
7894
- *
7895
- * ## Example
7896
- *
7897
- * <div class="alert alert-warning">
7898
- * **Note**: Typically directives are registered with `module.directive`. The example below is
7899
- * to illustrate how `$compile` works.
7900
- * </div>
7901
- *
7902
- <example module="compileExample" name="compile">
7903
- <file name="index.html">
7904
- <script>
7905
- angular.module('compileExample', [], function($compileProvider) {
7906
- // configure new 'compile' directive by passing a directive
7907
- // factory function. The factory function injects the '$compile'
7908
- $compileProvider.directive('compile', function($compile) {
7909
- // directive factory creates a link function
7910
- return function(scope, element, attrs) {
7911
- scope.$watch(
7912
- function(scope) {
7913
- // watch the 'compile' expression for changes
7914
- return scope.$eval(attrs.compile);
7915
- },
7916
- function(value) {
7917
- // when the 'compile' expression changes
7918
- // assign it into the current DOM
7919
- element.html(value);
7920
-
7921
- // compile the new DOM and link it to the current
7922
- // scope.
7923
- // NOTE: we only compile .childNodes so that
7924
- // we don't get into infinite loop compiling ourselves
7925
- $compile(element.contents())(scope);
7926
- }
7927
- );
7928
- };
7929
- });
7930
- })
7931
- .controller('GreeterController', ['$scope', function($scope) {
7932
- $scope.name = 'Angular';
7933
- $scope.html = 'Hello {{name}}';
7934
- }]);
7935
- </script>
7936
- <div ng-controller="GreeterController">
7937
- <input ng-model="name"> <br/>
7938
- <textarea ng-model="html"></textarea> <br/>
7939
- <div compile="html"></div>
7940
- </div>
7941
- </file>
7942
- <file name="protractor.js" type="protractor">
7943
- it('should auto compile', function() {
7944
- var textarea = $('textarea');
7945
- var output = $('div[compile]');
7946
- // The initial state reads 'Hello Angular'.
7947
- expect(output.getText()).toBe('Hello Angular');
7948
- textarea.clear();
7949
- textarea.sendKeys('{{name}}!');
7950
- expect(output.getText()).toBe('Angular!');
7951
- });
7952
- </file>
7953
- </example>
7954
-
7955
- *
7956
- *
7957
- * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7958
- * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7959
- *
7960
- * <div class="alert alert-danger">
7961
- * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7962
- * e.g. will not use the right outer scope. Please pass the transclude function as a
7963
- * `parentBoundTranscludeFn` to the link function instead.
7964
- * </div>
7965
- *
7966
- * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7967
- * root element(s), not their children)
7968
- * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7969
- * (a DOM element/tree) to a scope. Where:
7970
- *
7971
- * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7972
- * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7973
- * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7974
- * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7975
- * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7976
- *
7977
- * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7978
- * * `scope` - is the current scope with which the linking function is working with.
7979
- *
7980
- * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7981
- * keys may be used to control linking behavior:
7982
- *
7983
- * * `parentBoundTranscludeFn` - the transclude function made available to
7984
- * directives; if given, it will be passed through to the link functions of
7985
- * directives found in `element` during compilation.
7986
- * * `transcludeControllers` - an object hash with keys that map controller names
7987
- * to a hash with the key `instance`, which maps to the controller instance;
7988
- * if given, it will make the controllers available to directives on the compileNode:
7989
- * ```
7990
- * {
7991
- * parent: {
7992
- * instance: parentControllerInstance
7993
- * }
7994
- * }
7995
- * ```
7996
- * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7997
- * the cloned elements; only needed for transcludes that are allowed to contain non html
7998
- * elements (e.g. SVG elements). See also the directive.controller property.
7999
- *
8000
- * Calling the linking function returns the element of the template. It is either the original
8001
- * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
8002
- *
8003
- * After linking the view is not updated until after a call to $digest which typically is done by
8004
- * Angular automatically.
8005
- *
8006
- * If you need access to the bound view, there are two ways to do it:
8007
- *
8008
- * - If you are not asking the linking function to clone the template, create the DOM element(s)
8009
- * before you send them to the compiler and keep this reference around.
8010
- * ```js
8011
- * var element = $compile('<p>{{total}}</p>')(scope);
8012
- * ```
8013
- *
8014
- * - if on the other hand, you need the element to be cloned, the view reference from the original
8015
- * example would not point to the clone, but rather to the original template that was cloned. In
8016
- * this case, you can access the clone via the cloneAttachFn:
8017
- * ```js
8018
- * var templateElement = angular.element('<p>{{total}}</p>'),
8019
- * scope = ....;
8020
- *
8021
- * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
8022
- * //attach the clone to DOM document at the right place
8023
- * });
8024
- *
8025
- * //now we have reference to the cloned DOM via `clonedElement`
8026
- * ```
8027
- *
8028
- *
8029
- * For information on how the compiler works, see the
8030
- * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
8031
- *
8032
- * @knownIssue
8033
- *
8034
- * ### Double Compilation
8035
- *
8036
- Double compilation occurs when an already compiled part of the DOM gets
8037
- compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
8038
- and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
8039
- section on double compilation} for an in-depth explanation and ways to avoid it.
8040
- *
8041
- */
8042
-
8043
- var $compileMinErr = minErr('$compile');
8044
-
8045
- function UNINITIALIZED_VALUE() {}
8046
- var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
8047
-
8048
- /**
8049
- * @ngdoc provider
8050
- * @name $compileProvider
8051
- *
8052
- * @description
8053
- */
8054
- $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
8055
- /** @this */
8056
- function $CompileProvider($provide, $$sanitizeUriProvider) {
8057
- var hasDirectives = {},
8058
- Suffix = 'Directive',
8059
- COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/,
8060
- CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/,
8061
- ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
8062
- REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
8063
-
8064
- // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
8065
- // The assumption is that future DOM event attribute names will begin with
8066
- // 'on' and be composed of only English letters.
8067
- var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
8068
- var bindingCache = createMap();
8069
-
8070
- function parseIsolateBindings(scope, directiveName, isController) {
8071
- var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/;
8072
-
8073
- var bindings = createMap();
8074
-
8075
- forEach(scope, function(definition, scopeName) {
8076
- if (definition in bindingCache) {
8077
- bindings[scopeName] = bindingCache[definition];
8078
- return;
8079
- }
8080
- var match = definition.match(LOCAL_REGEXP);
8081
-
8082
- if (!match) {
8083
- throw $compileMinErr('iscp',
8084
- 'Invalid {3} for directive \'{0}\'.' +
8085
- ' Definition: {... {1}: \'{2}\' ...}',
8086
- directiveName, scopeName, definition,
8087
- (isController ? 'controller bindings definition' :
8088
- 'isolate scope definition'));
8089
- }
8090
-
8091
- bindings[scopeName] = {
8092
- mode: match[1][0],
8093
- collection: match[2] === '*',
8094
- optional: match[3] === '?',
8095
- attrName: match[4] || scopeName
8096
- };
8097
- if (match[4]) {
8098
- bindingCache[definition] = bindings[scopeName];
8099
- }
8100
- });
8101
-
8102
- return bindings;
8103
- }
8104
-
8105
- function parseDirectiveBindings(directive, directiveName) {
8106
- var bindings = {
8107
- isolateScope: null,
8108
- bindToController: null
8109
- };
8110
- if (isObject(directive.scope)) {
8111
- if (directive.bindToController === true) {
8112
- bindings.bindToController = parseIsolateBindings(directive.scope,
8113
- directiveName, true);
8114
- bindings.isolateScope = {};
8115
- } else {
8116
- bindings.isolateScope = parseIsolateBindings(directive.scope,
8117
- directiveName, false);
8118
- }
8119
- }
8120
- if (isObject(directive.bindToController)) {
8121
- bindings.bindToController =
8122
- parseIsolateBindings(directive.bindToController, directiveName, true);
8123
- }
8124
- if (bindings.bindToController && !directive.controller) {
8125
- // There is no controller
8126
- throw $compileMinErr('noctrl',
8127
- 'Cannot bind to controller without directive \'{0}\'s controller.',
8128
- directiveName);
8129
- }
8130
- return bindings;
8131
- }
8132
-
8133
- function assertValidDirectiveName(name) {
8134
- var letter = name.charAt(0);
8135
- if (!letter || letter !== lowercase(letter)) {
8136
- throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name);
8137
- }
8138
- if (name !== name.trim()) {
8139
- throw $compileMinErr('baddir',
8140
- 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces',
8141
- name);
8142
- }
8143
- }
8144
-
8145
- function getDirectiveRequire(directive) {
8146
- var require = directive.require || (directive.controller && directive.name);
8147
-
8148
- if (!isArray(require) && isObject(require)) {
8149
- forEach(require, function(value, key) {
8150
- var match = value.match(REQUIRE_PREFIX_REGEXP);
8151
- var name = value.substring(match[0].length);
8152
- if (!name) require[key] = match[0] + key;
8153
- });
8154
- }
8155
-
8156
- return require;
8157
- }
8158
-
8159
- function getDirectiveRestrict(restrict, name) {
8160
- if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) {
8161
- throw $compileMinErr('badrestrict',
8162
- 'Restrict property \'{0}\' of directive \'{1}\' is invalid',
8163
- restrict,
8164
- name);
8165
- }
8166
-
8167
- return restrict || 'EA';
8168
- }
8169
-
8170
- /**
8171
- * @ngdoc method
8172
- * @name $compileProvider#directive
8173
- * @kind function
8174
- *
8175
- * @description
8176
- * Register a new directive with the compiler.
8177
- *
8178
- * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
8179
- * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
8180
- * names and the values are the factories.
8181
- * @param {Function|Array} directiveFactory An injectable directive factory function. See the
8182
- * {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
8183
- * @returns {ng.$compileProvider} Self for chaining.
8184
- */
8185
- this.directive = function registerDirective(name, directiveFactory) {
8186
- assertArg(name, 'name');
8187
- assertNotHasOwnProperty(name, 'directive');
8188
- if (isString(name)) {
8189
- assertValidDirectiveName(name);
8190
- assertArg(directiveFactory, 'directiveFactory');
8191
- if (!hasDirectives.hasOwnProperty(name)) {
8192
- hasDirectives[name] = [];
8193
- $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
8194
- function($injector, $exceptionHandler) {
8195
- var directives = [];
8196
- forEach(hasDirectives[name], function(directiveFactory, index) {
8197
- try {
8198
- var directive = $injector.invoke(directiveFactory);
8199
- if (isFunction(directive)) {
8200
- directive = { compile: valueFn(directive) };
8201
- } else if (!directive.compile && directive.link) {
8202
- directive.compile = valueFn(directive.link);
8203
- }
8204
- directive.priority = directive.priority || 0;
8205
- directive.index = index;
8206
- directive.name = directive.name || name;
8207
- directive.require = getDirectiveRequire(directive);
8208
- directive.restrict = getDirectiveRestrict(directive.restrict, name);
8209
- directive.$$moduleName = directiveFactory.$$moduleName;
8210
- directives.push(directive);
8211
- } catch (e) {
8212
- $exceptionHandler(e);
8213
- }
8214
- });
8215
- return directives;
8216
- }]);
8217
- }
8218
- hasDirectives[name].push(directiveFactory);
8219
- } else {
8220
- forEach(name, reverseParams(registerDirective));
8221
- }
8222
- return this;
8223
- };
8224
-
8225
- /**
8226
- * @ngdoc method
8227
- * @name $compileProvider#component
8228
- * @module ng
8229
- * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
8230
- * or an object map of components where the keys are the names and the values are the component definition objects.
8231
- * @param {Object} options Component definition object (a simplified
8232
- * {@link ng.$compile#directive-definition-object directive definition object}),
8233
- * with the following properties (all optional):
8234
- *
8235
- * - `controller` – `{(string|function()=}` – controller constructor function that should be
8236
- * associated with newly created scope or the name of a {@link ng.$compile#-controller-
8237
- * registered controller} if passed as a string. An empty `noop` function by default.
8238
- * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
8239
- * If present, the controller will be published to scope under the `controllerAs` name.
8240
- * If not present, this will default to be `$ctrl`.
8241
- * - `template` – `{string=|function()=}` – html template as a string or a function that
8242
- * returns an html template as a string which should be used as the contents of this component.
8243
- * Empty string by default.
8244
- *
8245
- * If `template` is a function, then it is {@link auto.$injector#invoke injected} with
8246
- * the following locals:
8247
- *
8248
- * - `$element` - Current element
8249
- * - `$attrs` - Current attributes object for the element
8250
- *
8251
- * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
8252
- * template that should be used as the contents of this component.
8253
- *
8254
- * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
8255
- * the following locals:
8256
- *
8257
- * - `$element` - Current element
8258
- * - `$attrs` - Current attributes object for the element
8259
- *
8260
- * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
8261
- * Component properties are always bound to the component controller and not to the scope.
8262
- * See {@link ng.$compile#-bindtocontroller- `bindToController`}.
8263
- * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
8264
- * Disabled by default.
8265
- * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to
8266
- * this component's controller. The object keys specify the property names under which the required
8267
- * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}.
8268
- * - `$...` – additional properties to attach to the directive factory function and the controller
8269
- * constructor function. (This is used by the component router to annotate)
8270
- *
8271
- * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
8272
- * @description
8273
- * Register a **component definition** with the compiler. This is a shorthand for registering a special
8274
- * type of directive, which represents a self-contained UI component in your application. Such components
8275
- * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
8276
- *
8277
- * Component definitions are very simple and do not require as much configuration as defining general
8278
- * directives. Component definitions usually consist only of a template and a controller backing it.
8279
- *
8280
- * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
8281
- * `bindToController`. They always have **isolate scope** and are restricted to elements.
8282
- *
8283
- * Here are a few examples of how you would usually define components:
8284
- *
8285
- * ```js
8286
- * var myMod = angular.module(...);
8287
- * myMod.component('myComp', {
8288
- * template: '<div>My name is {{$ctrl.name}}</div>',
8289
- * controller: function() {
8290
- * this.name = 'shahar';
8291
- * }
8292
- * });
8293
- *
8294
- * myMod.component('myComp', {
8295
- * template: '<div>My name is {{$ctrl.name}}</div>',
8296
- * bindings: {name: '@'}
8297
- * });
8298
- *
8299
- * myMod.component('myComp', {
8300
- * templateUrl: 'views/my-comp.html',
8301
- * controller: 'MyCtrl',
8302
- * controllerAs: 'ctrl',
8303
- * bindings: {name: '@'}
8304
- * });
8305
- *
8306
- * ```
8307
- * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
8308
- *
8309
- * <br />
8310
- * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
8311
- */
8312
- this.component = function registerComponent(name, options) {
8313
- if (!isString(name)) {
8314
- forEach(name, reverseParams(bind(this, registerComponent)));
8315
- return this;
8316
- }
8317
-
8318
- var controller = options.controller || function() {};
8319
-
8320
- function factory($injector) {
8321
- function makeInjectable(fn) {
8322
- if (isFunction(fn) || isArray(fn)) {
8323
- return /** @this */ function(tElement, tAttrs) {
8324
- return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
8325
- };
8326
- } else {
8327
- return fn;
8328
- }
8329
- }
8330
-
8331
- var template = (!options.template && !options.templateUrl ? '' : options.template);
8332
- var ddo = {
8333
- controller: controller,
8334
- controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
8335
- template: makeInjectable(template),
8336
- templateUrl: makeInjectable(options.templateUrl),
8337
- transclude: options.transclude,
8338
- scope: {},
8339
- bindToController: options.bindings || {},
8340
- restrict: 'E',
8341
- require: options.require
8342
- };
8343
-
8344
- // Copy annotations (starting with $) over to the DDO
8345
- forEach(options, function(val, key) {
8346
- if (key.charAt(0) === '$') ddo[key] = val;
8347
- });
8348
-
8349
- return ddo;
8350
- }
8351
-
8352
- // TODO(pete) remove the following `forEach` before we release 1.6.0
8353
- // The component-router@0.2.0 looks for the annotations on the controller constructor
8354
- // Nothing in Angular looks for annotations on the factory function but we can't remove
8355
- // it from 1.5.x yet.
8356
-
8357
- // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
8358
- // These could be used by libraries such as the new component router
8359
- forEach(options, function(val, key) {
8360
- if (key.charAt(0) === '$') {
8361
- factory[key] = val;
8362
- // Don't try to copy over annotations to named controller
8363
- if (isFunction(controller)) controller[key] = val;
8364
- }
8365
- });
8366
-
8367
- factory.$inject = ['$injector'];
8368
-
8369
- return this.directive(name, factory);
8370
- };
8371
-
8372
-
8373
- /**
8374
- * @ngdoc method
8375
- * @name $compileProvider#aHrefSanitizationWhitelist
8376
- * @kind function
8377
- *
8378
- * @description
8379
- * Retrieves or overrides the default regular expression that is used for whitelisting of safe
8380
- * urls during a[href] sanitization.
8381
- *
8382
- * The sanitization is a security measure aimed at preventing XSS attacks via html links.
8383
- *
8384
- * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
8385
- * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
8386
- * regular expression. If a match is found, the original url is written into the dom. Otherwise,
8387
- * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
8388
- *
8389
- * @param {RegExp=} regexp New regexp to whitelist urls with.
8390
- * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
8391
- * chaining otherwise.
8392
- */
8393
- this.aHrefSanitizationWhitelist = function(regexp) {
8394
- if (isDefined(regexp)) {
8395
- $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
8396
- return this;
8397
- } else {
8398
- return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
8399
- }
8400
- };
8401
-
8402
-
8403
- /**
8404
- * @ngdoc method
8405
- * @name $compileProvider#imgSrcSanitizationWhitelist
8406
- * @kind function
8407
- *
8408
- * @description
8409
- * Retrieves or overrides the default regular expression that is used for whitelisting of safe
8410
- * urls during img[src] sanitization.
8411
- *
8412
- * The sanitization is a security measure aimed at prevent XSS attacks via html links.
8413
- *
8414
- * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
8415
- * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
8416
- * regular expression. If a match is found, the original url is written into the dom. Otherwise,
8417
- * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
8418
- *
8419
- * @param {RegExp=} regexp New regexp to whitelist urls with.
8420
- * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
8421
- * chaining otherwise.
8422
- */
8423
- this.imgSrcSanitizationWhitelist = function(regexp) {
8424
- if (isDefined(regexp)) {
8425
- $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
8426
- return this;
8427
- } else {
8428
- return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
8429
- }
8430
- };
8431
-
8432
- /**
8433
- * @ngdoc method
8434
- * @name $compileProvider#debugInfoEnabled
8435
- *
8436
- * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
8437
- * current debugInfoEnabled state
8438
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
8439
- *
8440
- * @kind function
8441
- *
8442
- * @description
8443
- * Call this method to enable/disable various debug runtime information in the compiler such as adding
8444
- * binding information and a reference to the current scope on to DOM elements.
8445
- * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
8446
- * * `ng-binding` CSS class
8447
- * * `$binding` data property containing an array of the binding expressions
8448
- *
8449
- * You may want to disable this in production for a significant performance boost. See
8450
- * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
8451
- *
8452
- * The default value is true.
8453
- */
8454
- var debugInfoEnabled = true;
8455
- this.debugInfoEnabled = function(enabled) {
8456
- if (isDefined(enabled)) {
8457
- debugInfoEnabled = enabled;
8458
- return this;
8459
- }
8460
- return debugInfoEnabled;
8461
- };
8462
-
8463
- /**
8464
- * @ngdoc method
8465
- * @name $compileProvider#preAssignBindingsEnabled
8466
- *
8467
- * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the
8468
- * current preAssignBindingsEnabled state
8469
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
8470
- *
8471
- * @kind function
8472
- *
8473
- * @description
8474
- * Call this method to enable/disable whether directive controllers are assigned bindings before
8475
- * calling the controller's constructor.
8476
- * If enabled (true), the compiler assigns the value of each of the bindings to the
8477
- * properties of the controller object before the constructor of this object is called.
8478
- *
8479
- * If disabled (false), the compiler calls the constructor first before assigning bindings.
8480
- *
8481
- * The default value is false.
8482
- *
8483
- * @deprecated
8484
- * sinceVersion="1.6.0"
8485
- * removeVersion="1.7.0"
8486
- *
8487
- * This method and the option to assign the bindings before calling the controller's constructor
8488
- * will be removed in v1.7.0.
8489
- */
8490
- var preAssignBindingsEnabled = false;
8491
- this.preAssignBindingsEnabled = function(enabled) {
8492
- if (isDefined(enabled)) {
8493
- preAssignBindingsEnabled = enabled;
8494
- return this;
8495
- }
8496
- return preAssignBindingsEnabled;
8497
- };
8498
-
8499
-
8500
- var TTL = 10;
8501
- /**
8502
- * @ngdoc method
8503
- * @name $compileProvider#onChangesTtl
8504
- * @description
8505
- *
8506
- * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
8507
- * assuming that the model is unstable.
8508
- *
8509
- * The current default is 10 iterations.
8510
- *
8511
- * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
8512
- * in several iterations of calls to these hooks. However if an application needs more than the default 10
8513
- * iterations to stabilize then you should investigate what is causing the model to continuously change during
8514
- * the `$onChanges` hook execution.
8515
- *
8516
- * Increasing the TTL could have performance implications, so you should not change it without proper justification.
8517
- *
8518
- * @param {number} limit The number of `$onChanges` hook iterations.
8519
- * @returns {number|object} the current limit (or `this` if called as a setter for chaining)
8520
- */
8521
- this.onChangesTtl = function(value) {
8522
- if (arguments.length) {
8523
- TTL = value;
8524
- return this;
8525
- }
8526
- return TTL;
8527
- };
8528
-
8529
- var commentDirectivesEnabledConfig = true;
8530
- /**
8531
- * @ngdoc method
8532
- * @name $compileProvider#commentDirectivesEnabled
8533
- * @description
8534
- *
8535
- * It indicates to the compiler
8536
- * whether or not directives on comments should be compiled.
8537
- * Defaults to `true`.
8538
- *
8539
- * Calling this function with false disables the compilation of directives
8540
- * on comments for the whole application.
8541
- * This results in a compilation performance gain,
8542
- * as the compiler doesn't have to check comments when looking for directives.
8543
- * This should however only be used if you are sure that no comment directives are used in
8544
- * the application (including any 3rd party directives).
8545
- *
8546
- * @param {boolean} enabled `false` if the compiler may ignore directives on comments
8547
- * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
8548
- */
8549
- this.commentDirectivesEnabled = function(value) {
8550
- if (arguments.length) {
8551
- commentDirectivesEnabledConfig = value;
8552
- return this;
8553
- }
8554
- return commentDirectivesEnabledConfig;
8555
- };
8556
-
8557
-
8558
- var cssClassDirectivesEnabledConfig = true;
8559
- /**
8560
- * @ngdoc method
8561
- * @name $compileProvider#cssClassDirectivesEnabled
8562
- * @description
8563
- *
8564
- * It indicates to the compiler
8565
- * whether or not directives on element classes should be compiled.
8566
- * Defaults to `true`.
8567
- *
8568
- * Calling this function with false disables the compilation of directives
8569
- * on element classes for the whole application.
8570
- * This results in a compilation performance gain,
8571
- * as the compiler doesn't have to check element classes when looking for directives.
8572
- * This should however only be used if you are sure that no class directives are used in
8573
- * the application (including any 3rd party directives).
8574
- *
8575
- * @param {boolean} enabled `false` if the compiler may ignore directives on element classes
8576
- * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
8577
- */
8578
- this.cssClassDirectivesEnabled = function(value) {
8579
- if (arguments.length) {
8580
- cssClassDirectivesEnabledConfig = value;
8581
- return this;
8582
- }
8583
- return cssClassDirectivesEnabledConfig;
8584
- };
8585
-
8586
- this.$get = [
8587
- '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
8588
- '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
8589
- function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
8590
- $controller, $rootScope, $sce, $animate, $$sanitizeUri) {
8591
-
8592
- var SIMPLE_ATTR_NAME = /^\w/;
8593
- var specialAttrHolder = window.document.createElement('div');
8594
-
8595
-
8596
- var commentDirectivesEnabled = commentDirectivesEnabledConfig;
8597
- var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig;
8598
-
8599
-
8600
- var onChangesTtl = TTL;
8601
- // The onChanges hooks should all be run together in a single digest
8602
- // When changes occur, the call to trigger their hooks will be added to this queue
8603
- var onChangesQueue;
8604
-
8605
- // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
8606
- function flushOnChangesQueue() {
8607
- try {
8608
- if (!(--onChangesTtl)) {
8609
- // We have hit the TTL limit so reset everything
8610
- onChangesQueue = undefined;
8611
- throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
8612
- }
8613
- // We must run this hook in an apply since the $$postDigest runs outside apply
8614
- $rootScope.$apply(function() {
8615
- var errors = [];
8616
- for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
8617
- try {
8618
- onChangesQueue[i]();
8619
- } catch (e) {
8620
- errors.push(e);
8621
- }
8622
- }
8623
- // Reset the queue to trigger a new schedule next time there is a change
8624
- onChangesQueue = undefined;
8625
- if (errors.length) {
8626
- throw errors;
8627
- }
8628
- });
8629
- } finally {
8630
- onChangesTtl++;
8631
- }
8632
- }
8633
-
8634
-
8635
- function Attributes(element, attributesToCopy) {
8636
- if (attributesToCopy) {
8637
- var keys = Object.keys(attributesToCopy);
8638
- var i, l, key;
8639
-
8640
- for (i = 0, l = keys.length; i < l; i++) {
8641
- key = keys[i];
8642
- this[key] = attributesToCopy[key];
8643
- }
8644
- } else {
8645
- this.$attr = {};
8646
- }
8647
-
8648
- this.$$element = element;
8649
- }
8650
-
8651
- Attributes.prototype = {
8652
- /**
8653
- * @ngdoc method
8654
- * @name $compile.directive.Attributes#$normalize
8655
- * @kind function
8656
- *
8657
- * @description
8658
- * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
8659
- * `data-`) to its normalized, camelCase form.
8660
- *
8661
- * Also there is special case for Moz prefix starting with upper case letter.
8662
- *
8663
- * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
8664
- *
8665
- * @param {string} name Name to normalize
8666
- */
8667
- $normalize: directiveNormalize,
8668
-
8669
-
8670
- /**
8671
- * @ngdoc method
8672
- * @name $compile.directive.Attributes#$addClass
8673
- * @kind function
8674
- *
8675
- * @description
8676
- * Adds the CSS class value specified by the classVal parameter to the element. If animations
8677
- * are enabled then an animation will be triggered for the class addition.
8678
- *
8679
- * @param {string} classVal The className value that will be added to the element
8680
- */
8681
- $addClass: function(classVal) {
8682
- if (classVal && classVal.length > 0) {
8683
- $animate.addClass(this.$$element, classVal);
8684
- }
8685
- },
8686
-
8687
- /**
8688
- * @ngdoc method
8689
- * @name $compile.directive.Attributes#$removeClass
8690
- * @kind function
8691
- *
8692
- * @description
8693
- * Removes the CSS class value specified by the classVal parameter from the element. If
8694
- * animations are enabled then an animation will be triggered for the class removal.
8695
- *
8696
- * @param {string} classVal The className value that will be removed from the element
8697
- */
8698
- $removeClass: function(classVal) {
8699
- if (classVal && classVal.length > 0) {
8700
- $animate.removeClass(this.$$element, classVal);
8701
- }
8702
- },
8703
-
8704
- /**
8705
- * @ngdoc method
8706
- * @name $compile.directive.Attributes#$updateClass
8707
- * @kind function
8708
- *
8709
- * @description
8710
- * Adds and removes the appropriate CSS class values to the element based on the difference
8711
- * between the new and old CSS class values (specified as newClasses and oldClasses).
8712
- *
8713
- * @param {string} newClasses The current CSS className value
8714
- * @param {string} oldClasses The former CSS className value
8715
- */
8716
- $updateClass: function(newClasses, oldClasses) {
8717
- var toAdd = tokenDifference(newClasses, oldClasses);
8718
- if (toAdd && toAdd.length) {
8719
- $animate.addClass(this.$$element, toAdd);
8720
- }
8721
-
8722
- var toRemove = tokenDifference(oldClasses, newClasses);
8723
- if (toRemove && toRemove.length) {
8724
- $animate.removeClass(this.$$element, toRemove);
8725
- }
8726
- },
8727
-
8728
- /**
8729
- * Set a normalized attribute on the element in a way such that all directives
8730
- * can share the attribute. This function properly handles boolean attributes.
8731
- * @param {string} key Normalized key. (ie ngAttribute)
8732
- * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
8733
- * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
8734
- * Defaults to true.
8735
- * @param {string=} attrName Optional none normalized name. Defaults to key.
8736
- */
8737
- $set: function(key, value, writeAttr, attrName) {
8738
- // TODO: decide whether or not to throw an error if "class"
8739
- //is set through this function since it may cause $updateClass to
8740
- //become unstable.
8741
-
8742
- var node = this.$$element[0],
8743
- booleanKey = getBooleanAttrName(node, key),
8744
- aliasedKey = getAliasedAttrName(key),
8745
- observer = key,
8746
- nodeName;
8747
-
8748
- if (booleanKey) {
8749
- this.$$element.prop(key, value);
8750
- attrName = booleanKey;
8751
- } else if (aliasedKey) {
8752
- this[aliasedKey] = value;
8753
- observer = aliasedKey;
8754
- }
8755
-
8756
- this[key] = value;
8757
-
8758
- // translate normalized key to actual key
8759
- if (attrName) {
8760
- this.$attr[key] = attrName;
8761
- } else {
8762
- attrName = this.$attr[key];
8763
- if (!attrName) {
8764
- this.$attr[key] = attrName = snake_case(key, '-');
8765
- }
8766
- }
8767
-
8768
- nodeName = nodeName_(this.$$element);
8769
-
8770
- if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
8771
- (nodeName === 'img' && key === 'src')) {
8772
- // sanitize a[href] and img[src] values
8773
- this[key] = value = $$sanitizeUri(value, key === 'src');
8774
- } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
8775
- // sanitize img[srcset] values
8776
- var result = '';
8777
-
8778
- // first check if there are spaces because it's not the same pattern
8779
- var trimmedSrcset = trim(value);
8780
- // ( 999x ,| 999w ,| ,|, )
8781
- var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
8782
- var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
8783
-
8784
- // split srcset into tuple of uri and descriptor except for the last item
8785
- var rawUris = trimmedSrcset.split(pattern);
8786
-
8787
- // for each tuples
8788
- var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
8789
- for (var i = 0; i < nbrUrisWith2parts; i++) {
8790
- var innerIdx = i * 2;
8791
- // sanitize the uri
8792
- result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
8793
- // add the descriptor
8794
- result += (' ' + trim(rawUris[innerIdx + 1]));
8795
- }
8796
-
8797
- // split the last item into uri and descriptor
8798
- var lastTuple = trim(rawUris[i * 2]).split(/\s/);
8799
-
8800
- // sanitize the last uri
8801
- result += $$sanitizeUri(trim(lastTuple[0]), true);
8802
-
8803
- // and add the last descriptor if any
8804
- if (lastTuple.length === 2) {
8805
- result += (' ' + trim(lastTuple[1]));
8806
- }
8807
- this[key] = value = result;
8808
- }
8809
-
8810
- if (writeAttr !== false) {
8811
- if (value === null || isUndefined(value)) {
8812
- this.$$element.removeAttr(attrName);
8813
- } else {
8814
- if (SIMPLE_ATTR_NAME.test(attrName)) {
8815
- this.$$element.attr(attrName, value);
8816
- } else {
8817
- setSpecialAttr(this.$$element[0], attrName, value);
8818
- }
8819
- }
8820
- }
8821
-
8822
- // fire observers
8823
- var $$observers = this.$$observers;
8824
- if ($$observers) {
8825
- forEach($$observers[observer], function(fn) {
8826
- try {
8827
- fn(value);
8828
- } catch (e) {
8829
- $exceptionHandler(e);
8830
- }
8831
- });
8832
- }
8833
- },
8834
-
8835
-
8836
- /**
8837
- * @ngdoc method
8838
- * @name $compile.directive.Attributes#$observe
8839
- * @kind function
8840
- *
8841
- * @description
8842
- * Observes an interpolated attribute.
8843
- *
8844
- * The observer function will be invoked once during the next `$digest` following
8845
- * compilation. The observer is then invoked whenever the interpolated value
8846
- * changes.
8847
- *
8848
- * @param {string} key Normalized key. (ie ngAttribute) .
8849
- * @param {function(interpolatedValue)} fn Function that will be called whenever
8850
- the interpolated value of the attribute changes.
8851
- * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
8852
- * guide} for more info.
8853
- * @returns {function()} Returns a deregistration function for this observer.
8854
- */
8855
- $observe: function(key, fn) {
8856
- var attrs = this,
8857
- $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
8858
- listeners = ($$observers[key] || ($$observers[key] = []));
8859
-
8860
- listeners.push(fn);
8861
- $rootScope.$evalAsync(function() {
8862
- if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
8863
- // no one registered attribute interpolation function, so lets call it manually
8864
- fn(attrs[key]);
8865
- }
8866
- });
8867
-
8868
- return function() {
8869
- arrayRemove(listeners, fn);
8870
- };
8871
- }
8872
- };
8873
-
8874
- function setSpecialAttr(element, attrName, value) {
8875
- // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
8876
- // so we have to jump through some hoops to get such an attribute
8877
- // https://github.com/angular/angular.js/pull/13318
8878
- specialAttrHolder.innerHTML = '<span ' + attrName + '>';
8879
- var attributes = specialAttrHolder.firstChild.attributes;
8880
- var attribute = attributes[0];
8881
- // We have to remove the attribute from its container element before we can add it to the destination element
8882
- attributes.removeNamedItem(attribute.name);
8883
- attribute.value = value;
8884
- element.attributes.setNamedItem(attribute);
8885
- }
8886
-
8887
- function safeAddClass($element, className) {
8888
- try {
8889
- $element.addClass(className);
8890
- } catch (e) {
8891
- // ignore, since it means that we are trying to set class on
8892
- // SVG element, where class name is read-only.
8893
- }
8894
- }
8895
-
8896
-
8897
- var startSymbol = $interpolate.startSymbol(),
8898
- endSymbol = $interpolate.endSymbol(),
8899
- denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}')
8900
- ? identity
8901
- : function denormalizeTemplate(template) {
8902
- return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
8903
- },
8904
- NG_ATTR_BINDING = /^ngAttr[A-Z]/;
8905
- var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
8906
-
8907
- compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
8908
- var bindings = $element.data('$binding') || [];
8909
-
8910
- if (isArray(binding)) {
8911
- bindings = bindings.concat(binding);
8912
- } else {
8913
- bindings.push(binding);
8914
- }
8915
-
8916
- $element.data('$binding', bindings);
8917
- } : noop;
8918
-
8919
- compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
8920
- safeAddClass($element, 'ng-binding');
8921
- } : noop;
8922
-
8923
- compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
8924
- var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
8925
- $element.data(dataName, scope);
8926
- } : noop;
8927
-
8928
- compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
8929
- safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
8930
- } : noop;
8931
-
8932
- compile.$$createComment = function(directiveName, comment) {
8933
- var content = '';
8934
- if (debugInfoEnabled) {
8935
- content = ' ' + (directiveName || '') + ': ';
8936
- if (comment) content += comment + ' ';
8937
- }
8938
- return window.document.createComment(content);
8939
- };
8940
-
8941
- return compile;
8942
-
8943
- //================================
8944
-
8945
- function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
8946
- previousCompileContext) {
8947
- if (!($compileNodes instanceof jqLite)) {
8948
- // jquery always rewraps, whereas we need to preserve the original selector so that we can
8949
- // modify it.
8950
- $compileNodes = jqLite($compileNodes);
8951
- }
8952
- var compositeLinkFn =
8953
- compileNodes($compileNodes, transcludeFn, $compileNodes,
8954
- maxPriority, ignoreDirective, previousCompileContext);
8955
- compile.$$addScopeClass($compileNodes);
8956
- var namespace = null;
8957
- return function publicLinkFn(scope, cloneConnectFn, options) {
8958
- if (!$compileNodes) {
8959
- throw $compileMinErr('multilink', 'This element has already been linked.');
8960
- }
8961
- assertArg(scope, 'scope');
8962
-
8963
- if (previousCompileContext && previousCompileContext.needsNewScope) {
8964
- // A parent directive did a replace and a directive on this element asked
8965
- // for transclusion, which caused us to lose a layer of element on which
8966
- // we could hold the new transclusion scope, so we will create it manually
8967
- // here.
8968
- scope = scope.$parent.$new();
8969
- }
8970
-
8971
- options = options || {};
8972
- var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
8973
- transcludeControllers = options.transcludeControllers,
8974
- futureParentElement = options.futureParentElement;
8975
-
8976
- // When `parentBoundTranscludeFn` is passed, it is a
8977
- // `controllersBoundTransclude` function (it was previously passed
8978
- // as `transclude` to directive.link) so we must unwrap it to get
8979
- // its `boundTranscludeFn`
8980
- if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
8981
- parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
8982
- }
8983
-
8984
- if (!namespace) {
8985
- namespace = detectNamespaceForChildElements(futureParentElement);
8986
- }
8987
- var $linkNode;
8988
- if (namespace !== 'html') {
8989
- // When using a directive with replace:true and templateUrl the $compileNodes
8990
- // (or a child element inside of them)
8991
- // might change, so we need to recreate the namespace adapted compileNodes
8992
- // for call to the link function.
8993
- // Note: This will already clone the nodes...
8994
- $linkNode = jqLite(
8995
- wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
8996
- );
8997
- } else if (cloneConnectFn) {
8998
- // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
8999
- // and sometimes changes the structure of the DOM.
9000
- $linkNode = JQLitePrototype.clone.call($compileNodes);
9001
- } else {
9002
- $linkNode = $compileNodes;
9003
- }
9004
-
9005
- if (transcludeControllers) {
9006
- for (var controllerName in transcludeControllers) {
9007
- $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
9008
- }
9009
- }
9010
-
9011
- compile.$$addScopeInfo($linkNode, scope);
9012
-
9013
- if (cloneConnectFn) cloneConnectFn($linkNode, scope);
9014
- if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
9015
-
9016
- if (!cloneConnectFn) {
9017
- $compileNodes = compositeLinkFn = null;
9018
- }
9019
- return $linkNode;
9020
- };
9021
- }
9022
-
9023
- function detectNamespaceForChildElements(parentElement) {
9024
- // TODO: Make this detect MathML as well...
9025
- var node = parentElement && parentElement[0];
9026
- if (!node) {
9027
- return 'html';
9028
- } else {
9029
- return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
9030
- }
9031
- }
9032
-
9033
- /**
9034
- * Compile function matches each node in nodeList against the directives. Once all directives
9035
- * for a particular node are collected their compile functions are executed. The compile
9036
- * functions return values - the linking functions - are combined into a composite linking
9037
- * function, which is the a linking function for the node.
9038
- *
9039
- * @param {NodeList} nodeList an array of nodes or NodeList to compile
9040
- * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
9041
- * scope argument is auto-generated to the new child of the transcluded parent scope.
9042
- * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
9043
- * the rootElement must be set the jqLite collection of the compile root. This is
9044
- * needed so that the jqLite collection items can be replaced with widgets.
9045
- * @param {number=} maxPriority Max directive priority.
9046
- * @returns {Function} A composite linking function of all of the matched directives or null.
9047
- */
9048
- function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
9049
- previousCompileContext) {
9050
- var linkFns = [],
9051
- // `nodeList` can be either an element's `.childNodes` (live NodeList)
9052
- // or a jqLite/jQuery collection or an array
9053
- notLiveList = isArray(nodeList) || (nodeList instanceof jqLite),
9054
- attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
9055
-
9056
-
9057
- for (var i = 0; i < nodeList.length; i++) {
9058
- attrs = new Attributes();
9059
-
9060
- // Support: IE 11 only
9061
- // Workaround for #11781 and #14924
9062
- if (msie === 11) {
9063
- mergeConsecutiveTextNodes(nodeList, i, notLiveList);
9064
- }
9065
-
9066
- // We must always refer to `nodeList[i]` hereafter,
9067
- // since the nodes can be replaced underneath us.
9068
- directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
9069
- ignoreDirective);
9070
-
9071
- nodeLinkFn = (directives.length)
9072
- ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
9073
- null, [], [], previousCompileContext)
9074
- : null;
9075
-
9076
- if (nodeLinkFn && nodeLinkFn.scope) {
9077
- compile.$$addScopeClass(attrs.$$element);
9078
- }
9079
-
9080
- childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
9081
- !(childNodes = nodeList[i].childNodes) ||
9082
- !childNodes.length)
9083
- ? null
9084
- : compileNodes(childNodes,
9085
- nodeLinkFn ? (
9086
- (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
9087
- && nodeLinkFn.transclude) : transcludeFn);
9088
-
9089
- if (nodeLinkFn || childLinkFn) {
9090
- linkFns.push(i, nodeLinkFn, childLinkFn);
9091
- linkFnFound = true;
9092
- nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
9093
- }
9094
-
9095
- //use the previous context only for the first element in the virtual group
9096
- previousCompileContext = null;
9097
- }
9098
-
9099
- // return a linking function if we have found anything, null otherwise
9100
- return linkFnFound ? compositeLinkFn : null;
9101
-
9102
- function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
9103
- var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
9104
- var stableNodeList;
9105
-
9106
-
9107
- if (nodeLinkFnFound) {
9108
- // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
9109
- // offsets don't get screwed up
9110
- var nodeListLength = nodeList.length;
9111
- stableNodeList = new Array(nodeListLength);
9112
-
9113
- // create a sparse array by only copying the elements which have a linkFn
9114
- for (i = 0; i < linkFns.length; i += 3) {
9115
- idx = linkFns[i];
9116
- stableNodeList[idx] = nodeList[idx];
9117
- }
9118
- } else {
9119
- stableNodeList = nodeList;
9120
- }
9121
-
9122
- for (i = 0, ii = linkFns.length; i < ii;) {
9123
- node = stableNodeList[linkFns[i++]];
9124
- nodeLinkFn = linkFns[i++];
9125
- childLinkFn = linkFns[i++];
9126
-
9127
- if (nodeLinkFn) {
9128
- if (nodeLinkFn.scope) {
9129
- childScope = scope.$new();
9130
- compile.$$addScopeInfo(jqLite(node), childScope);
9131
- } else {
9132
- childScope = scope;
9133
- }
9134
-
9135
- if (nodeLinkFn.transcludeOnThisElement) {
9136
- childBoundTranscludeFn = createBoundTranscludeFn(
9137
- scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
9138
-
9139
- } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
9140
- childBoundTranscludeFn = parentBoundTranscludeFn;
9141
-
9142
- } else if (!parentBoundTranscludeFn && transcludeFn) {
9143
- childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
9144
-
9145
- } else {
9146
- childBoundTranscludeFn = null;
9147
- }
9148
-
9149
- nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
9150
-
9151
- } else if (childLinkFn) {
9152
- childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
9153
- }
9154
- }
9155
- }
9156
- }
9157
-
9158
- function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) {
9159
- var node = nodeList[idx];
9160
- var parent = node.parentNode;
9161
- var sibling;
9162
-
9163
- if (node.nodeType !== NODE_TYPE_TEXT) {
9164
- return;
9165
- }
9166
-
9167
- while (true) {
9168
- sibling = parent ? node.nextSibling : nodeList[idx + 1];
9169
- if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) {
9170
- break;
9171
- }
9172
-
9173
- node.nodeValue = node.nodeValue + sibling.nodeValue;
9174
-
9175
- if (sibling.parentNode) {
9176
- sibling.parentNode.removeChild(sibling);
9177
- }
9178
- if (notLiveList && sibling === nodeList[idx + 1]) {
9179
- nodeList.splice(idx + 1, 1);
9180
- }
9181
- }
9182
- }
9183
-
9184
- function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
9185
- function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
9186
-
9187
- if (!transcludedScope) {
9188
- transcludedScope = scope.$new(false, containingScope);
9189
- transcludedScope.$$transcluded = true;
9190
- }
9191
-
9192
- return transcludeFn(transcludedScope, cloneFn, {
9193
- parentBoundTranscludeFn: previousBoundTranscludeFn,
9194
- transcludeControllers: controllers,
9195
- futureParentElement: futureParentElement
9196
- });
9197
- }
9198
-
9199
- // We need to attach the transclusion slots onto the `boundTranscludeFn`
9200
- // so that they are available inside the `controllersBoundTransclude` function
9201
- var boundSlots = boundTranscludeFn.$$slots = createMap();
9202
- for (var slotName in transcludeFn.$$slots) {
9203
- if (transcludeFn.$$slots[slotName]) {
9204
- boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
9205
- } else {
9206
- boundSlots[slotName] = null;
9207
- }
9208
- }
9209
-
9210
- return boundTranscludeFn;
9211
- }
9212
-
9213
- /**
9214
- * Looks for directives on the given node and adds them to the directive collection which is
9215
- * sorted.
9216
- *
9217
- * @param node Node to search.
9218
- * @param directives An array to which the directives are added to. This array is sorted before
9219
- * the function returns.
9220
- * @param attrs The shared attrs object which is used to populate the normalized attributes.
9221
- * @param {number=} maxPriority Max directive priority.
9222
- */
9223
- function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
9224
- var nodeType = node.nodeType,
9225
- attrsMap = attrs.$attr,
9226
- match,
9227
- nodeName,
9228
- className;
9229
-
9230
- switch (nodeType) {
9231
- case NODE_TYPE_ELEMENT: /* Element */
9232
-
9233
- nodeName = nodeName_(node);
9234
-
9235
- // use the node name: <directive>
9236
- addDirective(directives,
9237
- directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
9238
-
9239
- // iterate over the attributes
9240
- for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
9241
- j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
9242
- var attrStartName = false;
9243
- var attrEndName = false;
9244
-
9245
- attr = nAttrs[j];
9246
- name = attr.name;
9247
- value = attr.value;
9248
-
9249
- // support ngAttr attribute binding
9250
- ngAttrName = directiveNormalize(name);
9251
- isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
9252
- if (isNgAttr) {
9253
- name = name.replace(PREFIX_REGEXP, '')
9254
- .substr(8).replace(/_(.)/g, function(match, letter) {
9255
- return letter.toUpperCase();
9256
- });
9257
- }
9258
-
9259
- var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
9260
- if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
9261
- attrStartName = name;
9262
- attrEndName = name.substr(0, name.length - 5) + 'end';
9263
- name = name.substr(0, name.length - 6);
9264
- }
9265
-
9266
- nName = directiveNormalize(name.toLowerCase());
9267
- attrsMap[nName] = name;
9268
- if (isNgAttr || !attrs.hasOwnProperty(nName)) {
9269
- attrs[nName] = value;
9270
- if (getBooleanAttrName(node, nName)) {
9271
- attrs[nName] = true; // presence means true
9272
- }
9273
- }
9274
- addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
9275
- addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
9276
- attrEndName);
9277
- }
9278
-
9279
- if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
9280
- // Hidden input elements can have strange behaviour when navigating back to the page
9281
- // This tells the browser not to try to cache and reinstate previous values
9282
- node.setAttribute('autocomplete', 'off');
9283
- }
9284
-
9285
- // use class as directive
9286
- if (!cssClassDirectivesEnabled) break;
9287
- className = node.className;
9288
- if (isObject(className)) {
9289
- // Maybe SVGAnimatedString
9290
- className = className.animVal;
9291
- }
9292
- if (isString(className) && className !== '') {
9293
- while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) {
9294
- nName = directiveNormalize(match[2]);
9295
- if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
9296
- attrs[nName] = trim(match[3]);
9297
- }
9298
- className = className.substr(match.index + match[0].length);
9299
- }
9300
- }
9301
- break;
9302
- case NODE_TYPE_TEXT: /* Text Node */
9303
- addTextInterpolateDirective(directives, node.nodeValue);
9304
- break;
9305
- case NODE_TYPE_COMMENT: /* Comment */
9306
- if (!commentDirectivesEnabled) break;
9307
- collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective);
9308
- break;
9309
- }
9310
-
9311
- directives.sort(byPriority);
9312
- return directives;
9313
- }
9314
-
9315
- function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
9316
- // function created because of performance, try/catch disables
9317
- // the optimization of the whole function #14848
9318
- try {
9319
- var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
9320
- if (match) {
9321
- var nName = directiveNormalize(match[1]);
9322
- if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
9323
- attrs[nName] = trim(match[2]);
9324
- }
9325
- }
9326
- } catch (e) {
9327
- // turns out that under some circumstances IE9 throws errors when one attempts to read
9328
- // comment's node value.
9329
- // Just ignore it and continue. (Can't seem to reproduce in test case.)
9330
- }
9331
- }
9332
-
9333
- /**
9334
- * Given a node with a directive-start it collects all of the siblings until it finds
9335
- * directive-end.
9336
- * @param node
9337
- * @param attrStart
9338
- * @param attrEnd
9339
- * @returns {*}
9340
- */
9341
- function groupScan(node, attrStart, attrEnd) {
9342
- var nodes = [];
9343
- var depth = 0;
9344
- if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
9345
- do {
9346
- if (!node) {
9347
- throw $compileMinErr('uterdir',
9348
- 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.',
9349
- attrStart, attrEnd);
9350
- }
9351
- if (node.nodeType === NODE_TYPE_ELEMENT) {
9352
- if (node.hasAttribute(attrStart)) depth++;
9353
- if (node.hasAttribute(attrEnd)) depth--;
9354
- }
9355
- nodes.push(node);
9356
- node = node.nextSibling;
9357
- } while (depth > 0);
9358
- } else {
9359
- nodes.push(node);
9360
- }
9361
-
9362
- return jqLite(nodes);
9363
- }
9364
-
9365
- /**
9366
- * Wrapper for linking function which converts normal linking function into a grouped
9367
- * linking function.
9368
- * @param linkFn
9369
- * @param attrStart
9370
- * @param attrEnd
9371
- * @returns {Function}
9372
- */
9373
- function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
9374
- return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) {
9375
- element = groupScan(element[0], attrStart, attrEnd);
9376
- return linkFn(scope, element, attrs, controllers, transcludeFn);
9377
- };
9378
- }
9379
-
9380
- /**
9381
- * A function generator that is used to support both eager and lazy compilation
9382
- * linking function.
9383
- * @param eager
9384
- * @param $compileNodes
9385
- * @param transcludeFn
9386
- * @param maxPriority
9387
- * @param ignoreDirective
9388
- * @param previousCompileContext
9389
- * @returns {Function}
9390
- */
9391
- function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
9392
- var compiled;
9393
-
9394
- if (eager) {
9395
- return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
9396
- }
9397
- return /** @this */ function lazyCompilation() {
9398
- if (!compiled) {
9399
- compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
9400
-
9401
- // Null out all of these references in order to make them eligible for garbage collection
9402
- // since this is a potentially long lived closure
9403
- $compileNodes = transcludeFn = previousCompileContext = null;
9404
- }
9405
- return compiled.apply(this, arguments);
9406
- };
9407
- }
9408
-
9409
- /**
9410
- * Once the directives have been collected, their compile functions are executed. This method
9411
- * is responsible for inlining directive templates as well as terminating the application
9412
- * of the directives if the terminal directive has been reached.
9413
- *
9414
- * @param {Array} directives Array of collected directives to execute their compile function.
9415
- * this needs to be pre-sorted by priority order.
9416
- * @param {Node} compileNode The raw DOM node to apply the compile functions to
9417
- * @param {Object} templateAttrs The shared attribute function
9418
- * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
9419
- * scope argument is auto-generated to the new
9420
- * child of the transcluded parent scope.
9421
- * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
9422
- * argument has the root jqLite array so that we can replace nodes
9423
- * on it.
9424
- * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
9425
- * compiling the transclusion.
9426
- * @param {Array.<Function>} preLinkFns
9427
- * @param {Array.<Function>} postLinkFns
9428
- * @param {Object} previousCompileContext Context used for previous compilation of the current
9429
- * node
9430
- * @returns {Function} linkFn
9431
- */
9432
- function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
9433
- jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
9434
- previousCompileContext) {
9435
- previousCompileContext = previousCompileContext || {};
9436
-
9437
- var terminalPriority = -Number.MAX_VALUE,
9438
- newScopeDirective = previousCompileContext.newScopeDirective,
9439
- controllerDirectives = previousCompileContext.controllerDirectives,
9440
- newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
9441
- templateDirective = previousCompileContext.templateDirective,
9442
- nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
9443
- hasTranscludeDirective = false,
9444
- hasTemplate = false,
9445
- hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
9446
- $compileNode = templateAttrs.$$element = jqLite(compileNode),
9447
- directive,
9448
- directiveName,
9449
- $template,
9450
- replaceDirective = originalReplaceDirective,
9451
- childTranscludeFn = transcludeFn,
9452
- linkFn,
9453
- didScanForMultipleTransclusion = false,
9454
- mightHaveMultipleTransclusionError = false,
9455
- directiveValue;
9456
-
9457
- // executes all directives on the current element
9458
- for (var i = 0, ii = directives.length; i < ii; i++) {
9459
- directive = directives[i];
9460
- var attrStart = directive.$$start;
9461
- var attrEnd = directive.$$end;
9462
-
9463
- // collect multiblock sections
9464
- if (attrStart) {
9465
- $compileNode = groupScan(compileNode, attrStart, attrEnd);
9466
- }
9467
- $template = undefined;
9468
-
9469
- if (terminalPriority > directive.priority) {
9470
- break; // prevent further processing of directives
9471
- }
9472
-
9473
- directiveValue = directive.scope;
9474
-
9475
- if (directiveValue) {
9476
-
9477
- // skip the check for directives with async templates, we'll check the derived sync
9478
- // directive when the template arrives
9479
- if (!directive.templateUrl) {
9480
- if (isObject(directiveValue)) {
9481
- // This directive is trying to add an isolated scope.
9482
- // Check that there is no scope of any kind already
9483
- assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
9484
- directive, $compileNode);
9485
- newIsolateScopeDirective = directive;
9486
- } else {
9487
- // This directive is trying to add a child scope.
9488
- // Check that there is no isolated scope already
9489
- assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
9490
- $compileNode);
9491
- }
9492
- }
9493
-
9494
- newScopeDirective = newScopeDirective || directive;
9495
- }
9496
-
9497
- directiveName = directive.name;
9498
-
9499
- // If we encounter a condition that can result in transclusion on the directive,
9500
- // then scan ahead in the remaining directives for others that may cause a multiple
9501
- // transclusion error to be thrown during the compilation process. If a matching directive
9502
- // is found, then we know that when we encounter a transcluded directive, we need to eagerly
9503
- // compile the `transclude` function rather than doing it lazily in order to throw
9504
- // exceptions at the correct time
9505
- if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
9506
- || (directive.transclude && !directive.$$tlb))) {
9507
- var candidateDirective;
9508
-
9509
- for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) {
9510
- if ((candidateDirective.transclude && !candidateDirective.$$tlb)
9511
- || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
9512
- mightHaveMultipleTransclusionError = true;
9513
- break;
9514
- }
9515
- }
9516
-
9517
- didScanForMultipleTransclusion = true;
9518
- }
9519
-
9520
- if (!directive.templateUrl && directive.controller) {
9521
- controllerDirectives = controllerDirectives || createMap();
9522
- assertNoDuplicate('\'' + directiveName + '\' controller',
9523
- controllerDirectives[directiveName], directive, $compileNode);
9524
- controllerDirectives[directiveName] = directive;
9525
- }
9526
-
9527
- directiveValue = directive.transclude;
9528
-
9529
- if (directiveValue) {
9530
- hasTranscludeDirective = true;
9531
-
9532
- // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
9533
- // This option should only be used by directives that know how to safely handle element transclusion,
9534
- // where the transcluded nodes are added or replaced after linking.
9535
- if (!directive.$$tlb) {
9536
- assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
9537
- nonTlbTranscludeDirective = directive;
9538
- }
9539
-
9540
- if (directiveValue === 'element') {
9541
- hasElementTranscludeDirective = true;
9542
- terminalPriority = directive.priority;
9543
- $template = $compileNode;
9544
- $compileNode = templateAttrs.$$element =
9545
- jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName]));
9546
- compileNode = $compileNode[0];
9547
- replaceWith(jqCollection, sliceArgs($template), compileNode);
9548
-
9549
- // Support: Chrome < 50
9550
- // https://github.com/angular/angular.js/issues/14041
9551
-
9552
- // In the versions of V8 prior to Chrome 50, the document fragment that is created
9553
- // in the `replaceWith` function is improperly garbage collected despite still
9554
- // being referenced by the `parentNode` property of all of the child nodes. By adding
9555
- // a reference to the fragment via a different property, we can avoid that incorrect
9556
- // behavior.
9557
- // TODO: remove this line after Chrome 50 has been released
9558
- $template[0].$$parentNode = $template[0].parentNode;
9559
-
9560
- childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
9561
- replaceDirective && replaceDirective.name, {
9562
- // Don't pass in:
9563
- // - controllerDirectives - otherwise we'll create duplicates controllers
9564
- // - newIsolateScopeDirective or templateDirective - combining templates with
9565
- // element transclusion doesn't make sense.
9566
- //
9567
- // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
9568
- // on the same element more than once.
9569
- nonTlbTranscludeDirective: nonTlbTranscludeDirective
9570
- });
9571
- } else {
9572
-
9573
- var slots = createMap();
9574
-
9575
- if (!isObject(directiveValue)) {
9576
- $template = jqLite(jqLiteClone(compileNode)).contents();
9577
- } else {
9578
-
9579
- // We have transclusion slots,
9580
- // collect them up, compile them and store their transclusion functions
9581
- $template = [];
9582
-
9583
- var slotMap = createMap();
9584
- var filledSlots = createMap();
9585
-
9586
- // Parse the element selectors
9587
- forEach(directiveValue, function(elementSelector, slotName) {
9588
- // If an element selector starts with a ? then it is optional
9589
- var optional = (elementSelector.charAt(0) === '?');
9590
- elementSelector = optional ? elementSelector.substring(1) : elementSelector;
9591
-
9592
- slotMap[elementSelector] = slotName;
9593
-
9594
- // We explicitly assign `null` since this implies that a slot was defined but not filled.
9595
- // Later when calling boundTransclusion functions with a slot name we only error if the
9596
- // slot is `undefined`
9597
- slots[slotName] = null;
9598
-
9599
- // filledSlots contains `true` for all slots that are either optional or have been
9600
- // filled. This is used to check that we have not missed any required slots
9601
- filledSlots[slotName] = optional;
9602
- });
9603
-
9604
- // Add the matching elements into their slot
9605
- forEach($compileNode.contents(), function(node) {
9606
- var slotName = slotMap[directiveNormalize(nodeName_(node))];
9607
- if (slotName) {
9608
- filledSlots[slotName] = true;
9609
- slots[slotName] = slots[slotName] || [];
9610
- slots[slotName].push(node);
9611
- } else {
9612
- $template.push(node);
9613
- }
9614
- });
9615
-
9616
- // Check for required slots that were not filled
9617
- forEach(filledSlots, function(filled, slotName) {
9618
- if (!filled) {
9619
- throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
9620
- }
9621
- });
9622
-
9623
- for (var slotName in slots) {
9624
- if (slots[slotName]) {
9625
- // Only define a transclusion function if the slot was filled
9626
- slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
9627
- }
9628
- }
9629
- }
9630
-
9631
- $compileNode.empty(); // clear contents
9632
- childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
9633
- undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
9634
- childTranscludeFn.$$slots = slots;
9635
- }
9636
- }
9637
-
9638
- if (directive.template) {
9639
- hasTemplate = true;
9640
- assertNoDuplicate('template', templateDirective, directive, $compileNode);
9641
- templateDirective = directive;
9642
-
9643
- directiveValue = (isFunction(directive.template))
9644
- ? directive.template($compileNode, templateAttrs)
9645
- : directive.template;
9646
-
9647
- directiveValue = denormalizeTemplate(directiveValue);
9648
-
9649
- if (directive.replace) {
9650
- replaceDirective = directive;
9651
- if (jqLiteIsTextNode(directiveValue)) {
9652
- $template = [];
9653
- } else {
9654
- $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
9655
- }
9656
- compileNode = $template[0];
9657
-
9658
- if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
9659
- throw $compileMinErr('tplrt',
9660
- 'Template for directive \'{0}\' must have exactly one root element. {1}',
9661
- directiveName, '');
9662
- }
9663
-
9664
- replaceWith(jqCollection, $compileNode, compileNode);
9665
-
9666
- var newTemplateAttrs = {$attr: {}};
9667
-
9668
- // combine directives from the original node and from the template:
9669
- // - take the array of directives for this element
9670
- // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
9671
- // - collect directives from the template and sort them by priority
9672
- // - combine directives as: processed + template + unprocessed
9673
- var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
9674
- var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
9675
-
9676
- if (newIsolateScopeDirective || newScopeDirective) {
9677
- // The original directive caused the current element to be replaced but this element
9678
- // also needs to have a new scope, so we need to tell the template directives
9679
- // that they would need to get their scope from further up, if they require transclusion
9680
- markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
9681
- }
9682
- directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
9683
- mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
9684
-
9685
- ii = directives.length;
9686
- } else {
9687
- $compileNode.html(directiveValue);
9688
- }
9689
- }
9690
-
9691
- if (directive.templateUrl) {
9692
- hasTemplate = true;
9693
- assertNoDuplicate('template', templateDirective, directive, $compileNode);
9694
- templateDirective = directive;
9695
-
9696
- if (directive.replace) {
9697
- replaceDirective = directive;
9698
- }
9699
-
9700
- // eslint-disable-next-line no-func-assign
9701
- nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
9702
- templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
9703
- controllerDirectives: controllerDirectives,
9704
- newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
9705
- newIsolateScopeDirective: newIsolateScopeDirective,
9706
- templateDirective: templateDirective,
9707
- nonTlbTranscludeDirective: nonTlbTranscludeDirective
9708
- });
9709
- ii = directives.length;
9710
- } else if (directive.compile) {
9711
- try {
9712
- linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
9713
- var context = directive.$$originalDirective || directive;
9714
- if (isFunction(linkFn)) {
9715
- addLinkFns(null, bind(context, linkFn), attrStart, attrEnd);
9716
- } else if (linkFn) {
9717
- addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd);
9718
- }
9719
- } catch (e) {
9720
- $exceptionHandler(e, startingTag($compileNode));
9721
- }
9722
- }
9723
-
9724
- if (directive.terminal) {
9725
- nodeLinkFn.terminal = true;
9726
- terminalPriority = Math.max(terminalPriority, directive.priority);
9727
- }
9728
-
9729
- }
9730
-
9731
- nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
9732
- nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
9733
- nodeLinkFn.templateOnThisElement = hasTemplate;
9734
- nodeLinkFn.transclude = childTranscludeFn;
9735
-
9736
- previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
9737
-
9738
- // might be normal or delayed nodeLinkFn depending on if templateUrl is present
9739
- return nodeLinkFn;
9740
-
9741
- ////////////////////
9742
-
9743
- function addLinkFns(pre, post, attrStart, attrEnd) {
9744
- if (pre) {
9745
- if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
9746
- pre.require = directive.require;
9747
- pre.directiveName = directiveName;
9748
- if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
9749
- pre = cloneAndAnnotateFn(pre, {isolateScope: true});
9750
- }
9751
- preLinkFns.push(pre);
9752
- }
9753
- if (post) {
9754
- if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
9755
- post.require = directive.require;
9756
- post.directiveName = directiveName;
9757
- if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
9758
- post = cloneAndAnnotateFn(post, {isolateScope: true});
9759
- }
9760
- postLinkFns.push(post);
9761
- }
9762
- }
9763
-
9764
- function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
9765
- var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
9766
- attrs, scopeBindingInfo;
9767
-
9768
- if (compileNode === linkNode) {
9769
- attrs = templateAttrs;
9770
- $element = templateAttrs.$$element;
9771
- } else {
9772
- $element = jqLite(linkNode);
9773
- attrs = new Attributes($element, templateAttrs);
9774
- }
9775
-
9776
- controllerScope = scope;
9777
- if (newIsolateScopeDirective) {
9778
- isolateScope = scope.$new(true);
9779
- } else if (newScopeDirective) {
9780
- controllerScope = scope.$parent;
9781
- }
9782
-
9783
- if (boundTranscludeFn) {
9784
- // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
9785
- // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
9786
- transcludeFn = controllersBoundTransclude;
9787
- transcludeFn.$$boundTransclude = boundTranscludeFn;
9788
- // expose the slots on the `$transclude` function
9789
- transcludeFn.isSlotFilled = function(slotName) {
9790
- return !!boundTranscludeFn.$$slots[slotName];
9791
- };
9792
- }
9793
-
9794
- if (controllerDirectives) {
9795
- elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
9796
- }
9797
-
9798
- if (newIsolateScopeDirective) {
9799
- // Initialize isolate scope bindings for new isolate scope directive.
9800
- compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
9801
- templateDirective === newIsolateScopeDirective.$$originalDirective)));
9802
- compile.$$addScopeClass($element, true);
9803
- isolateScope.$$isolateBindings =
9804
- newIsolateScopeDirective.$$isolateBindings;
9805
- scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
9806
- isolateScope.$$isolateBindings,
9807
- newIsolateScopeDirective);
9808
- if (scopeBindingInfo.removeWatches) {
9809
- isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
9810
- }
9811
- }
9812
-
9813
- // Initialize bindToController bindings
9814
- for (var name in elementControllers) {
9815
- var controllerDirective = controllerDirectives[name];
9816
- var controller = elementControllers[name];
9817
- var bindings = controllerDirective.$$bindings.bindToController;
9818
-
9819
- if (preAssignBindingsEnabled) {
9820
- if (bindings) {
9821
- controller.bindingInfo =
9822
- initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9823
- } else {
9824
- controller.bindingInfo = {};
9825
- }
9826
-
9827
- var controllerResult = controller();
9828
- if (controllerResult !== controller.instance) {
9829
- // If the controller constructor has a return value, overwrite the instance
9830
- // from setupControllers
9831
- controller.instance = controllerResult;
9832
- $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
9833
- if (controller.bindingInfo.removeWatches) {
9834
- controller.bindingInfo.removeWatches();
9835
- }
9836
- controller.bindingInfo =
9837
- initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9838
- }
9839
- } else {
9840
- controller.instance = controller();
9841
- $element.data('$' + controllerDirective.name + 'Controller', controller.instance);
9842
- controller.bindingInfo =
9843
- initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
9844
- }
9845
- }
9846
-
9847
- // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
9848
- forEach(controllerDirectives, function(controllerDirective, name) {
9849
- var require = controllerDirective.require;
9850
- if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
9851
- extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
9852
- }
9853
- });
9854
-
9855
- // Handle the init and destroy lifecycle hooks on all controllers that have them
9856
- forEach(elementControllers, function(controller) {
9857
- var controllerInstance = controller.instance;
9858
- if (isFunction(controllerInstance.$onChanges)) {
9859
- try {
9860
- controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
9861
- } catch (e) {
9862
- $exceptionHandler(e);
9863
- }
9864
- }
9865
- if (isFunction(controllerInstance.$onInit)) {
9866
- try {
9867
- controllerInstance.$onInit();
9868
- } catch (e) {
9869
- $exceptionHandler(e);
9870
- }
9871
- }
9872
- if (isFunction(controllerInstance.$doCheck)) {
9873
- controllerScope.$watch(function() { controllerInstance.$doCheck(); });
9874
- controllerInstance.$doCheck();
9875
- }
9876
- if (isFunction(controllerInstance.$onDestroy)) {
9877
- controllerScope.$on('$destroy', function callOnDestroyHook() {
9878
- controllerInstance.$onDestroy();
9879
- });
9880
- }
9881
- });
9882
-
9883
- // PRELINKING
9884
- for (i = 0, ii = preLinkFns.length; i < ii; i++) {
9885
- linkFn = preLinkFns[i];
9886
- invokeLinkFn(linkFn,
9887
- linkFn.isolateScope ? isolateScope : scope,
9888
- $element,
9889
- attrs,
9890
- linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
9891
- transcludeFn
9892
- );
9893
- }
9894
-
9895
- // RECURSION
9896
- // We only pass the isolate scope, if the isolate directive has a template,
9897
- // otherwise the child elements do not belong to the isolate directive.
9898
- var scopeToChild = scope;
9899
- if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
9900
- scopeToChild = isolateScope;
9901
- }
9902
- if (childLinkFn) {
9903
- childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
9904
- }
9905
-
9906
- // POSTLINKING
9907
- for (i = postLinkFns.length - 1; i >= 0; i--) {
9908
- linkFn = postLinkFns[i];
9909
- invokeLinkFn(linkFn,
9910
- linkFn.isolateScope ? isolateScope : scope,
9911
- $element,
9912
- attrs,
9913
- linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
9914
- transcludeFn
9915
- );
9916
- }
9917
-
9918
- // Trigger $postLink lifecycle hooks
9919
- forEach(elementControllers, function(controller) {
9920
- var controllerInstance = controller.instance;
9921
- if (isFunction(controllerInstance.$postLink)) {
9922
- controllerInstance.$postLink();
9923
- }
9924
- });
9925
-
9926
- // This is the function that is injected as `$transclude`.
9927
- // Note: all arguments are optional!
9928
- function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
9929
- var transcludeControllers;
9930
- // No scope passed in:
9931
- if (!isScope(scope)) {
9932
- slotName = futureParentElement;
9933
- futureParentElement = cloneAttachFn;
9934
- cloneAttachFn = scope;
9935
- scope = undefined;
9936
- }
9937
-
9938
- if (hasElementTranscludeDirective) {
9939
- transcludeControllers = elementControllers;
9940
- }
9941
- if (!futureParentElement) {
9942
- futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
9943
- }
9944
- if (slotName) {
9945
- // slotTranscludeFn can be one of three things:
9946
- // * a transclude function - a filled slot
9947
- // * `null` - an optional slot that was not filled
9948
- // * `undefined` - a slot that was not declared (i.e. invalid)
9949
- var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
9950
- if (slotTranscludeFn) {
9951
- return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
9952
- } else if (isUndefined(slotTranscludeFn)) {
9953
- throw $compileMinErr('noslot',
9954
- 'No parent directive that requires a transclusion with slot name "{0}". ' +
9955
- 'Element: {1}',
9956
- slotName, startingTag($element));
9957
- }
9958
- } else {
9959
- return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
9960
- }
9961
- }
9962
- }
9963
- }
9964
-
9965
- function getControllers(directiveName, require, $element, elementControllers) {
9966
- var value;
9967
-
9968
- if (isString(require)) {
9969
- var match = require.match(REQUIRE_PREFIX_REGEXP);
9970
- var name = require.substring(match[0].length);
9971
- var inheritType = match[1] || match[3];
9972
- var optional = match[2] === '?';
9973
-
9974
- //If only parents then start at the parent element
9975
- if (inheritType === '^^') {
9976
- $element = $element.parent();
9977
- //Otherwise attempt getting the controller from elementControllers in case
9978
- //the element is transcluded (and has no data) and to avoid .data if possible
9979
- } else {
9980
- value = elementControllers && elementControllers[name];
9981
- value = value && value.instance;
9982
- }
9983
-
9984
- if (!value) {
9985
- var dataName = '$' + name + 'Controller';
9986
- value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
9987
- }
9988
-
9989
- if (!value && !optional) {
9990
- throw $compileMinErr('ctreq',
9991
- 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!',
9992
- name, directiveName);
9993
- }
9994
- } else if (isArray(require)) {
9995
- value = [];
9996
- for (var i = 0, ii = require.length; i < ii; i++) {
9997
- value[i] = getControllers(directiveName, require[i], $element, elementControllers);
9998
- }
9999
- } else if (isObject(require)) {
10000
- value = {};
10001
- forEach(require, function(controller, property) {
10002
- value[property] = getControllers(directiveName, controller, $element, elementControllers);
10003
- });
10004
- }
10005
-
10006
- return value || null;
10007
- }
10008
-
10009
- function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
10010
- var elementControllers = createMap();
10011
- for (var controllerKey in controllerDirectives) {
10012
- var directive = controllerDirectives[controllerKey];
10013
- var locals = {
10014
- $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
10015
- $element: $element,
10016
- $attrs: attrs,
10017
- $transclude: transcludeFn
10018
- };
10019
-
10020
- var controller = directive.controller;
10021
- if (controller === '@') {
10022
- controller = attrs[directive.name];
10023
- }
10024
-
10025
- var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
10026
-
10027
- // For directives with element transclusion the element is a comment.
10028
- // In this case .data will not attach any data.
10029
- // Instead, we save the controllers for the element in a local hash and attach to .data
10030
- // later, once we have the actual element.
10031
- elementControllers[directive.name] = controllerInstance;
10032
- $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
10033
- }
10034
- return elementControllers;
10035
- }
10036
-
10037
- // Depending upon the context in which a directive finds itself it might need to have a new isolated
10038
- // or child scope created. For instance:
10039
- // * if the directive has been pulled into a template because another directive with a higher priority
10040
- // asked for element transclusion
10041
- // * if the directive itself asks for transclusion but it is at the root of a template and the original
10042
- // element was replaced. See https://github.com/angular/angular.js/issues/12936
10043
- function markDirectiveScope(directives, isolateScope, newScope) {
10044
- for (var j = 0, jj = directives.length; j < jj; j++) {
10045
- directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
10046
- }
10047
- }
10048
-
10049
- /**
10050
- * looks up the directive and decorates it with exception handling and proper parameters. We
10051
- * call this the boundDirective.
10052
- *
10053
- * @param {string} name name of the directive to look up.
10054
- * @param {string} location The directive must be found in specific format.
10055
- * String containing any of theses characters:
10056
- *
10057
- * * `E`: element name
10058
- * * `A': attribute
10059
- * * `C`: class
10060
- * * `M`: comment
10061
- * @returns {boolean} true if directive was added.
10062
- */
10063
- function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
10064
- endAttrName) {
10065
- if (name === ignoreDirective) return null;
10066
- var match = null;
10067
- if (hasDirectives.hasOwnProperty(name)) {
10068
- for (var directive, directives = $injector.get(name + Suffix),
10069
- i = 0, ii = directives.length; i < ii; i++) {
10070
- directive = directives[i];
10071
- if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
10072
- directive.restrict.indexOf(location) !== -1) {
10073
- if (startAttrName) {
10074
- directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
10075
- }
10076
- if (!directive.$$bindings) {
10077
- var bindings = directive.$$bindings =
10078
- parseDirectiveBindings(directive, directive.name);
10079
- if (isObject(bindings.isolateScope)) {
10080
- directive.$$isolateBindings = bindings.isolateScope;
10081
- }
10082
- }
10083
- tDirectives.push(directive);
10084
- match = directive;
10085
- }
10086
- }
10087
- }
10088
- return match;
10089
- }
10090
-
10091
-
10092
- /**
10093
- * looks up the directive and returns true if it is a multi-element directive,
10094
- * and therefore requires DOM nodes between -start and -end markers to be grouped
10095
- * together.
10096
- *
10097
- * @param {string} name name of the directive to look up.
10098
- * @returns true if directive was registered as multi-element.
10099
- */
10100
- function directiveIsMultiElement(name) {
10101
- if (hasDirectives.hasOwnProperty(name)) {
10102
- for (var directive, directives = $injector.get(name + Suffix),
10103
- i = 0, ii = directives.length; i < ii; i++) {
10104
- directive = directives[i];
10105
- if (directive.multiElement) {
10106
- return true;
10107
- }
10108
- }
10109
- }
10110
- return false;
10111
- }
10112
-
10113
- /**
10114
- * When the element is replaced with HTML template then the new attributes
10115
- * on the template need to be merged with the existing attributes in the DOM.
10116
- * The desired effect is to have both of the attributes present.
10117
- *
10118
- * @param {object} dst destination attributes (original DOM)
10119
- * @param {object} src source attributes (from the directive template)
10120
- */
10121
- function mergeTemplateAttributes(dst, src) {
10122
- var srcAttr = src.$attr,
10123
- dstAttr = dst.$attr;
10124
-
10125
- // reapply the old attributes to the new element
10126
- forEach(dst, function(value, key) {
10127
- if (key.charAt(0) !== '$') {
10128
- if (src[key] && src[key] !== value) {
10129
- if (value.length) {
10130
- value += (key === 'style' ? ';' : ' ') + src[key];
10131
- } else {
10132
- value = src[key];
10133
- }
10134
- }
10135
- dst.$set(key, value, true, srcAttr[key]);
10136
- }
10137
- });
10138
-
10139
- // copy the new attributes on the old attrs object
10140
- forEach(src, function(value, key) {
10141
- // Check if we already set this attribute in the loop above.
10142
- // `dst` will never contain hasOwnProperty as DOM parser won't let it.
10143
- // You will get an "InvalidCharacterError: DOM Exception 5" error if you
10144
- // have an attribute like "has-own-property" or "data-has-own-property", etc.
10145
- if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
10146
- dst[key] = value;
10147
-
10148
- if (key !== 'class' && key !== 'style') {
10149
- dstAttr[key] = srcAttr[key];
10150
- }
10151
- }
10152
- });
10153
- }
10154
-
10155
-
10156
- function compileTemplateUrl(directives, $compileNode, tAttrs,
10157
- $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
10158
- var linkQueue = [],
10159
- afterTemplateNodeLinkFn,
10160
- afterTemplateChildLinkFn,
10161
- beforeTemplateCompileNode = $compileNode[0],
10162
- origAsyncDirective = directives.shift(),
10163
- derivedSyncDirective = inherit(origAsyncDirective, {
10164
- templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
10165
- }),
10166
- templateUrl = (isFunction(origAsyncDirective.templateUrl))
10167
- ? origAsyncDirective.templateUrl($compileNode, tAttrs)
10168
- : origAsyncDirective.templateUrl,
10169
- templateNamespace = origAsyncDirective.templateNamespace;
10170
-
10171
- $compileNode.empty();
10172
-
10173
- $templateRequest(templateUrl)
10174
- .then(function(content) {
10175
- var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
10176
-
10177
- content = denormalizeTemplate(content);
10178
-
10179
- if (origAsyncDirective.replace) {
10180
- if (jqLiteIsTextNode(content)) {
10181
- $template = [];
10182
- } else {
10183
- $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
10184
- }
10185
- compileNode = $template[0];
10186
-
10187
- if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
10188
- throw $compileMinErr('tplrt',
10189
- 'Template for directive \'{0}\' must have exactly one root element. {1}',
10190
- origAsyncDirective.name, templateUrl);
10191
- }
10192
-
10193
- tempTemplateAttrs = {$attr: {}};
10194
- replaceWith($rootElement, $compileNode, compileNode);
10195
- var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
10196
-
10197
- if (isObject(origAsyncDirective.scope)) {
10198
- // the original directive that caused the template to be loaded async required
10199
- // an isolate scope
10200
- markDirectiveScope(templateDirectives, true);
10201
- }
10202
- directives = templateDirectives.concat(directives);
10203
- mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
10204
- } else {
10205
- compileNode = beforeTemplateCompileNode;
10206
- $compileNode.html(content);
10207
- }
10208
-
10209
- directives.unshift(derivedSyncDirective);
10210
-
10211
- afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
10212
- childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
10213
- previousCompileContext);
10214
- forEach($rootElement, function(node, i) {
10215
- if (node === compileNode) {
10216
- $rootElement[i] = $compileNode[0];
10217
- }
10218
- });
10219
- afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
10220
-
10221
- while (linkQueue.length) {
10222
- var scope = linkQueue.shift(),
10223
- beforeTemplateLinkNode = linkQueue.shift(),
10224
- linkRootElement = linkQueue.shift(),
10225
- boundTranscludeFn = linkQueue.shift(),
10226
- linkNode = $compileNode[0];
10227
-
10228
- if (scope.$$destroyed) continue;
10229
-
10230
- if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
10231
- var oldClasses = beforeTemplateLinkNode.className;
10232
-
10233
- if (!(previousCompileContext.hasElementTranscludeDirective &&
10234
- origAsyncDirective.replace)) {
10235
- // it was cloned therefore we have to clone as well.
10236
- linkNode = jqLiteClone(compileNode);
10237
- }
10238
- replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
10239
-
10240
- // Copy in CSS classes from original node
10241
- safeAddClass(jqLite(linkNode), oldClasses);
10242
- }
10243
- if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
10244
- childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
10245
- } else {
10246
- childBoundTranscludeFn = boundTranscludeFn;
10247
- }
10248
- afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
10249
- childBoundTranscludeFn);
10250
- }
10251
- linkQueue = null;
10252
- }).catch(function(error) {
10253
- if (isError(error)) {
10254
- $exceptionHandler(error);
10255
- }
10256
- });
10257
-
10258
- return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
10259
- var childBoundTranscludeFn = boundTranscludeFn;
10260
- if (scope.$$destroyed) return;
10261
- if (linkQueue) {
10262
- linkQueue.push(scope,
10263
- node,
10264
- rootElement,
10265
- childBoundTranscludeFn);
10266
- } else {
10267
- if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
10268
- childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
10269
- }
10270
- afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
10271
- }
10272
- };
10273
- }
10274
-
10275
-
10276
- /**
10277
- * Sorting function for bound directives.
10278
- */
10279
- function byPriority(a, b) {
10280
- var diff = b.priority - a.priority;
10281
- if (diff !== 0) return diff;
10282
- if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
10283
- return a.index - b.index;
10284
- }
10285
-
10286
- function assertNoDuplicate(what, previousDirective, directive, element) {
10287
-
10288
- function wrapModuleNameIfDefined(moduleName) {
10289
- return moduleName ?
10290
- (' (module: ' + moduleName + ')') :
10291
- '';
10292
- }
10293
-
10294
- if (previousDirective) {
10295
- throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
10296
- previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
10297
- directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
10298
- }
10299
- }
10300
-
10301
-
10302
- function addTextInterpolateDirective(directives, text) {
10303
- var interpolateFn = $interpolate(text, true);
10304
- if (interpolateFn) {
10305
- directives.push({
10306
- priority: 0,
10307
- compile: function textInterpolateCompileFn(templateNode) {
10308
- var templateNodeParent = templateNode.parent(),
10309
- hasCompileParent = !!templateNodeParent.length;
10310
-
10311
- // When transcluding a template that has bindings in the root
10312
- // we don't have a parent and thus need to add the class during linking fn.
10313
- if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
10314
-
10315
- return function textInterpolateLinkFn(scope, node) {
10316
- var parent = node.parent();
10317
- if (!hasCompileParent) compile.$$addBindingClass(parent);
10318
- compile.$$addBindingInfo(parent, interpolateFn.expressions);
10319
- scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
10320
- node[0].nodeValue = value;
10321
- });
10322
- };
10323
- }
10324
- });
10325
- }
10326
- }
10327
-
10328
-
10329
- function wrapTemplate(type, template) {
10330
- type = lowercase(type || 'html');
10331
- switch (type) {
10332
- case 'svg':
10333
- case 'math':
10334
- var wrapper = window.document.createElement('div');
10335
- wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
10336
- return wrapper.childNodes[0].childNodes;
10337
- default:
10338
- return template;
10339
- }
10340
- }
10341
-
10342
-
10343
- function getTrustedContext(node, attrNormalizedName) {
10344
- if (attrNormalizedName === 'srcdoc') {
10345
- return $sce.HTML;
10346
- }
10347
- var tag = nodeName_(node);
10348
- // All tags with src attributes require a RESOURCE_URL value, except for
10349
- // img and various html5 media tags.
10350
- if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
10351
- if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
10352
- return $sce.RESOURCE_URL;
10353
- }
10354
- // maction[xlink:href] can source SVG. It's not limited to <maction>.
10355
- } else if (attrNormalizedName === 'xlinkHref' ||
10356
- (tag === 'form' && attrNormalizedName === 'action') ||
10357
- // links can be stylesheets or imports, which can run script in the current origin
10358
- (tag === 'link' && attrNormalizedName === 'href')
10359
- ) {
10360
- return $sce.RESOURCE_URL;
10361
- }
10362
- }
10363
-
10364
-
10365
- function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
10366
- var trustedContext = getTrustedContext(node, name);
10367
- var mustHaveExpression = !isNgAttr;
10368
- var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
10369
-
10370
- var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
10371
-
10372
- // no interpolation found -> ignore
10373
- if (!interpolateFn) return;
10374
-
10375
- if (name === 'multiple' && nodeName_(node) === 'select') {
10376
- throw $compileMinErr('selmulti',
10377
- 'Binding to the \'multiple\' attribute is not supported. Element: {0}',
10378
- startingTag(node));
10379
- }
10380
-
10381
- if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
10382
- throw $compileMinErr('nodomevents',
10383
- 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' +
10384
- 'ng- versions (such as ng-click instead of onclick) instead.');
10385
- }
10386
-
10387
- directives.push({
10388
- priority: 100,
10389
- compile: function() {
10390
- return {
10391
- pre: function attrInterpolatePreLinkFn(scope, element, attr) {
10392
- var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
10393
-
10394
- // If the attribute has changed since last $interpolate()ed
10395
- var newValue = attr[name];
10396
- if (newValue !== value) {
10397
- // we need to interpolate again since the attribute value has been updated
10398
- // (e.g. by another directive's compile function)
10399
- // ensure unset/empty values make interpolateFn falsy
10400
- interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
10401
- value = newValue;
10402
- }
10403
-
10404
- // if attribute was updated so that there is no interpolation going on we don't want to
10405
- // register any observers
10406
- if (!interpolateFn) return;
10407
-
10408
- // initialize attr object so that it's ready in case we need the value for isolate
10409
- // scope initialization, otherwise the value would not be available from isolate
10410
- // directive's linking fn during linking phase
10411
- attr[name] = interpolateFn(scope);
10412
-
10413
- ($$observers[name] || ($$observers[name] = [])).$$inter = true;
10414
- (attr.$$observers && attr.$$observers[name].$$scope || scope).
10415
- $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
10416
- //special case for class attribute addition + removal
10417
- //so that class changes can tap into the animation
10418
- //hooks provided by the $animate service. Be sure to
10419
- //skip animations when the first digest occurs (when
10420
- //both the new and the old values are the same) since
10421
- //the CSS classes are the non-interpolated values
10422
- if (name === 'class' && newValue !== oldValue) {
10423
- attr.$updateClass(newValue, oldValue);
10424
- } else {
10425
- attr.$set(name, newValue);
10426
- }
10427
- });
10428
- }
10429
- };
10430
- }
10431
- });
10432
- }
10433
-
10434
-
10435
- /**
10436
- * This is a special jqLite.replaceWith, which can replace items which
10437
- * have no parents, provided that the containing jqLite collection is provided.
10438
- *
10439
- * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
10440
- * in the root of the tree.
10441
- * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
10442
- * the shell, but replace its DOM node reference.
10443
- * @param {Node} newNode The new DOM node.
10444
- */
10445
- function replaceWith($rootElement, elementsToRemove, newNode) {
10446
- var firstElementToRemove = elementsToRemove[0],
10447
- removeCount = elementsToRemove.length,
10448
- parent = firstElementToRemove.parentNode,
10449
- i, ii;
10450
-
10451
- if ($rootElement) {
10452
- for (i = 0, ii = $rootElement.length; i < ii; i++) {
10453
- if ($rootElement[i] === firstElementToRemove) {
10454
- $rootElement[i++] = newNode;
10455
- for (var j = i, j2 = j + removeCount - 1,
10456
- jj = $rootElement.length;
10457
- j < jj; j++, j2++) {
10458
- if (j2 < jj) {
10459
- $rootElement[j] = $rootElement[j2];
10460
- } else {
10461
- delete $rootElement[j];
10462
- }
10463
- }
10464
- $rootElement.length -= removeCount - 1;
10465
-
10466
- // If the replaced element is also the jQuery .context then replace it
10467
- // .context is a deprecated jQuery api, so we should set it only when jQuery set it
10468
- // http://api.jquery.com/context/
10469
- if ($rootElement.context === firstElementToRemove) {
10470
- $rootElement.context = newNode;
10471
- }
10472
- break;
10473
- }
10474
- }
10475
- }
10476
-
10477
- if (parent) {
10478
- parent.replaceChild(newNode, firstElementToRemove);
10479
- }
10480
-
10481
- // Append all the `elementsToRemove` to a fragment. This will...
10482
- // - remove them from the DOM
10483
- // - allow them to still be traversed with .nextSibling
10484
- // - allow a single fragment.qSA to fetch all elements being removed
10485
- var fragment = window.document.createDocumentFragment();
10486
- for (i = 0; i < removeCount; i++) {
10487
- fragment.appendChild(elementsToRemove[i]);
10488
- }
10489
-
10490
- if (jqLite.hasData(firstElementToRemove)) {
10491
- // Copy over user data (that includes Angular's $scope etc.). Don't copy private
10492
- // data here because there's no public interface in jQuery to do that and copying over
10493
- // event listeners (which is the main use of private data) wouldn't work anyway.
10494
- jqLite.data(newNode, jqLite.data(firstElementToRemove));
10495
-
10496
- // Remove $destroy event listeners from `firstElementToRemove`
10497
- jqLite(firstElementToRemove).off('$destroy');
10498
- }
10499
-
10500
- // Cleanup any data/listeners on the elements and children.
10501
- // This includes invoking the $destroy event on any elements with listeners.
10502
- jqLite.cleanData(fragment.querySelectorAll('*'));
10503
-
10504
- // Update the jqLite collection to only contain the `newNode`
10505
- for (i = 1; i < removeCount; i++) {
10506
- delete elementsToRemove[i];
10507
- }
10508
- elementsToRemove[0] = newNode;
10509
- elementsToRemove.length = 1;
10510
- }
10511
-
10512
-
10513
- function cloneAndAnnotateFn(fn, annotation) {
10514
- return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
10515
- }
10516
-
10517
-
10518
- function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
10519
- try {
10520
- linkFn(scope, $element, attrs, controllers, transcludeFn);
10521
- } catch (e) {
10522
- $exceptionHandler(e, startingTag($element));
10523
- }
10524
- }
10525
-
10526
-
10527
- // Set up $watches for isolate scope and controller bindings.
10528
- function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
10529
- var removeWatchCollection = [];
10530
- var initialChanges = {};
10531
- var changes;
10532
- forEach(bindings, function initializeBinding(definition, scopeName) {
10533
- var attrName = definition.attrName,
10534
- optional = definition.optional,
10535
- mode = definition.mode, // @, =, <, or &
10536
- lastValue,
10537
- parentGet, parentSet, compare, removeWatch;
10538
-
10539
- switch (mode) {
10540
-
10541
- case '@':
10542
- if (!optional && !hasOwnProperty.call(attrs, attrName)) {
10543
- destination[scopeName] = attrs[attrName] = undefined;
10544
- }
10545
- removeWatch = attrs.$observe(attrName, function(value) {
10546
- if (isString(value) || isBoolean(value)) {
10547
- var oldValue = destination[scopeName];
10548
- recordChanges(scopeName, value, oldValue);
10549
- destination[scopeName] = value;
10550
- }
10551
- });
10552
- attrs.$$observers[attrName].$$scope = scope;
10553
- lastValue = attrs[attrName];
10554
- if (isString(lastValue)) {
10555
- // If the attribute has been provided then we trigger an interpolation to ensure
10556
- // the value is there for use in the link fn
10557
- destination[scopeName] = $interpolate(lastValue)(scope);
10558
- } else if (isBoolean(lastValue)) {
10559
- // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted
10560
- // the value to boolean rather than a string, so we special case this situation
10561
- destination[scopeName] = lastValue;
10562
- }
10563
- initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
10564
- removeWatchCollection.push(removeWatch);
10565
- break;
10566
-
10567
- case '=':
10568
- if (!hasOwnProperty.call(attrs, attrName)) {
10569
- if (optional) break;
10570
- attrs[attrName] = undefined;
10571
- }
10572
- if (optional && !attrs[attrName]) break;
10573
-
10574
- parentGet = $parse(attrs[attrName]);
10575
- if (parentGet.literal) {
10576
- compare = equals;
10577
- } else {
10578
- compare = simpleCompare;
10579
- }
10580
- parentSet = parentGet.assign || function() {
10581
- // reset the change, or we will throw this exception on every $digest
10582
- lastValue = destination[scopeName] = parentGet(scope);
10583
- throw $compileMinErr('nonassign',
10584
- 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!',
10585
- attrs[attrName], attrName, directive.name);
10586
- };
10587
- lastValue = destination[scopeName] = parentGet(scope);
10588
- var parentValueWatch = function parentValueWatch(parentValue) {
10589
- if (!compare(parentValue, destination[scopeName])) {
10590
- // we are out of sync and need to copy
10591
- if (!compare(parentValue, lastValue)) {
10592
- // parent changed and it has precedence
10593
- destination[scopeName] = parentValue;
10594
- } else {
10595
- // if the parent can be assigned then do so
10596
- parentSet(scope, parentValue = destination[scopeName]);
10597
- }
10598
- }
10599
- lastValue = parentValue;
10600
- return lastValue;
10601
- };
10602
- parentValueWatch.$stateful = true;
10603
- if (definition.collection) {
10604
- removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
10605
- } else {
10606
- removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
10607
- }
10608
- removeWatchCollection.push(removeWatch);
10609
- break;
10610
-
10611
- case '<':
10612
- if (!hasOwnProperty.call(attrs, attrName)) {
10613
- if (optional) break;
10614
- attrs[attrName] = undefined;
10615
- }
10616
- if (optional && !attrs[attrName]) break;
10617
-
10618
- parentGet = $parse(attrs[attrName]);
10619
- var deepWatch = parentGet.literal;
10620
-
10621
- var initialValue = destination[scopeName] = parentGet(scope);
10622
- initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
10623
-
10624
- removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
10625
- if (oldValue === newValue) {
10626
- if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) {
10627
- return;
10628
- }
10629
- oldValue = initialValue;
10630
- }
10631
- recordChanges(scopeName, newValue, oldValue);
10632
- destination[scopeName] = newValue;
10633
- }, deepWatch);
10634
-
10635
- removeWatchCollection.push(removeWatch);
10636
- break;
10637
-
10638
- case '&':
10639
- // Don't assign Object.prototype method to scope
10640
- parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
10641
-
10642
- // Don't assign noop to destination if expression is not valid
10643
- if (parentGet === noop && optional) break;
10644
-
10645
- destination[scopeName] = function(locals) {
10646
- return parentGet(scope, locals);
10647
- };
10648
- break;
10649
- }
10650
- });
10651
-
10652
- function recordChanges(key, currentValue, previousValue) {
10653
- if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) {
10654
- // If we have not already scheduled the top level onChangesQueue handler then do so now
10655
- if (!onChangesQueue) {
10656
- scope.$$postDigest(flushOnChangesQueue);
10657
- onChangesQueue = [];
10658
- }
10659
- // If we have not already queued a trigger of onChanges for this controller then do so now
10660
- if (!changes) {
10661
- changes = {};
10662
- onChangesQueue.push(triggerOnChangesHook);
10663
- }
10664
- // If the has been a change on this property already then we need to reuse the previous value
10665
- if (changes[key]) {
10666
- previousValue = changes[key].previousValue;
10667
- }
10668
- // Store this change
10669
- changes[key] = new SimpleChange(previousValue, currentValue);
10670
- }
10671
- }
10672
-
10673
- function triggerOnChangesHook() {
10674
- destination.$onChanges(changes);
10675
- // Now clear the changes so that we schedule onChanges when more changes arrive
10676
- changes = undefined;
10677
- }
10678
-
10679
- return {
10680
- initialChanges: initialChanges,
10681
- removeWatches: removeWatchCollection.length && function removeWatches() {
10682
- for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
10683
- removeWatchCollection[i]();
10684
- }
10685
- }
10686
- };
10687
- }
10688
- }];
10689
- }
10690
-
10691
- function SimpleChange(previous, current) {
10692
- this.previousValue = previous;
10693
- this.currentValue = current;
10694
- }
10695
- SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
10696
-
10697
-
10698
- var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i;
10699
- var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;
10700
-
10701
- /**
10702
- * Converts all accepted directives format into proper directive name.
10703
- * @param name Name to normalize
10704
- */
10705
- function directiveNormalize(name) {
10706
- return name
10707
- .replace(PREFIX_REGEXP, '')
10708
- .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace);
10709
- }
10710
-
10711
- /**
10712
- * @ngdoc type
10713
- * @name $compile.directive.Attributes
10714
- *
10715
- * @description
10716
- * A shared object between directive compile / linking functions which contains normalized DOM
10717
- * element attributes. The values reflect current binding state `{{ }}`. The normalization is
10718
- * needed since all of these are treated as equivalent in Angular:
10719
- *
10720
- * ```
10721
- * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
10722
- * ```
10723
- */
10724
-
10725
- /**
10726
- * @ngdoc property
10727
- * @name $compile.directive.Attributes#$attr
10728
- *
10729
- * @description
10730
- * A map of DOM element attribute names to the normalized name. This is
10731
- * needed to do reverse lookup from normalized name back to actual name.
10732
- */
10733
-
10734
-
10735
- /**
10736
- * @ngdoc method
10737
- * @name $compile.directive.Attributes#$set
10738
- * @kind function
10739
- *
10740
- * @description
10741
- * Set DOM element attribute value.
10742
- *
10743
- *
10744
- * @param {string} name Normalized element attribute name of the property to modify. The name is
10745
- * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
10746
- * property to the original name.
10747
- * @param {string} value Value to set the attribute to. The value can be an interpolated string.
10748
- */
10749
-
10750
-
10751
-
10752
- /**
10753
- * Closure compiler type information
10754
- */
10755
-
10756
- function nodesetLinkingFn(
10757
- /* angular.Scope */ scope,
10758
- /* NodeList */ nodeList,
10759
- /* Element */ rootElement,
10760
- /* function(Function) */ boundTranscludeFn
10761
- ) {}
10762
-
10763
- function directiveLinkingFn(
10764
- /* nodesetLinkingFn */ nodesetLinkingFn,
10765
- /* angular.Scope */ scope,
10766
- /* Node */ node,
10767
- /* Element */ rootElement,
10768
- /* function(Function) */ boundTranscludeFn
10769
- ) {}
10770
-
10771
- function tokenDifference(str1, str2) {
10772
- var values = '',
10773
- tokens1 = str1.split(/\s+/),
10774
- tokens2 = str2.split(/\s+/);
10775
-
10776
- outer:
10777
- for (var i = 0; i < tokens1.length; i++) {
10778
- var token = tokens1[i];
10779
- for (var j = 0; j < tokens2.length; j++) {
10780
- if (token === tokens2[j]) continue outer;
10781
- }
10782
- values += (values.length > 0 ? ' ' : '') + token;
10783
- }
10784
- return values;
10785
- }
10786
-
10787
- function removeComments(jqNodes) {
10788
- jqNodes = jqLite(jqNodes);
10789
- var i = jqNodes.length;
10790
-
10791
- if (i <= 1) {
10792
- return jqNodes;
10793
- }
10794
-
10795
- while (i--) {
10796
- var node = jqNodes[i];
10797
- if (node.nodeType === NODE_TYPE_COMMENT ||
10798
- (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) {
10799
- splice.call(jqNodes, i, 1);
10800
- }
10801
- }
10802
- return jqNodes;
10803
- }
10804
-
10805
- var $controllerMinErr = minErr('$controller');
10806
-
10807
-
10808
- var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
10809
- function identifierForController(controller, ident) {
10810
- if (ident && isString(ident)) return ident;
10811
- if (isString(controller)) {
10812
- var match = CNTRL_REG.exec(controller);
10813
- if (match) return match[3];
10814
- }
10815
- }
10816
-
10817
-
10818
- /**
10819
- * @ngdoc provider
10820
- * @name $controllerProvider
10821
- * @this
10822
- *
10823
- * @description
10824
- * The {@link ng.$controller $controller service} is used by Angular to create new
10825
- * controllers.
10826
- *
10827
- * This provider allows controller registration via the
10828
- * {@link ng.$controllerProvider#register register} method.
10829
- */
10830
- function $ControllerProvider() {
10831
- var controllers = {},
10832
- globals = false;
10833
-
10834
- /**
10835
- * @ngdoc method
10836
- * @name $controllerProvider#has
10837
- * @param {string} name Controller name to check.
10838
- */
10839
- this.has = function(name) {
10840
- return controllers.hasOwnProperty(name);
10841
- };
10842
-
10843
- /**
10844
- * @ngdoc method
10845
- * @name $controllerProvider#register
10846
- * @param {string|Object} name Controller name, or an object map of controllers where the keys are
10847
- * the names and the values are the constructors.
10848
- * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
10849
- * annotations in the array notation).
10850
- */
10851
- this.register = function(name, constructor) {
10852
- assertNotHasOwnProperty(name, 'controller');
10853
- if (isObject(name)) {
10854
- extend(controllers, name);
10855
- } else {
10856
- controllers[name] = constructor;
10857
- }
10858
- };
10859
-
10860
- /**
10861
- * @ngdoc method
10862
- * @name $controllerProvider#allowGlobals
10863
- * @description If called, allows `$controller` to find controller constructors on `window`
10864
- *
10865
- * @deprecated
10866
- * sinceVersion="v1.3.0"
10867
- * removeVersion="v1.7.0"
10868
- * This method of finding controllers has been deprecated.
10869
- */
10870
- this.allowGlobals = function() {
10871
- globals = true;
10872
- };
10873
-
10874
-
10875
- this.$get = ['$injector', '$window', function($injector, $window) {
10876
-
10877
- /**
10878
- * @ngdoc service
10879
- * @name $controller
10880
- * @requires $injector
10881
- *
10882
- * @param {Function|string} constructor If called with a function then it's considered to be the
10883
- * controller constructor function. Otherwise it's considered to be a string which is used
10884
- * to retrieve the controller constructor using the following steps:
10885
- *
10886
- * * check if a controller with given name is registered via `$controllerProvider`
10887
- * * check if evaluating the string on the current scope returns a constructor
10888
- * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
10889
- * `window` object (deprecated, not recommended)
10890
- *
10891
- * The string can use the `controller as property` syntax, where the controller instance is published
10892
- * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
10893
- * to work correctly.
10894
- *
10895
- * @param {Object} locals Injection locals for Controller.
10896
- * @return {Object} Instance of given controller.
10897
- *
10898
- * @description
10899
- * `$controller` service is responsible for instantiating controllers.
10900
- *
10901
- * It's just a simple call to {@link auto.$injector $injector}, but extracted into
10902
- * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
10903
- */
10904
- return function $controller(expression, locals, later, ident) {
10905
- // PRIVATE API:
10906
- // param `later` --- indicates that the controller's constructor is invoked at a later time.
10907
- // If true, $controller will allocate the object with the correct
10908
- // prototype chain, but will not invoke the controller until a returned
10909
- // callback is invoked.
10910
- // param `ident` --- An optional label which overrides the label parsed from the controller
10911
- // expression, if any.
10912
- var instance, match, constructor, identifier;
10913
- later = later === true;
10914
- if (ident && isString(ident)) {
10915
- identifier = ident;
10916
- }
10917
-
10918
- if (isString(expression)) {
10919
- match = expression.match(CNTRL_REG);
10920
- if (!match) {
10921
- throw $controllerMinErr('ctrlfmt',
10922
- 'Badly formed controller string \'{0}\'. ' +
10923
- 'Must match `__name__ as __id__` or `__name__`.', expression);
10924
- }
10925
- constructor = match[1];
10926
- identifier = identifier || match[3];
10927
- expression = controllers.hasOwnProperty(constructor)
10928
- ? controllers[constructor]
10929
- : getter(locals.$scope, constructor, true) ||
10930
- (globals ? getter($window, constructor, true) : undefined);
10931
-
10932
- if (!expression) {
10933
- throw $controllerMinErr('ctrlreg',
10934
- 'The controller with the name \'{0}\' is not registered.', constructor);
10935
- }
10936
-
10937
- assertArgFn(expression, constructor, true);
10938
- }
10939
-
10940
- if (later) {
10941
- // Instantiate controller later:
10942
- // This machinery is used to create an instance of the object before calling the
10943
- // controller's constructor itself.
10944
- //
10945
- // This allows properties to be added to the controller before the constructor is
10946
- // invoked. Primarily, this is used for isolate scope bindings in $compile.
10947
- //
10948
- // This feature is not intended for use by applications, and is thus not documented
10949
- // publicly.
10950
- // Object creation: http://jsperf.com/create-constructor/2
10951
- var controllerPrototype = (isArray(expression) ?
10952
- expression[expression.length - 1] : expression).prototype;
10953
- instance = Object.create(controllerPrototype || null);
10954
-
10955
- if (identifier) {
10956
- addIdentifier(locals, identifier, instance, constructor || expression.name);
10957
- }
10958
-
10959
- return extend(function $controllerInit() {
10960
- var result = $injector.invoke(expression, instance, locals, constructor);
10961
- if (result !== instance && (isObject(result) || isFunction(result))) {
10962
- instance = result;
10963
- if (identifier) {
10964
- // If result changed, re-assign controllerAs value to scope.
10965
- addIdentifier(locals, identifier, instance, constructor || expression.name);
10966
- }
10967
- }
10968
- return instance;
10969
- }, {
10970
- instance: instance,
10971
- identifier: identifier
10972
- });
10973
- }
10974
-
10975
- instance = $injector.instantiate(expression, locals, constructor);
10976
-
10977
- if (identifier) {
10978
- addIdentifier(locals, identifier, instance, constructor || expression.name);
10979
- }
10980
-
10981
- return instance;
10982
- };
10983
-
10984
- function addIdentifier(locals, identifier, instance, name) {
10985
- if (!(locals && isObject(locals.$scope))) {
10986
- throw minErr('$controller')('noscp',
10987
- 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.',
10988
- name, identifier);
10989
- }
10990
-
10991
- locals.$scope[identifier] = instance;
10992
- }
10993
- }];
10994
- }
10995
-
10996
- /**
10997
- * @ngdoc service
10998
- * @name $document
10999
- * @requires $window
11000
- * @this
11001
- *
11002
- * @description
11003
- * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
11004
- *
11005
- * @example
11006
- <example module="documentExample" name="document">
11007
- <file name="index.html">
11008
- <div ng-controller="ExampleController">
11009
- <p>$document title: <b ng-bind="title"></b></p>
11010
- <p>window.document title: <b ng-bind="windowTitle"></b></p>
11011
- </div>
11012
- </file>
11013
- <file name="script.js">
11014
- angular.module('documentExample', [])
11015
- .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
11016
- $scope.title = $document[0].title;
11017
- $scope.windowTitle = angular.element(window.document)[0].title;
11018
- }]);
11019
- </file>
11020
- </example>
11021
- */
11022
- function $DocumentProvider() {
11023
- this.$get = ['$window', function(window) {
11024
- return jqLite(window.document);
11025
- }];
11026
- }
11027
-
11028
-
11029
- /**
11030
- * @private
11031
- * @this
11032
- * Listens for document visibility change and makes the current status accessible.
11033
- */
11034
- function $$IsDocumentHiddenProvider() {
11035
- this.$get = ['$document', '$rootScope', function($document, $rootScope) {
11036
- var doc = $document[0];
11037
- var hidden = doc && doc.hidden;
11038
-
11039
- $document.on('visibilitychange', changeListener);
11040
-
11041
- $rootScope.$on('$destroy', function() {
11042
- $document.off('visibilitychange', changeListener);
11043
- });
11044
-
11045
- function changeListener() {
11046
- hidden = doc.hidden;
11047
- }
11048
-
11049
- return function() {
11050
- return hidden;
11051
- };
11052
- }];
11053
- }
11054
-
11055
- /**
11056
- * @ngdoc service
11057
- * @name $exceptionHandler
11058
- * @requires ng.$log
11059
- * @this
11060
- *
11061
- * @description
11062
- * Any uncaught exception in angular expressions is delegated to this service.
11063
- * The default implementation simply delegates to `$log.error` which logs it into
11064
- * the browser console.
11065
- *
11066
- * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
11067
- * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
11068
- *
11069
- * ## Example:
11070
- *
11071
- * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
11072
- * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
11073
- * of `$log.error()`.
11074
- *
11075
- * ```js
11076
- * angular.
11077
- * module('exceptionOverwrite', []).
11078
- * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
11079
- * return function myExceptionHandler(exception, cause) {
11080
- * logErrorsToBackend(exception, cause);
11081
- * $log.warn(exception, cause);
11082
- * };
11083
- * }]);
11084
- * ```
11085
- *
11086
- * <hr />
11087
- * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
11088
- * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
11089
- * (unless executed during a digest).
11090
- *
11091
- * If you wish, you can manually delegate exceptions, e.g.
11092
- * `try { ... } catch(e) { $exceptionHandler(e); }`
11093
- *
11094
- * @param {Error} exception Exception associated with the error.
11095
- * @param {string=} cause Optional information about the context in which
11096
- * the error was thrown.
11097
- *
11098
- */
11099
- function $ExceptionHandlerProvider() {
11100
- this.$get = ['$log', function($log) {
11101
- return function(exception, cause) {
11102
- $log.error.apply($log, arguments);
11103
- };
11104
- }];
11105
- }
11106
-
11107
- var $$ForceReflowProvider = /** @this */ function() {
11108
- this.$get = ['$document', function($document) {
11109
- return function(domNode) {
11110
- //the line below will force the browser to perform a repaint so
11111
- //that all the animated elements within the animation frame will
11112
- //be properly updated and drawn on screen. This is required to
11113
- //ensure that the preparation animation is properly flushed so that
11114
- //the active state picks up from there. DO NOT REMOVE THIS LINE.
11115
- //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
11116
- //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
11117
- //WILL TAKE YEARS AWAY FROM YOUR LIFE.
11118
- if (domNode) {
11119
- if (!domNode.nodeType && domNode instanceof jqLite) {
11120
- domNode = domNode[0];
11121
- }
11122
- } else {
11123
- domNode = $document[0].body;
11124
- }
11125
- return domNode.offsetWidth + 1;
11126
- };
11127
- }];
11128
- };
11129
-
11130
- var APPLICATION_JSON = 'application/json';
11131
- var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
11132
- var JSON_START = /^\[|^\{(?!\{)/;
11133
- var JSON_ENDS = {
11134
- '[': /]$/,
11135
- '{': /}$/
11136
- };
11137
- var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
11138
- var $httpMinErr = minErr('$http');
11139
-
11140
- function serializeValue(v) {
11141
- if (isObject(v)) {
11142
- return isDate(v) ? v.toISOString() : toJson(v);
11143
- }
11144
- return v;
11145
- }
11146
-
11147
-
11148
- /** @this */
11149
- function $HttpParamSerializerProvider() {
11150
- /**
11151
- * @ngdoc service
11152
- * @name $httpParamSerializer
11153
- * @description
11154
- *
11155
- * Default {@link $http `$http`} params serializer that converts objects to strings
11156
- * according to the following rules:
11157
- *
11158
- * * `{'foo': 'bar'}` results in `foo=bar`
11159
- * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
11160
- * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
11161
- * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
11162
- *
11163
- * Note that serializer will sort the request parameters alphabetically.
11164
- * */
11165
-
11166
- this.$get = function() {
11167
- return function ngParamSerializer(params) {
11168
- if (!params) return '';
11169
- var parts = [];
11170
- forEachSorted(params, function(value, key) {
11171
- if (value === null || isUndefined(value)) return;
11172
- if (isArray(value)) {
11173
- forEach(value, function(v) {
11174
- parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
11175
- });
11176
- } else {
11177
- parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
11178
- }
11179
- });
11180
-
11181
- return parts.join('&');
11182
- };
11183
- };
11184
- }
11185
-
11186
- /** @this */
11187
- function $HttpParamSerializerJQLikeProvider() {
11188
- /**
11189
- * @ngdoc service
11190
- * @name $httpParamSerializerJQLike
11191
- *
11192
- * @description
11193
- *
11194
- * Alternative {@link $http `$http`} params serializer that follows
11195
- * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
11196
- * The serializer will also sort the params alphabetically.
11197
- *
11198
- * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
11199
- *
11200
- * ```js
11201
- * $http({
11202
- * url: myUrl,
11203
- * method: 'GET',
11204
- * params: myParams,
11205
- * paramSerializer: '$httpParamSerializerJQLike'
11206
- * });
11207
- * ```
11208
- *
11209
- * It is also possible to set it as the default `paramSerializer` in the
11210
- * {@link $httpProvider#defaults `$httpProvider`}.
11211
- *
11212
- * Additionally, you can inject the serializer and use it explicitly, for example to serialize
11213
- * form data for submission:
11214
- *
11215
- * ```js
11216
- * .controller(function($http, $httpParamSerializerJQLike) {
11217
- * //...
11218
- *
11219
- * $http({
11220
- * url: myUrl,
11221
- * method: 'POST',
11222
- * data: $httpParamSerializerJQLike(myData),
11223
- * headers: {
11224
- * 'Content-Type': 'application/x-www-form-urlencoded'
11225
- * }
11226
- * });
11227
- *
11228
- * });
11229
- * ```
11230
- *
11231
- * */
11232
- this.$get = function() {
11233
- return function jQueryLikeParamSerializer(params) {
11234
- if (!params) return '';
11235
- var parts = [];
11236
- serialize(params, '', true);
11237
- return parts.join('&');
11238
-
11239
- function serialize(toSerialize, prefix, topLevel) {
11240
- if (toSerialize === null || isUndefined(toSerialize)) return;
11241
- if (isArray(toSerialize)) {
11242
- forEach(toSerialize, function(value, index) {
11243
- serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
11244
- });
11245
- } else if (isObject(toSerialize) && !isDate(toSerialize)) {
11246
- forEachSorted(toSerialize, function(value, key) {
11247
- serialize(value, prefix +
11248
- (topLevel ? '' : '[') +
11249
- key +
11250
- (topLevel ? '' : ']'));
11251
- });
11252
- } else {
11253
- parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
11254
- }
11255
- }
11256
- };
11257
- };
11258
- }
11259
-
11260
- function defaultHttpResponseTransform(data, headers) {
11261
- if (isString(data)) {
11262
- // Strip json vulnerability protection prefix and trim whitespace
11263
- var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
11264
-
11265
- if (tempData) {
11266
- var contentType = headers('Content-Type');
11267
- if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
11268
- try {
11269
- data = fromJson(tempData);
11270
- } catch (e) {
11271
- throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' +
11272
- 'Parse error: "{1}"', data, e);
11273
- }
11274
- }
11275
- }
11276
- }
11277
-
11278
- return data;
11279
- }
11280
-
11281
- function isJsonLike(str) {
11282
- var jsonStart = str.match(JSON_START);
11283
- return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
11284
- }
11285
-
11286
- /**
11287
- * Parse headers into key value object
11288
- *
11289
- * @param {string} headers Raw headers as a string
11290
- * @returns {Object} Parsed headers as key value object
11291
- */
11292
- function parseHeaders(headers) {
11293
- var parsed = createMap(), i;
11294
-
11295
- function fillInParsed(key, val) {
11296
- if (key) {
11297
- parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
11298
- }
11299
- }
11300
-
11301
- if (isString(headers)) {
11302
- forEach(headers.split('\n'), function(line) {
11303
- i = line.indexOf(':');
11304
- fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
11305
- });
11306
- } else if (isObject(headers)) {
11307
- forEach(headers, function(headerVal, headerKey) {
11308
- fillInParsed(lowercase(headerKey), trim(headerVal));
11309
- });
11310
- }
11311
-
11312
- return parsed;
11313
- }
11314
-
11315
-
11316
- /**
11317
- * Returns a function that provides access to parsed headers.
11318
- *
11319
- * Headers are lazy parsed when first requested.
11320
- * @see parseHeaders
11321
- *
11322
- * @param {(string|Object)} headers Headers to provide access to.
11323
- * @returns {function(string=)} Returns a getter function which if called with:
11324
- *
11325
- * - if called with an argument returns a single header value or null
11326
- * - if called with no arguments returns an object containing all headers.
11327
- */
11328
- function headersGetter(headers) {
11329
- var headersObj;
11330
-
11331
- return function(name) {
11332
- if (!headersObj) headersObj = parseHeaders(headers);
11333
-
11334
- if (name) {
11335
- var value = headersObj[lowercase(name)];
11336
- if (value === undefined) {
11337
- value = null;
11338
- }
11339
- return value;
11340
- }
11341
-
11342
- return headersObj;
11343
- };
11344
- }
11345
-
11346
-
11347
- /**
11348
- * Chain all given functions
11349
- *
11350
- * This function is used for both request and response transforming
11351
- *
11352
- * @param {*} data Data to transform.
11353
- * @param {function(string=)} headers HTTP headers getter fn.
11354
- * @param {number} status HTTP status code of the response.
11355
- * @param {(Function|Array.<Function>)} fns Function or an array of functions.
11356
- * @returns {*} Transformed data.
11357
- */
11358
- function transformData(data, headers, status, fns) {
11359
- if (isFunction(fns)) {
11360
- return fns(data, headers, status);
11361
- }
11362
-
11363
- forEach(fns, function(fn) {
11364
- data = fn(data, headers, status);
11365
- });
11366
-
11367
- return data;
11368
- }
11369
-
11370
-
11371
- function isSuccess(status) {
11372
- return 200 <= status && status < 300;
11373
- }
11374
-
11375
-
11376
- /**
11377
- * @ngdoc provider
11378
- * @name $httpProvider
11379
- * @this
11380
- *
11381
- * @description
11382
- * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
11383
- * */
11384
- function $HttpProvider() {
11385
- /**
11386
- * @ngdoc property
11387
- * @name $httpProvider#defaults
11388
- * @description
11389
- *
11390
- * Object containing default values for all {@link ng.$http $http} requests.
11391
- *
11392
- * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
11393
- * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
11394
- * by default. See {@link $http#caching $http Caching} for more information.
11395
- *
11396
- * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
11397
- * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
11398
- * setting default headers.
11399
- * - **`defaults.headers.common`**
11400
- * - **`defaults.headers.post`**
11401
- * - **`defaults.headers.put`**
11402
- * - **`defaults.headers.patch`**
11403
- *
11404
- * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the
11405
- * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
11406
- * {@link $jsonpCallbacks} service. Defaults to `'callback'`.
11407
- *
11408
- * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
11409
- * used to the prepare string representation of request parameters (specified as an object).
11410
- * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
11411
- * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
11412
- *
11413
- * - **`defaults.transformRequest`** -
11414
- * `{Array<function(data, headersGetter)>|function(data, headersGetter)}` -
11415
- * An array of functions (or a single function) which are applied to the request data.
11416
- * By default, this is an array with one request transformation function:
11417
- *
11418
- * - If the `data` property of the request configuration object contains an object, serialize it
11419
- * into JSON format.
11420
- *
11421
- * - **`defaults.transformResponse`** -
11422
- * `{Array<function(data, headersGetter, status)>|function(data, headersGetter, status)}` -
11423
- * An array of functions (or a single function) which are applied to the response data. By default,
11424
- * this is an array which applies one response transformation function that does two things:
11425
- *
11426
- * - If XSRF prefix is detected, strip it
11427
- * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}).
11428
- * - If the `Content-Type` is `application/json` or the response looks like JSON,
11429
- * deserialize it using a JSON parser.
11430
- *
11431
- * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
11432
- * Defaults value is `'XSRF-TOKEN'`.
11433
- *
11434
- * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
11435
- * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
11436
- *
11437
- **/
11438
- var defaults = this.defaults = {
11439
- // transform incoming response data
11440
- transformResponse: [defaultHttpResponseTransform],
11441
-
11442
- // transform outgoing request data
11443
- transformRequest: [function(d) {
11444
- return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
11445
- }],
11446
-
11447
- // default headers
11448
- headers: {
11449
- common: {
11450
- 'Accept': 'application/json, text/plain, */*'
11451
- },
11452
- post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
11453
- put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
11454
- patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
11455
- },
11456
-
11457
- xsrfCookieName: 'XSRF-TOKEN',
11458
- xsrfHeaderName: 'X-XSRF-TOKEN',
11459
-
11460
- paramSerializer: '$httpParamSerializer',
11461
-
11462
- jsonpCallbackParam: 'callback'
11463
- };
11464
-
11465
- var useApplyAsync = false;
11466
- /**
11467
- * @ngdoc method
11468
- * @name $httpProvider#useApplyAsync
11469
- * @description
11470
- *
11471
- * Configure $http service to combine processing of multiple http responses received at around
11472
- * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
11473
- * significant performance improvement for bigger applications that make many HTTP requests
11474
- * concurrently (common during application bootstrap).
11475
- *
11476
- * Defaults to false. If no value is specified, returns the current configured value.
11477
- *
11478
- * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
11479
- * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
11480
- * to load and share the same digest cycle.
11481
- *
11482
- * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
11483
- * otherwise, returns the current configured value.
11484
- **/
11485
- this.useApplyAsync = function(value) {
11486
- if (isDefined(value)) {
11487
- useApplyAsync = !!value;
11488
- return this;
11489
- }
11490
- return useApplyAsync;
11491
- };
11492
-
11493
- /**
11494
- * @ngdoc property
11495
- * @name $httpProvider#interceptors
11496
- * @description
11497
- *
11498
- * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
11499
- * pre-processing of request or postprocessing of responses.
11500
- *
11501
- * These service factories are ordered by request, i.e. they are applied in the same order as the
11502
- * array, on request, but reverse order, on response.
11503
- *
11504
- * {@link ng.$http#interceptors Interceptors detailed info}
11505
- **/
11506
- var interceptorFactories = this.interceptors = [];
11507
-
11508
- this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
11509
- function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {
11510
-
11511
- var defaultCache = $cacheFactory('$http');
11512
-
11513
- /**
11514
- * Make sure that default param serializer is exposed as a function
11515
- */
11516
- defaults.paramSerializer = isString(defaults.paramSerializer) ?
11517
- $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
11518
-
11519
- /**
11520
- * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
11521
- * The reversal is needed so that we can build up the interception chain around the
11522
- * server request.
11523
- */
11524
- var reversedInterceptors = [];
11525
-
11526
- forEach(interceptorFactories, function(interceptorFactory) {
11527
- reversedInterceptors.unshift(isString(interceptorFactory)
11528
- ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
11529
- });
11530
-
11531
- /**
11532
- * @ngdoc service
11533
- * @kind function
11534
- * @name $http
11535
- * @requires ng.$httpBackend
11536
- * @requires $cacheFactory
11537
- * @requires $rootScope
11538
- * @requires $q
11539
- * @requires $injector
11540
- *
11541
- * @description
11542
- * The `$http` service is a core Angular service that facilitates communication with the remote
11543
- * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
11544
- * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
11545
- *
11546
- * For unit testing applications that use `$http` service, see
11547
- * {@link ngMock.$httpBackend $httpBackend mock}.
11548
- *
11549
- * For a higher level of abstraction, please check out the {@link ngResource.$resource
11550
- * $resource} service.
11551
- *
11552
- * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
11553
- * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
11554
- * it is important to familiarize yourself with these APIs and the guarantees they provide.
11555
- *
11556
- *
11557
- * ## General usage
11558
- * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
11559
- * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
11560
- *
11561
- * ```js
11562
- * // Simple GET request example:
11563
- * $http({
11564
- * method: 'GET',
11565
- * url: '/someUrl'
11566
- * }).then(function successCallback(response) {
11567
- * // this callback will be called asynchronously
11568
- * // when the response is available
11569
- * }, function errorCallback(response) {
11570
- * // called asynchronously if an error occurs
11571
- * // or server returns response with an error status.
11572
- * });
11573
- * ```
11574
- *
11575
- * The response object has these properties:
11576
- *
11577
- * - **data** – `{string|Object}` – The response body transformed with the transform
11578
- * functions.
11579
- * - **status** – `{number}` – HTTP status code of the response.
11580
- * - **headers** – `{function([headerName])}` – Header getter function.
11581
- * - **config** – `{Object}` – The configuration object that was used to generate the request.
11582
- * - **statusText** – `{string}` – HTTP status text of the response.
11583
- *
11584
- * A response status code between 200 and 299 is considered a success status and will result in
11585
- * the success callback being called. Any response status code outside of that range is
11586
- * considered an error status and will result in the error callback being called.
11587
- * Also, status codes less than -1 are normalized to zero. -1 usually means the request was
11588
- * aborted, e.g. using a `config.timeout`.
11589
- * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning
11590
- * that the outcome (success or error) will be determined by the final response status code.
11591
- *
11592
- *
11593
- * ## Shortcut methods
11594
- *
11595
- * Shortcut methods are also available. All shortcut methods require passing in the URL, and
11596
- * request data must be passed in for POST/PUT requests. An optional config can be passed as the
11597
- * last argument.
11598
- *
11599
- * ```js
11600
- * $http.get('/someUrl', config).then(successCallback, errorCallback);
11601
- * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
11602
- * ```
11603
- *
11604
- * Complete list of shortcut methods:
11605
- *
11606
- * - {@link ng.$http#get $http.get}
11607
- * - {@link ng.$http#head $http.head}
11608
- * - {@link ng.$http#post $http.post}
11609
- * - {@link ng.$http#put $http.put}
11610
- * - {@link ng.$http#delete $http.delete}
11611
- * - {@link ng.$http#jsonp $http.jsonp}
11612
- * - {@link ng.$http#patch $http.patch}
11613
- *
11614
- *
11615
- * ## Writing Unit Tests that use $http
11616
- * When unit testing (using {@link ngMock ngMock}), it is necessary to call
11617
- * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
11618
- * request using trained responses.
11619
- *
11620
- * ```
11621
- * $httpBackend.expectGET(...);
11622
- * $http.get(...);
11623
- * $httpBackend.flush();
11624
- * ```
11625
- *
11626
- * ## Setting HTTP Headers
11627
- *
11628
- * The $http service will automatically add certain HTTP headers to all requests. These defaults
11629
- * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
11630
- * object, which currently contains this default configuration:
11631
- *
11632
- * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
11633
- * - <code>Accept: application/json, text/plain, \*&#65279;/&#65279;\*</code>
11634
- * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
11635
- * - `Content-Type: application/json`
11636
- * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
11637
- * - `Content-Type: application/json`
11638
- *
11639
- * To add or overwrite these defaults, simply add or remove a property from these configuration
11640
- * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
11641
- * with the lowercased HTTP method name as the key, e.g.
11642
- * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
11643
- *
11644
- * The defaults can also be set at runtime via the `$http.defaults` object in the same
11645
- * fashion. For example:
11646
- *
11647
- * ```
11648
- * module.run(function($http) {
11649
- * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
11650
- * });
11651
- * ```
11652
- *
11653
- * In addition, you can supply a `headers` property in the config object passed when
11654
- * calling `$http(config)`, which overrides the defaults without changing them globally.
11655
- *
11656
- * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
11657
- * Use the `headers` property, setting the desired header to `undefined`. For example:
11658
- *
11659
- * ```js
11660
- * var req = {
11661
- * method: 'POST',
11662
- * url: 'http://example.com',
11663
- * headers: {
11664
- * 'Content-Type': undefined
11665
- * },
11666
- * data: { test: 'test' }
11667
- * }
11668
- *
11669
- * $http(req).then(function(){...}, function(){...});
11670
- * ```
11671
- *
11672
- * ## Transforming Requests and Responses
11673
- *
11674
- * Both requests and responses can be transformed using transformation functions: `transformRequest`
11675
- * and `transformResponse`. These properties can be a single function that returns
11676
- * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
11677
- * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
11678
- *
11679
- * <div class="alert alert-warning">
11680
- * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline.
11681
- * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
11682
- * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
11683
- * function will be reflected on the scope and in any templates where the object is data-bound.
11684
- * To prevent this, transform functions should have no side-effects.
11685
- * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
11686
- * </div>
11687
- *
11688
- * ### Default Transformations
11689
- *
11690
- * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
11691
- * `defaults.transformResponse` properties. If a request does not provide its own transformations
11692
- * then these will be applied.
11693
- *
11694
- * You can augment or replace the default transformations by modifying these properties by adding to or
11695
- * replacing the array.
11696
- *
11697
- * Angular provides the following default transformations:
11698
- *
11699
- * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is
11700
- * an array with one function that does the following:
11701
- *
11702
- * - If the `data` property of the request configuration object contains an object, serialize it
11703
- * into JSON format.
11704
- *
11705
- * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is
11706
- * an array with one function that does the following:
11707
- *
11708
- * - If XSRF prefix is detected, strip it (see Security Considerations section below).
11709
- * - If the `Content-Type` is `application/json` or the response looks like JSON,
11710
- * deserialize it using a JSON parser.
11711
- *
11712
- *
11713
- * ### Overriding the Default Transformations Per Request
11714
- *
11715
- * If you wish to override the request/response transformations only for a single request then provide
11716
- * `transformRequest` and/or `transformResponse` properties on the configuration object passed
11717
- * into `$http`.
11718
- *
11719
- * Note that if you provide these properties on the config object the default transformations will be
11720
- * overwritten. If you wish to augment the default transformations then you must include them in your
11721
- * local transformation array.
11722
- *
11723
- * The following code demonstrates adding a new response transformation to be run after the default response
11724
- * transformations have been run.
11725
- *
11726
- * ```js
11727
- * function appendTransform(defaults, transform) {
11728
- *
11729
- * // We can't guarantee that the default transformation is an array
11730
- * defaults = angular.isArray(defaults) ? defaults : [defaults];
11731
- *
11732
- * // Append the new transformation to the defaults
11733
- * return defaults.concat(transform);
11734
- * }
11735
- *
11736
- * $http({
11737
- * url: '...',
11738
- * method: 'GET',
11739
- * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
11740
- * return doTransform(value);
11741
- * })
11742
- * });
11743
- * ```
11744
- *
11745
- *
11746
- * ## Caching
11747
- *
11748
- * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must
11749
- * set the config.cache value or the default cache value to TRUE or to a cache object (created
11750
- * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes
11751
- * precedence over the default cache value.
11752
- *
11753
- * In order to:
11754
- * * cache all responses - set the default cache value to TRUE or to a cache object
11755
- * * cache a specific response - set config.cache value to TRUE or to a cache object
11756
- *
11757
- * If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
11758
- * then the default `$cacheFactory("$http")` object is used.
11759
- *
11760
- * The default cache value can be set by updating the
11761
- * {@link ng.$http#defaults `$http.defaults.cache`} property or the
11762
- * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property.
11763
- *
11764
- * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using
11765
- * the relevant cache object. The next time the same request is made, the response is returned
11766
- * from the cache without sending a request to the server.
11767
- *
11768
- * Take note that:
11769
- *
11770
- * * Only GET and JSONP requests are cached.
11771
- * * The cache key is the request URL including search parameters; headers are not considered.
11772
- * * Cached responses are returned asynchronously, in the same way as responses from the server.
11773
- * * If multiple identical requests are made using the same cache, which is not yet populated,
11774
- * one request will be made to the server and remaining requests will return the same response.
11775
- * * A cache-control header on the response does not affect if or how responses are cached.
11776
- *
11777
- *
11778
- * ## Interceptors
11779
- *
11780
- * Before you start creating interceptors, be sure to understand the
11781
- * {@link ng.$q $q and deferred/promise APIs}.
11782
- *
11783
- * For purposes of global error handling, authentication, or any kind of synchronous or
11784
- * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
11785
- * able to intercept requests before they are handed to the server and
11786
- * responses before they are handed over to the application code that
11787
- * initiated these requests. The interceptors leverage the {@link ng.$q
11788
- * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
11789
- *
11790
- * The interceptors are service factories that are registered with the `$httpProvider` by
11791
- * adding them to the `$httpProvider.interceptors` array. The factory is called and
11792
- * injected with dependencies (if specified) and returns the interceptor.
11793
- *
11794
- * There are two kinds of interceptors (and two kinds of rejection interceptors):
11795
- *
11796
- * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
11797
- * modify the `config` object or create a new one. The function needs to return the `config`
11798
- * object directly, or a promise containing the `config` or a new `config` object.
11799
- * * `requestError`: interceptor gets called when a previous interceptor threw an error or
11800
- * resolved with a rejection.
11801
- * * `response`: interceptors get called with http `response` object. The function is free to
11802
- * modify the `response` object or create a new one. The function needs to return the `response`
11803
- * object directly, or as a promise containing the `response` or a new `response` object.
11804
- * * `responseError`: interceptor gets called when a previous interceptor threw an error or
11805
- * resolved with a rejection.
11806
- *
11807
- *
11808
- * ```js
11809
- * // register the interceptor as a service
11810
- * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
11811
- * return {
11812
- * // optional method
11813
- * 'request': function(config) {
11814
- * // do something on success
11815
- * return config;
11816
- * },
11817
- *
11818
- * // optional method
11819
- * 'requestError': function(rejection) {
11820
- * // do something on error
11821
- * if (canRecover(rejection)) {
11822
- * return responseOrNewPromise
11823
- * }
11824
- * return $q.reject(rejection);
11825
- * },
11826
- *
11827
- *
11828
- *
11829
- * // optional method
11830
- * 'response': function(response) {
11831
- * // do something on success
11832
- * return response;
11833
- * },
11834
- *
11835
- * // optional method
11836
- * 'responseError': function(rejection) {
11837
- * // do something on error
11838
- * if (canRecover(rejection)) {
11839
- * return responseOrNewPromise
11840
- * }
11841
- * return $q.reject(rejection);
11842
- * }
11843
- * };
11844
- * });
11845
- *
11846
- * $httpProvider.interceptors.push('myHttpInterceptor');
11847
- *
11848
- *
11849
- * // alternatively, register the interceptor via an anonymous factory
11850
- * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
11851
- * return {
11852
- * 'request': function(config) {
11853
- * // same as above
11854
- * },
11855
- *
11856
- * 'response': function(response) {
11857
- * // same as above
11858
- * }
11859
- * };
11860
- * });
11861
- * ```
11862
- *
11863
- * ## Security Considerations
11864
- *
11865
- * When designing web applications, consider security threats from:
11866
- *
11867
- * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
11868
- * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
11869
- *
11870
- * Both server and the client must cooperate in order to eliminate these threats. Angular comes
11871
- * pre-configured with strategies that address these issues, but for this to work backend server
11872
- * cooperation is required.
11873
- *
11874
- * ### JSON Vulnerability Protection
11875
- *
11876
- * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
11877
- * allows third party website to turn your JSON resource URL into
11878
- * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
11879
- * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
11880
- * Angular will automatically strip the prefix before processing it as JSON.
11881
- *
11882
- * For example if your server needs to return:
11883
- * ```js
11884
- * ['one','two']
11885
- * ```
11886
- *
11887
- * which is vulnerable to attack, your server can return:
11888
- * ```js
11889
- * )]}',
11890
- * ['one','two']
11891
- * ```
11892
- *
11893
- * Angular will strip the prefix, before processing the JSON.
11894
- *
11895
- *
11896
- * ### Cross Site Request Forgery (XSRF) Protection
11897
- *
11898
- * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
11899
- * which the attacker can trick an authenticated user into unknowingly executing actions on your
11900
- * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the
11901
- * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
11902
- * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the
11903
- * cookie, your server can be assured that the XHR came from JavaScript running on your domain.
11904
- * The header will not be set for cross-domain requests.
11905
- *
11906
- * To take advantage of this, your server needs to set a token in a JavaScript readable session
11907
- * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
11908
- * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
11909
- * that only JavaScript running on your domain could have sent the request. The token must be
11910
- * unique for each user and must be verifiable by the server (to prevent the JavaScript from
11911
- * making up its own tokens). We recommend that the token is a digest of your site's
11912
- * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
11913
- * for added security.
11914
- *
11915
- * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
11916
- * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
11917
- * or the per-request config object.
11918
- *
11919
- * In order to prevent collisions in environments where multiple Angular apps share the
11920
- * same domain or subdomain, we recommend that each application uses unique cookie name.
11921
- *
11922
- * @param {object} config Object describing the request to be made and how it should be
11923
- * processed. The object has following properties:
11924
- *
11925
- * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
11926
- * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested;
11927
- * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
11928
- * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
11929
- * with the `paramSerializer` and appended as GET parameters.
11930
- * - **data** – `{string|Object}` – Data to be sent as the request message data.
11931
- * - **headers** – `{Object}` – Map of strings or functions which return strings representing
11932
- * HTTP headers to send to the server. If the return value of a function is null, the
11933
- * header will not be sent. Functions accept a config object as an argument.
11934
- * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
11935
- * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
11936
- * The handler will be called in the context of a `$apply` block.
11937
- * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
11938
- * object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
11939
- * The handler will be called in the context of a `$apply` block.
11940
- * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
11941
- * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
11942
- * - **transformRequest** –
11943
- * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
11944
- * transform function or an array of such functions. The transform function takes the http
11945
- * request body and headers and returns its transformed (typically serialized) version.
11946
- * See {@link ng.$http#overriding-the-default-transformations-per-request
11947
- * Overriding the Default Transformations}
11948
- * - **transformResponse** –
11949
- * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
11950
- * transform function or an array of such functions. The transform function takes the http
11951
- * response body, headers and status and returns its transformed (typically deserialized) version.
11952
- * See {@link ng.$http#overriding-the-default-transformations-per-request
11953
- * Overriding the Default Transformations}
11954
- * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
11955
- * prepare the string representation of request parameters (specified as an object).
11956
- * If specified as string, it is interpreted as function registered with the
11957
- * {@link $injector $injector}, which means you can create your own serializer
11958
- * by registering it as a {@link auto.$provide#service service}.
11959
- * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
11960
- * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
11961
- * - **cache** – `{boolean|Object}` – A boolean value or object created with
11962
- * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
11963
- * See {@link $http#caching $http Caching} for more information.
11964
- * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
11965
- * that should abort the request when resolved.
11966
- * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
11967
- * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
11968
- * for more information.
11969
- * - **responseType** - `{string}` - see
11970
- * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
11971
- *
11972
- * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
11973
- * when the request succeeds or fails.
11974
- *
11975
- *
11976
- * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
11977
- * requests. This is primarily meant to be used for debugging purposes.
11978
- *
11979
- *
11980
- * @example
11981
- <example module="httpExample" name="http-service">
11982
- <file name="index.html">
11983
- <div ng-controller="FetchController">
11984
- <select ng-model="method" aria-label="Request method">
11985
- <option>GET</option>
11986
- <option>JSONP</option>
11987
- </select>
11988
- <input type="text" ng-model="url" size="80" aria-label="URL" />
11989
- <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
11990
- <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
11991
- <button id="samplejsonpbtn"
11992
- ng-click="updateModel('JSONP',
11993
- 'https://angularjs.org/greet.php?name=Super%20Hero')">
11994
- Sample JSONP
11995
- </button>
11996
- <button id="invalidjsonpbtn"
11997
- ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')">
11998
- Invalid JSONP
11999
- </button>
12000
- <pre>http status code: {{status}}</pre>
12001
- <pre>http response data: {{data}}</pre>
12002
- </div>
12003
- </file>
12004
- <file name="script.js">
12005
- angular.module('httpExample', [])
12006
- .config(['$sceDelegateProvider', function($sceDelegateProvider) {
12007
- // We must whitelist the JSONP endpoint that we are using to show that we trust it
12008
- $sceDelegateProvider.resourceUrlWhitelist([
12009
- 'self',
12010
- 'https://angularjs.org/**'
12011
- ]);
12012
- }])
12013
- .controller('FetchController', ['$scope', '$http', '$templateCache',
12014
- function($scope, $http, $templateCache) {
12015
- $scope.method = 'GET';
12016
- $scope.url = 'http-hello.html';
12017
-
12018
- $scope.fetch = function() {
12019
- $scope.code = null;
12020
- $scope.response = null;
12021
-
12022
- $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
12023
- then(function(response) {
12024
- $scope.status = response.status;
12025
- $scope.data = response.data;
12026
- }, function(response) {
12027
- $scope.data = response.data || 'Request failed';
12028
- $scope.status = response.status;
12029
- });
12030
- };
12031
-
12032
- $scope.updateModel = function(method, url) {
12033
- $scope.method = method;
12034
- $scope.url = url;
12035
- };
12036
- }]);
12037
- </file>
12038
- <file name="http-hello.html">
12039
- Hello, $http!
12040
- </file>
12041
- <file name="protractor.js" type="protractor">
12042
- var status = element(by.binding('status'));
12043
- var data = element(by.binding('data'));
12044
- var fetchBtn = element(by.id('fetchbtn'));
12045
- var sampleGetBtn = element(by.id('samplegetbtn'));
12046
- var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
12047
-
12048
- it('should make an xhr GET request', function() {
12049
- sampleGetBtn.click();
12050
- fetchBtn.click();
12051
- expect(status.getText()).toMatch('200');
12052
- expect(data.getText()).toMatch(/Hello, \$http!/);
12053
- });
12054
-
12055
- // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
12056
- // it('should make a JSONP request to angularjs.org', function() {
12057
- // var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
12058
- // sampleJsonpBtn.click();
12059
- // fetchBtn.click();
12060
- // expect(status.getText()).toMatch('200');
12061
- // expect(data.getText()).toMatch(/Super Hero!/);
12062
- // });
12063
-
12064
- it('should make JSONP request to invalid URL and invoke the error handler',
12065
- function() {
12066
- invalidJsonpBtn.click();
12067
- fetchBtn.click();
12068
- expect(status.getText()).toMatch('0');
12069
- expect(data.getText()).toMatch('Request failed');
12070
- });
12071
- </file>
12072
- </example>
12073
- */
12074
- function $http(requestConfig) {
12075
-
12076
- if (!isObject(requestConfig)) {
12077
- throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
12078
- }
12079
-
12080
- if (!isString($sce.valueOf(requestConfig.url))) {
12081
- throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url);
12082
- }
12083
-
12084
- var config = extend({
12085
- method: 'get',
12086
- transformRequest: defaults.transformRequest,
12087
- transformResponse: defaults.transformResponse,
12088
- paramSerializer: defaults.paramSerializer,
12089
- jsonpCallbackParam: defaults.jsonpCallbackParam
12090
- }, requestConfig);
12091
-
12092
- config.headers = mergeHeaders(requestConfig);
12093
- config.method = uppercase(config.method);
12094
- config.paramSerializer = isString(config.paramSerializer) ?
12095
- $injector.get(config.paramSerializer) : config.paramSerializer;
12096
-
12097
- $browser.$$incOutstandingRequestCount();
12098
-
12099
- var requestInterceptors = [];
12100
- var responseInterceptors = [];
12101
- var promise = $q.resolve(config);
12102
-
12103
- // apply interceptors
12104
- forEach(reversedInterceptors, function(interceptor) {
12105
- if (interceptor.request || interceptor.requestError) {
12106
- requestInterceptors.unshift(interceptor.request, interceptor.requestError);
12107
- }
12108
- if (interceptor.response || interceptor.responseError) {
12109
- responseInterceptors.push(interceptor.response, interceptor.responseError);
12110
- }
12111
- });
12112
-
12113
- promise = chainInterceptors(promise, requestInterceptors);
12114
- promise = promise.then(serverRequest);
12115
- promise = chainInterceptors(promise, responseInterceptors);
12116
- promise = promise.finally(completeOutstandingRequest);
12117
-
12118
- return promise;
12119
-
12120
-
12121
- function chainInterceptors(promise, interceptors) {
12122
- for (var i = 0, ii = interceptors.length; i < ii;) {
12123
- var thenFn = interceptors[i++];
12124
- var rejectFn = interceptors[i++];
12125
-
12126
- promise = promise.then(thenFn, rejectFn);
12127
- }
12128
-
12129
- interceptors.length = 0;
12130
-
12131
- return promise;
12132
- }
12133
-
12134
- function completeOutstandingRequest() {
12135
- $browser.$$completeOutstandingRequest(noop);
12136
- }
12137
-
12138
- function executeHeaderFns(headers, config) {
12139
- var headerContent, processedHeaders = {};
12140
-
12141
- forEach(headers, function(headerFn, header) {
12142
- if (isFunction(headerFn)) {
12143
- headerContent = headerFn(config);
12144
- if (headerContent != null) {
12145
- processedHeaders[header] = headerContent;
12146
- }
12147
- } else {
12148
- processedHeaders[header] = headerFn;
12149
- }
12150
- });
12151
-
12152
- return processedHeaders;
12153
- }
12154
-
12155
- function mergeHeaders(config) {
12156
- var defHeaders = defaults.headers,
12157
- reqHeaders = extend({}, config.headers),
12158
- defHeaderName, lowercaseDefHeaderName, reqHeaderName;
12159
-
12160
- defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
12161
-
12162
- // using for-in instead of forEach to avoid unnecessary iteration after header has been found
12163
- defaultHeadersIteration:
12164
- for (defHeaderName in defHeaders) {
12165
- lowercaseDefHeaderName = lowercase(defHeaderName);
12166
-
12167
- for (reqHeaderName in reqHeaders) {
12168
- if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
12169
- continue defaultHeadersIteration;
12170
- }
12171
- }
12172
-
12173
- reqHeaders[defHeaderName] = defHeaders[defHeaderName];
12174
- }
12175
-
12176
- // execute if header value is a function for merged headers
12177
- return executeHeaderFns(reqHeaders, shallowCopy(config));
12178
- }
12179
-
12180
- function serverRequest(config) {
12181
- var headers = config.headers;
12182
- var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
12183
-
12184
- // strip content-type if data is undefined
12185
- if (isUndefined(reqData)) {
12186
- forEach(headers, function(value, header) {
12187
- if (lowercase(header) === 'content-type') {
12188
- delete headers[header];
12189
- }
12190
- });
12191
- }
12192
-
12193
- if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
12194
- config.withCredentials = defaults.withCredentials;
12195
- }
12196
-
12197
- // send request
12198
- return sendReq(config, reqData).then(transformResponse, transformResponse);
12199
- }
12200
-
12201
- function transformResponse(response) {
12202
- // make a copy since the response must be cacheable
12203
- var resp = extend({}, response);
12204
- resp.data = transformData(response.data, response.headers, response.status,
12205
- config.transformResponse);
12206
- return (isSuccess(response.status))
12207
- ? resp
12208
- : $q.reject(resp);
12209
- }
12210
- }
12211
-
12212
- $http.pendingRequests = [];
12213
-
12214
- /**
12215
- * @ngdoc method
12216
- * @name $http#get
12217
- *
12218
- * @description
12219
- * Shortcut method to perform `GET` request.
12220
- *
12221
- * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12222
- * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12223
- * @param {Object=} config Optional configuration object
12224
- * @returns {HttpPromise} Future object
12225
- */
12226
-
12227
- /**
12228
- * @ngdoc method
12229
- * @name $http#delete
12230
- *
12231
- * @description
12232
- * Shortcut method to perform `DELETE` request.
12233
- *
12234
- * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12235
- * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12236
- * @param {Object=} config Optional configuration object
12237
- * @returns {HttpPromise} Future object
12238
- */
12239
-
12240
- /**
12241
- * @ngdoc method
12242
- * @name $http#head
12243
- *
12244
- * @description
12245
- * Shortcut method to perform `HEAD` request.
12246
- *
12247
- * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12248
- * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12249
- * @param {Object=} config Optional configuration object
12250
- * @returns {HttpPromise} Future object
12251
- */
12252
-
12253
- /**
12254
- * @ngdoc method
12255
- * @name $http#jsonp
12256
- *
12257
- * @description
12258
- * Shortcut method to perform `JSONP` request.
12259
- *
12260
- * Note that, since JSONP requests are sensitive because the response is given full access to the browser,
12261
- * the url must be declared, via {@link $sce} as a trusted resource URL.
12262
- * You can trust a URL by adding it to the whitelist via
12263
- * {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or
12264
- * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}.
12265
- *
12266
- * JSONP requests must specify a callback to be used in the response from the server. This callback
12267
- * is passed as a query parameter in the request. You must specify the name of this parameter by
12268
- * setting the `jsonpCallbackParam` property on the request config object.
12269
- *
12270
- * ```
12271
- * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'})
12272
- * ```
12273
- *
12274
- * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`.
12275
- * Initially this is set to `'callback'`.
12276
- *
12277
- * <div class="alert alert-danger">
12278
- * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback
12279
- * parameter value should go.
12280
- * </div>
12281
- *
12282
- * If you would like to customise where and how the callbacks are stored then try overriding
12283
- * or decorating the {@link $jsonpCallbacks} service.
12284
- *
12285
- * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
12286
- * or an object created by a call to `$sce.trustAsResourceUrl(url)`.
12287
- * @param {Object=} config Optional configuration object
12288
- * @returns {HttpPromise} Future object
12289
- */
12290
- createShortMethods('get', 'delete', 'head', 'jsonp');
12291
-
12292
- /**
12293
- * @ngdoc method
12294
- * @name $http#post
12295
- *
12296
- * @description
12297
- * Shortcut method to perform `POST` request.
12298
- *
12299
- * @param {string} url Relative or absolute URL specifying the destination of the request
12300
- * @param {*} data Request content
12301
- * @param {Object=} config Optional configuration object
12302
- * @returns {HttpPromise} Future object
12303
- */
12304
-
12305
- /**
12306
- * @ngdoc method
12307
- * @name $http#put
12308
- *
12309
- * @description
12310
- * Shortcut method to perform `PUT` request.
12311
- *
12312
- * @param {string} url Relative or absolute URL specifying the destination of the request
12313
- * @param {*} data Request content
12314
- * @param {Object=} config Optional configuration object
12315
- * @returns {HttpPromise} Future object
12316
- */
12317
-
12318
- /**
12319
- * @ngdoc method
12320
- * @name $http#patch
12321
- *
12322
- * @description
12323
- * Shortcut method to perform `PATCH` request.
12324
- *
12325
- * @param {string} url Relative or absolute URL specifying the destination of the request
12326
- * @param {*} data Request content
12327
- * @param {Object=} config Optional configuration object
12328
- * @returns {HttpPromise} Future object
12329
- */
12330
- createShortMethodsWithData('post', 'put', 'patch');
12331
-
12332
- /**
12333
- * @ngdoc property
12334
- * @name $http#defaults
12335
- *
12336
- * @description
12337
- * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
12338
- * default headers, withCredentials as well as request and response transformations.
12339
- *
12340
- * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
12341
- */
12342
- $http.defaults = defaults;
12343
-
12344
-
12345
- return $http;
12346
-
12347
-
12348
- function createShortMethods(names) {
12349
- forEach(arguments, function(name) {
12350
- $http[name] = function(url, config) {
12351
- return $http(extend({}, config || {}, {
12352
- method: name,
12353
- url: url
12354
- }));
12355
- };
12356
- });
12357
- }
12358
-
12359
-
12360
- function createShortMethodsWithData(name) {
12361
- forEach(arguments, function(name) {
12362
- $http[name] = function(url, data, config) {
12363
- return $http(extend({}, config || {}, {
12364
- method: name,
12365
- url: url,
12366
- data: data
12367
- }));
12368
- };
12369
- });
12370
- }
12371
-
12372
-
12373
- /**
12374
- * Makes the request.
12375
- *
12376
- * !!! ACCESSES CLOSURE VARS:
12377
- * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
12378
- */
12379
- function sendReq(config, reqData) {
12380
- var deferred = $q.defer(),
12381
- promise = deferred.promise,
12382
- cache,
12383
- cachedResp,
12384
- reqHeaders = config.headers,
12385
- isJsonp = lowercase(config.method) === 'jsonp',
12386
- url = config.url;
12387
-
12388
- if (isJsonp) {
12389
- // JSONP is a pretty sensitive operation where we're allowing a script to have full access to
12390
- // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL.
12391
- url = $sce.getTrustedResourceUrl(url);
12392
- } else if (!isString(url)) {
12393
- // If it is not a string then the URL must be a $sce trusted object
12394
- url = $sce.valueOf(url);
12395
- }
12396
-
12397
- url = buildUrl(url, config.paramSerializer(config.params));
12398
-
12399
- if (isJsonp) {
12400
- // Check the url and add the JSONP callback placeholder
12401
- url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
12402
- }
12403
-
12404
- $http.pendingRequests.push(config);
12405
- promise.then(removePendingReq, removePendingReq);
12406
-
12407
- if ((config.cache || defaults.cache) && config.cache !== false &&
12408
- (config.method === 'GET' || config.method === 'JSONP')) {
12409
- cache = isObject(config.cache) ? config.cache
12410
- : isObject(/** @type {?} */ (defaults).cache)
12411
- ? /** @type {?} */ (defaults).cache
12412
- : defaultCache;
12413
- }
12414
-
12415
- if (cache) {
12416
- cachedResp = cache.get(url);
12417
- if (isDefined(cachedResp)) {
12418
- if (isPromiseLike(cachedResp)) {
12419
- // cached request has already been sent, but there is no response yet
12420
- cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
12421
- } else {
12422
- // serving from cache
12423
- if (isArray(cachedResp)) {
12424
- resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
12425
- } else {
12426
- resolvePromise(cachedResp, 200, {}, 'OK');
12427
- }
12428
- }
12429
- } else {
12430
- // put the promise for the non-transformed response into cache as a placeholder
12431
- cache.put(url, promise);
12432
- }
12433
- }
12434
-
12435
-
12436
- // if we won't have the response in cache, set the xsrf headers and
12437
- // send the request to the backend
12438
- if (isUndefined(cachedResp)) {
12439
- var xsrfValue = urlIsSameOrigin(config.url)
12440
- ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
12441
- : undefined;
12442
- if (xsrfValue) {
12443
- reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
12444
- }
12445
-
12446
- $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
12447
- config.withCredentials, config.responseType,
12448
- createApplyHandlers(config.eventHandlers),
12449
- createApplyHandlers(config.uploadEventHandlers));
12450
- }
12451
-
12452
- return promise;
12453
-
12454
- function createApplyHandlers(eventHandlers) {
12455
- if (eventHandlers) {
12456
- var applyHandlers = {};
12457
- forEach(eventHandlers, function(eventHandler, key) {
12458
- applyHandlers[key] = function(event) {
12459
- if (useApplyAsync) {
12460
- $rootScope.$applyAsync(callEventHandler);
12461
- } else if ($rootScope.$$phase) {
12462
- callEventHandler();
12463
- } else {
12464
- $rootScope.$apply(callEventHandler);
12465
- }
12466
-
12467
- function callEventHandler() {
12468
- eventHandler(event);
12469
- }
12470
- };
12471
- });
12472
- return applyHandlers;
12473
- }
12474
- }
12475
-
12476
-
12477
- /**
12478
- * Callback registered to $httpBackend():
12479
- * - caches the response if desired
12480
- * - resolves the raw $http promise
12481
- * - calls $apply
12482
- */
12483
- function done(status, response, headersString, statusText) {
12484
- if (cache) {
12485
- if (isSuccess(status)) {
12486
- cache.put(url, [status, response, parseHeaders(headersString), statusText]);
12487
- } else {
12488
- // remove promise from the cache
12489
- cache.remove(url);
12490
- }
12491
- }
12492
-
12493
- function resolveHttpPromise() {
12494
- resolvePromise(response, status, headersString, statusText);
12495
- }
12496
-
12497
- if (useApplyAsync) {
12498
- $rootScope.$applyAsync(resolveHttpPromise);
12499
- } else {
12500
- resolveHttpPromise();
12501
- if (!$rootScope.$$phase) $rootScope.$apply();
12502
- }
12503
- }
12504
-
12505
-
12506
- /**
12507
- * Resolves the raw $http promise.
12508
- */
12509
- function resolvePromise(response, status, headers, statusText) {
12510
- //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
12511
- status = status >= -1 ? status : 0;
12512
-
12513
- (isSuccess(status) ? deferred.resolve : deferred.reject)({
12514
- data: response,
12515
- status: status,
12516
- headers: headersGetter(headers),
12517
- config: config,
12518
- statusText: statusText
12519
- });
12520
- }
12521
-
12522
- function resolvePromiseWithResult(result) {
12523
- resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
12524
- }
12525
-
12526
- function removePendingReq() {
12527
- var idx = $http.pendingRequests.indexOf(config);
12528
- if (idx !== -1) $http.pendingRequests.splice(idx, 1);
12529
- }
12530
- }
12531
-
12532
-
12533
- function buildUrl(url, serializedParams) {
12534
- if (serializedParams.length > 0) {
12535
- url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
12536
- }
12537
- return url;
12538
- }
12539
-
12540
- function sanitizeJsonpCallbackParam(url, key) {
12541
- if (/[&?][^=]+=JSON_CALLBACK/.test(url)) {
12542
- // Throw if the url already contains a reference to JSON_CALLBACK
12543
- throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url);
12544
- }
12545
-
12546
- var callbackParamRegex = new RegExp('[&?]' + key + '=');
12547
- if (callbackParamRegex.test(url)) {
12548
- // Throw if the callback param was already provided
12549
- throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', key, url);
12550
- }
12551
-
12552
- // Add in the JSON_CALLBACK callback param value
12553
- url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK';
12554
-
12555
- return url;
12556
- }
12557
- }];
12558
- }
12559
-
12560
- /**
12561
- * @ngdoc service
12562
- * @name $xhrFactory
12563
- * @this
12564
- *
12565
- * @description
12566
- * Factory function used to create XMLHttpRequest objects.
12567
- *
12568
- * Replace or decorate this service to create your own custom XMLHttpRequest objects.
12569
- *
12570
- * ```
12571
- * angular.module('myApp', [])
12572
- * .factory('$xhrFactory', function() {
12573
- * return function createXhr(method, url) {
12574
- * return new window.XMLHttpRequest({mozSystem: true});
12575
- * };
12576
- * });
12577
- * ```
12578
- *
12579
- * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
12580
- * @param {string} url URL of the request.
12581
- */
12582
- function $xhrFactoryProvider() {
12583
- this.$get = function() {
12584
- return function createXhr() {
12585
- return new window.XMLHttpRequest();
12586
- };
12587
- };
12588
- }
12589
-
12590
- /**
12591
- * @ngdoc service
12592
- * @name $httpBackend
12593
- * @requires $jsonpCallbacks
12594
- * @requires $document
12595
- * @requires $xhrFactory
12596
- * @this
12597
- *
12598
- * @description
12599
- * HTTP backend used by the {@link ng.$http service} that delegates to
12600
- * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
12601
- *
12602
- * You should never need to use this service directly, instead use the higher-level abstractions:
12603
- * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
12604
- *
12605
- * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
12606
- * $httpBackend} which can be trained with responses.
12607
- */
12608
- function $HttpBackendProvider() {
12609
- this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) {
12610
- return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]);
12611
- }];
12612
- }
12613
-
12614
- function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
12615
- // TODO(vojta): fix the signature
12616
- return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
12617
- url = url || $browser.url();
12618
-
12619
- if (lowercase(method) === 'jsonp') {
12620
- var callbackPath = callbacks.createCallback(url);
12621
- var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
12622
- // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
12623
- var response = (status === 200) && callbacks.getResponse(callbackPath);
12624
- completeRequest(callback, status, response, '', text);
12625
- callbacks.removeCallback(callbackPath);
12626
- });
12627
- } else {
12628
-
12629
- var xhr = createXhr(method, url);
12630
-
12631
- xhr.open(method, url, true);
12632
- forEach(headers, function(value, key) {
12633
- if (isDefined(value)) {
12634
- xhr.setRequestHeader(key, value);
12635
- }
12636
- });
12637
-
12638
- xhr.onload = function requestLoaded() {
12639
- var statusText = xhr.statusText || '';
12640
-
12641
- // responseText is the old-school way of retrieving response (supported by IE9)
12642
- // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
12643
- var response = ('response' in xhr) ? xhr.response : xhr.responseText;
12644
-
12645
- // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
12646
- var status = xhr.status === 1223 ? 204 : xhr.status;
12647
-
12648
- // fix status code when it is 0 (0 status is undocumented).
12649
- // Occurs when accessing file resources or on Android 4.1 stock browser
12650
- // while retrieving files from application cache.
12651
- if (status === 0) {
12652
- status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0;
12653
- }
12654
-
12655
- completeRequest(callback,
12656
- status,
12657
- response,
12658
- xhr.getAllResponseHeaders(),
12659
- statusText);
12660
- };
12661
-
12662
- var requestError = function() {
12663
- // The response is always empty
12664
- // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
12665
- completeRequest(callback, -1, null, null, '');
12666
- };
12667
-
12668
- xhr.onerror = requestError;
12669
- xhr.onabort = requestError;
12670
- xhr.ontimeout = requestError;
12671
-
12672
- forEach(eventHandlers, function(value, key) {
12673
- xhr.addEventListener(key, value);
12674
- });
12675
-
12676
- forEach(uploadEventHandlers, function(value, key) {
12677
- xhr.upload.addEventListener(key, value);
12678
- });
12679
-
12680
- if (withCredentials) {
12681
- xhr.withCredentials = true;
12682
- }
12683
-
12684
- if (responseType) {
12685
- try {
12686
- xhr.responseType = responseType;
12687
- } catch (e) {
12688
- // WebKit added support for the json responseType value on 09/03/2013
12689
- // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
12690
- // known to throw when setting the value "json" as the response type. Other older
12691
- // browsers implementing the responseType
12692
- //
12693
- // The json response type can be ignored if not supported, because JSON payloads are
12694
- // parsed on the client-side regardless.
12695
- if (responseType !== 'json') {
12696
- throw e;
12697
- }
12698
- }
12699
- }
12700
-
12701
- xhr.send(isUndefined(post) ? null : post);
12702
- }
12703
-
12704
- if (timeout > 0) {
12705
- var timeoutId = $browserDefer(timeoutRequest, timeout);
12706
- } else if (isPromiseLike(timeout)) {
12707
- timeout.then(timeoutRequest);
12708
- }
12709
-
12710
-
12711
- function timeoutRequest() {
12712
- if (jsonpDone) {
12713
- jsonpDone();
12714
- }
12715
- if (xhr) {
12716
- xhr.abort();
12717
- }
12718
- }
12719
-
12720
- function completeRequest(callback, status, response, headersString, statusText) {
12721
- // cancel timeout and subsequent timeout promise resolution
12722
- if (isDefined(timeoutId)) {
12723
- $browserDefer.cancel(timeoutId);
12724
- }
12725
- jsonpDone = xhr = null;
12726
-
12727
- callback(status, response, headersString, statusText);
12728
- }
12729
- };
12730
-
12731
- function jsonpReq(url, callbackPath, done) {
12732
- url = url.replace('JSON_CALLBACK', callbackPath);
12733
- // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
12734
- // - fetches local scripts via XHR and evals them
12735
- // - adds and immediately removes script elements from the document
12736
- var script = rawDocument.createElement('script'), callback = null;
12737
- script.type = 'text/javascript';
12738
- script.src = url;
12739
- script.async = true;
12740
-
12741
- callback = function(event) {
12742
- script.removeEventListener('load', callback);
12743
- script.removeEventListener('error', callback);
12744
- rawDocument.body.removeChild(script);
12745
- script = null;
12746
- var status = -1;
12747
- var text = 'unknown';
12748
-
12749
- if (event) {
12750
- if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) {
12751
- event = { type: 'error' };
12752
- }
12753
- text = event.type;
12754
- status = event.type === 'error' ? 404 : 200;
12755
- }
12756
-
12757
- if (done) {
12758
- done(status, text);
12759
- }
12760
- };
12761
-
12762
- script.addEventListener('load', callback);
12763
- script.addEventListener('error', callback);
12764
- rawDocument.body.appendChild(script);
12765
- return callback;
12766
- }
12767
- }
12768
-
12769
- var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
12770
- $interpolateMinErr.throwNoconcat = function(text) {
12771
- throw $interpolateMinErr('noconcat',
12772
- 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' +
12773
- 'interpolations that concatenate multiple expressions when a trusted value is ' +
12774
- 'required. See http://docs.angularjs.org/api/ng.$sce', text);
12775
- };
12776
-
12777
- $interpolateMinErr.interr = function(text, err) {
12778
- return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString());
12779
- };
12780
-
12781
- /**
12782
- * @ngdoc provider
12783
- * @name $interpolateProvider
12784
- * @this
12785
- *
12786
- * @description
12787
- *
12788
- * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
12789
- *
12790
- * <div class="alert alert-danger">
12791
- * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
12792
- * template within a Python Jinja template (or any other template language). Mixing templating
12793
- * languages is **very dangerous**. The embedding template language will not safely escape Angular
12794
- * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
12795
- * security bugs!
12796
- * </div>
12797
- *
12798
- * @example
12799
- <example name="custom-interpolation-markup" module="customInterpolationApp">
12800
- <file name="index.html">
12801
- <script>
12802
- var customInterpolationApp = angular.module('customInterpolationApp', []);
12803
-
12804
- customInterpolationApp.config(function($interpolateProvider) {
12805
- $interpolateProvider.startSymbol('//');
12806
- $interpolateProvider.endSymbol('//');
12807
- });
12808
-
12809
-
12810
- customInterpolationApp.controller('DemoController', function() {
12811
- this.label = "This binding is brought you by // interpolation symbols.";
12812
- });
12813
- </script>
12814
- <div ng-controller="DemoController as demo">
12815
- //demo.label//
12816
- </div>
12817
- </file>
12818
- <file name="protractor.js" type="protractor">
12819
- it('should interpolate binding with custom symbols', function() {
12820
- expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
12821
- });
12822
- </file>
12823
- </example>
12824
- */
12825
- function $InterpolateProvider() {
12826
- var startSymbol = '{{';
12827
- var endSymbol = '}}';
12828
-
12829
- /**
12830
- * @ngdoc method
12831
- * @name $interpolateProvider#startSymbol
12832
- * @description
12833
- * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
12834
- *
12835
- * @param {string=} value new value to set the starting symbol to.
12836
- * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
12837
- */
12838
- this.startSymbol = function(value) {
12839
- if (value) {
12840
- startSymbol = value;
12841
- return this;
12842
- } else {
12843
- return startSymbol;
12844
- }
12845
- };
12846
-
12847
- /**
12848
- * @ngdoc method
12849
- * @name $interpolateProvider#endSymbol
12850
- * @description
12851
- * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
12852
- *
12853
- * @param {string=} value new value to set the ending symbol to.
12854
- * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
12855
- */
12856
- this.endSymbol = function(value) {
12857
- if (value) {
12858
- endSymbol = value;
12859
- return this;
12860
- } else {
12861
- return endSymbol;
12862
- }
12863
- };
12864
-
12865
-
12866
- this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
12867
- var startSymbolLength = startSymbol.length,
12868
- endSymbolLength = endSymbol.length,
12869
- escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
12870
- escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
12871
-
12872
- function escape(ch) {
12873
- return '\\\\\\' + ch;
12874
- }
12875
-
12876
- function unescapeText(text) {
12877
- return text.replace(escapedStartRegexp, startSymbol).
12878
- replace(escapedEndRegexp, endSymbol);
12879
- }
12880
-
12881
- // TODO: this is the same as the constantWatchDelegate in parse.js
12882
- function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
12883
- var unwatch = scope.$watch(function constantInterpolateWatch(scope) {
12884
- unwatch();
12885
- return constantInterp(scope);
12886
- }, listener, objectEquality);
12887
- return unwatch;
12888
- }
12889
-
12890
- /**
12891
- * @ngdoc service
12892
- * @name $interpolate
12893
- * @kind function
12894
- *
12895
- * @requires $parse
12896
- * @requires $sce
12897
- *
12898
- * @description
12899
- *
12900
- * Compiles a string with markup into an interpolation function. This service is used by the
12901
- * HTML {@link ng.$compile $compile} service for data binding. See
12902
- * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
12903
- * interpolation markup.
12904
- *
12905
- *
12906
- * ```js
12907
- * var $interpolate = ...; // injected
12908
- * var exp = $interpolate('Hello {{name | uppercase}}!');
12909
- * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
12910
- * ```
12911
- *
12912
- * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
12913
- * `true`, the interpolation function will return `undefined` unless all embedded expressions
12914
- * evaluate to a value other than `undefined`.
12915
- *
12916
- * ```js
12917
- * var $interpolate = ...; // injected
12918
- * var context = {greeting: 'Hello', name: undefined };
12919
- *
12920
- * // default "forgiving" mode
12921
- * var exp = $interpolate('{{greeting}} {{name}}!');
12922
- * expect(exp(context)).toEqual('Hello !');
12923
- *
12924
- * // "allOrNothing" mode
12925
- * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
12926
- * expect(exp(context)).toBeUndefined();
12927
- * context.name = 'Angular';
12928
- * expect(exp(context)).toEqual('Hello Angular!');
12929
- * ```
12930
- *
12931
- * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
12932
- *
12933
- * #### Escaped Interpolation
12934
- * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
12935
- * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
12936
- * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
12937
- * or binding.
12938
- *
12939
- * This enables web-servers to prevent script injection attacks and defacing attacks, to some
12940
- * degree, while also enabling code examples to work without relying on the
12941
- * {@link ng.directive:ngNonBindable ngNonBindable} directive.
12942
- *
12943
- * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
12944
- * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
12945
- * interpolation start/end markers with their escaped counterparts.**
12946
- *
12947
- * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
12948
- * output when the $interpolate service processes the text. So, for HTML elements interpolated
12949
- * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
12950
- * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
12951
- * this is typically useful only when user-data is used in rendering a template from the server, or
12952
- * when otherwise untrusted data is used by a directive.
12953
- *
12954
- * <example name="interpolation">
12955
- * <file name="index.html">
12956
- * <div ng-init="username='A user'">
12957
- * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
12958
- * </p>
12959
- * <p><strong>{{username}}</strong> attempts to inject code which will deface the
12960
- * application, but fails to accomplish their task, because the server has correctly
12961
- * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
12962
- * characters.</p>
12963
- * <p>Instead, the result of the attempted script injection is visible, and can be removed
12964
- * from the database by an administrator.</p>
12965
- * </div>
12966
- * </file>
12967
- * </example>
12968
- *
12969
- * @knownIssue
12970
- * It is currently not possible for an interpolated expression to contain the interpolation end
12971
- * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
12972
- * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
12973
- *
12974
- * @knownIssue
12975
- * All directives and components must use the standard `{{` `}}` interpolation symbols
12976
- * in their templates. If you change the application interpolation symbols the {@link $compile}
12977
- * service will attempt to denormalize the standard symbols to the custom symbols.
12978
- * The denormalization process is not clever enough to know not to replace instances of the standard
12979
- * symbols where they would not normally be treated as interpolation symbols. For example in the following
12980
- * code snippet the closing braces of the literal object will get incorrectly denormalized:
12981
- *
12982
- * ```
12983
- * <div data-context='{"context":{"id":3,"type":"page"}}">
12984
- * ```
12985
- *
12986
- * The workaround is to ensure that such instances are separated by whitespace:
12987
- * ```
12988
- * <div data-context='{"context":{"id":3,"type":"page"} }">
12989
- * ```
12990
- *
12991
- * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information.
12992
- *
12993
- * @param {string} text The text with markup to interpolate.
12994
- * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
12995
- * embedded expression in order to return an interpolation function. Strings with no
12996
- * embedded expression will return null for the interpolation function.
12997
- * @param {string=} trustedContext when provided, the returned function passes the interpolated
12998
- * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
12999
- * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
13000
- * provides Strict Contextual Escaping for details.
13001
- * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
13002
- * unless all embedded expressions evaluate to a value other than `undefined`.
13003
- * @returns {function(context)} an interpolation function which is used to compute the
13004
- * interpolated string. The function has these parameters:
13005
- *
13006
- * - `context`: evaluation context for all expressions embedded in the interpolated text
13007
- */
13008
- function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
13009
- // Provide a quick exit and simplified result function for text with no interpolation
13010
- if (!text.length || text.indexOf(startSymbol) === -1) {
13011
- var constantInterp;
13012
- if (!mustHaveExpression) {
13013
- var unescapedText = unescapeText(text);
13014
- constantInterp = valueFn(unescapedText);
13015
- constantInterp.exp = text;
13016
- constantInterp.expressions = [];
13017
- constantInterp.$$watchDelegate = constantWatchDelegate;
13018
- }
13019
- return constantInterp;
13020
- }
13021
-
13022
- allOrNothing = !!allOrNothing;
13023
- var startIndex,
13024
- endIndex,
13025
- index = 0,
13026
- expressions = [],
13027
- parseFns = [],
13028
- textLength = text.length,
13029
- exp,
13030
- concat = [],
13031
- expressionPositions = [];
13032
-
13033
- while (index < textLength) {
13034
- if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
13035
- ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
13036
- if (index !== startIndex) {
13037
- concat.push(unescapeText(text.substring(index, startIndex)));
13038
- }
13039
- exp = text.substring(startIndex + startSymbolLength, endIndex);
13040
- expressions.push(exp);
13041
- parseFns.push($parse(exp, parseStringifyInterceptor));
13042
- index = endIndex + endSymbolLength;
13043
- expressionPositions.push(concat.length);
13044
- concat.push('');
13045
- } else {
13046
- // we did not find an interpolation, so we have to add the remainder to the separators array
13047
- if (index !== textLength) {
13048
- concat.push(unescapeText(text.substring(index)));
13049
- }
13050
- break;
13051
- }
13052
- }
13053
-
13054
- // Concatenating expressions makes it hard to reason about whether some combination of
13055
- // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
13056
- // single expression be used for iframe[src], object[src], etc., we ensure that the value
13057
- // that's used is assigned or constructed by some JS code somewhere that is more testable or
13058
- // make it obvious that you bound the value to some user controlled value. This helps reduce
13059
- // the load when auditing for XSS issues.
13060
- if (trustedContext && concat.length > 1) {
13061
- $interpolateMinErr.throwNoconcat(text);
13062
- }
13063
-
13064
- if (!mustHaveExpression || expressions.length) {
13065
- var compute = function(values) {
13066
- for (var i = 0, ii = expressions.length; i < ii; i++) {
13067
- if (allOrNothing && isUndefined(values[i])) return;
13068
- concat[expressionPositions[i]] = values[i];
13069
- }
13070
- return concat.join('');
13071
- };
13072
-
13073
- var getValue = function(value) {
13074
- return trustedContext ?
13075
- $sce.getTrusted(trustedContext, value) :
13076
- $sce.valueOf(value);
13077
- };
13078
-
13079
- return extend(function interpolationFn(context) {
13080
- var i = 0;
13081
- var ii = expressions.length;
13082
- var values = new Array(ii);
13083
-
13084
- try {
13085
- for (; i < ii; i++) {
13086
- values[i] = parseFns[i](context);
13087
- }
13088
-
13089
- return compute(values);
13090
- } catch (err) {
13091
- $exceptionHandler($interpolateMinErr.interr(text, err));
13092
- }
13093
-
13094
- }, {
13095
- // all of these properties are undocumented for now
13096
- exp: text, //just for compatibility with regular watchers created via $watch
13097
- expressions: expressions,
13098
- $$watchDelegate: function(scope, listener) {
13099
- var lastValue;
13100
- return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) {
13101
- var currValue = compute(values);
13102
- if (isFunction(listener)) {
13103
- listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
13104
- }
13105
- lastValue = currValue;
13106
- });
13107
- }
13108
- });
13109
- }
13110
-
13111
- function parseStringifyInterceptor(value) {
13112
- try {
13113
- value = getValue(value);
13114
- return allOrNothing && !isDefined(value) ? value : stringify(value);
13115
- } catch (err) {
13116
- $exceptionHandler($interpolateMinErr.interr(text, err));
13117
- }
13118
- }
13119
- }
13120
-
13121
-
13122
- /**
13123
- * @ngdoc method
13124
- * @name $interpolate#startSymbol
13125
- * @description
13126
- * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
13127
- *
13128
- * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
13129
- * the symbol.
13130
- *
13131
- * @returns {string} start symbol.
13132
- */
13133
- $interpolate.startSymbol = function() {
13134
- return startSymbol;
13135
- };
13136
-
13137
-
13138
- /**
13139
- * @ngdoc method
13140
- * @name $interpolate#endSymbol
13141
- * @description
13142
- * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
13143
- *
13144
- * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
13145
- * the symbol.
13146
- *
13147
- * @returns {string} end symbol.
13148
- */
13149
- $interpolate.endSymbol = function() {
13150
- return endSymbol;
13151
- };
13152
-
13153
- return $interpolate;
13154
- }];
13155
- }
13156
-
13157
- /** @this */
13158
- function $IntervalProvider() {
13159
- this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
13160
- function($rootScope, $window, $q, $$q, $browser) {
13161
- var intervals = {};
13162
-
13163
-
13164
- /**
13165
- * @ngdoc service
13166
- * @name $interval
13167
- *
13168
- * @description
13169
- * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
13170
- * milliseconds.
13171
- *
13172
- * The return value of registering an interval function is a promise. This promise will be
13173
- * notified upon each tick of the interval, and will be resolved after `count` iterations, or
13174
- * run indefinitely if `count` is not defined. The value of the notification will be the
13175
- * number of iterations that have run.
13176
- * To cancel an interval, call `$interval.cancel(promise)`.
13177
- *
13178
- * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
13179
- * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
13180
- * time.
13181
- *
13182
- * <div class="alert alert-warning">
13183
- * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
13184
- * with them. In particular they are not automatically destroyed when a controller's scope or a
13185
- * directive's element are destroyed.
13186
- * You should take this into consideration and make sure to always cancel the interval at the
13187
- * appropriate moment. See the example below for more details on how and when to do this.
13188
- * </div>
13189
- *
13190
- * @param {function()} fn A function that should be called repeatedly. If no additional arguments
13191
- * are passed (see below), the function is called with the current iteration count.
13192
- * @param {number} delay Number of milliseconds between each function call.
13193
- * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
13194
- * indefinitely.
13195
- * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
13196
- * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
13197
- * @param {...*=} Pass additional parameters to the executed function.
13198
- * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete.
13199
- *
13200
- * @example
13201
- * <example module="intervalExample" name="interval-service">
13202
- * <file name="index.html">
13203
- * <script>
13204
- * angular.module('intervalExample', [])
13205
- * .controller('ExampleController', ['$scope', '$interval',
13206
- * function($scope, $interval) {
13207
- * $scope.format = 'M/d/yy h:mm:ss a';
13208
- * $scope.blood_1 = 100;
13209
- * $scope.blood_2 = 120;
13210
- *
13211
- * var stop;
13212
- * $scope.fight = function() {
13213
- * // Don't start a new fight if we are already fighting
13214
- * if ( angular.isDefined(stop) ) return;
13215
- *
13216
- * stop = $interval(function() {
13217
- * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
13218
- * $scope.blood_1 = $scope.blood_1 - 3;
13219
- * $scope.blood_2 = $scope.blood_2 - 4;
13220
- * } else {
13221
- * $scope.stopFight();
13222
- * }
13223
- * }, 100);
13224
- * };
13225
- *
13226
- * $scope.stopFight = function() {
13227
- * if (angular.isDefined(stop)) {
13228
- * $interval.cancel(stop);
13229
- * stop = undefined;
13230
- * }
13231
- * };
13232
- *
13233
- * $scope.resetFight = function() {
13234
- * $scope.blood_1 = 100;
13235
- * $scope.blood_2 = 120;
13236
- * };
13237
- *
13238
- * $scope.$on('$destroy', function() {
13239
- * // Make sure that the interval is destroyed too
13240
- * $scope.stopFight();
13241
- * });
13242
- * }])
13243
- * // Register the 'myCurrentTime' directive factory method.
13244
- * // We inject $interval and dateFilter service since the factory method is DI.
13245
- * .directive('myCurrentTime', ['$interval', 'dateFilter',
13246
- * function($interval, dateFilter) {
13247
- * // return the directive link function. (compile function not needed)
13248
- * return function(scope, element, attrs) {
13249
- * var format, // date format
13250
- * stopTime; // so that we can cancel the time updates
13251
- *
13252
- * // used to update the UI
13253
- * function updateTime() {
13254
- * element.text(dateFilter(new Date(), format));
13255
- * }
13256
- *
13257
- * // watch the expression, and update the UI on change.
13258
- * scope.$watch(attrs.myCurrentTime, function(value) {
13259
- * format = value;
13260
- * updateTime();
13261
- * });
13262
- *
13263
- * stopTime = $interval(updateTime, 1000);
13264
- *
13265
- * // listen on DOM destroy (removal) event, and cancel the next UI update
13266
- * // to prevent updating time after the DOM element was removed.
13267
- * element.on('$destroy', function() {
13268
- * $interval.cancel(stopTime);
13269
- * });
13270
- * }
13271
- * }]);
13272
- * </script>
13273
- *
13274
- * <div>
13275
- * <div ng-controller="ExampleController">
13276
- * <label>Date format: <input ng-model="format"></label> <hr/>
13277
- * Current time is: <span my-current-time="format"></span>
13278
- * <hr/>
13279
- * Blood 1 : <font color='red'>{{blood_1}}</font>
13280
- * Blood 2 : <font color='red'>{{blood_2}}</font>
13281
- * <button type="button" data-ng-click="fight()">Fight</button>
13282
- * <button type="button" data-ng-click="stopFight()">StopFight</button>
13283
- * <button type="button" data-ng-click="resetFight()">resetFight</button>
13284
- * </div>
13285
- * </div>
13286
- *
13287
- * </file>
13288
- * </example>
13289
- */
13290
- function interval(fn, delay, count, invokeApply) {
13291
- var hasParams = arguments.length > 4,
13292
- args = hasParams ? sliceArgs(arguments, 4) : [],
13293
- setInterval = $window.setInterval,
13294
- clearInterval = $window.clearInterval,
13295
- iteration = 0,
13296
- skipApply = (isDefined(invokeApply) && !invokeApply),
13297
- deferred = (skipApply ? $$q : $q).defer(),
13298
- promise = deferred.promise;
13299
-
13300
- count = isDefined(count) ? count : 0;
13301
-
13302
- promise.$$intervalId = setInterval(function tick() {
13303
- if (skipApply) {
13304
- $browser.defer(callback);
13305
- } else {
13306
- $rootScope.$evalAsync(callback);
13307
- }
13308
- deferred.notify(iteration++);
13309
-
13310
- if (count > 0 && iteration >= count) {
13311
- deferred.resolve(iteration);
13312
- clearInterval(promise.$$intervalId);
13313
- delete intervals[promise.$$intervalId];
13314
- }
13315
-
13316
- if (!skipApply) $rootScope.$apply();
13317
-
13318
- }, delay);
13319
-
13320
- intervals[promise.$$intervalId] = deferred;
13321
-
13322
- return promise;
13323
-
13324
- function callback() {
13325
- if (!hasParams) {
13326
- fn(iteration);
13327
- } else {
13328
- fn.apply(null, args);
13329
- }
13330
- }
13331
- }
13332
-
13333
-
13334
- /**
13335
- * @ngdoc method
13336
- * @name $interval#cancel
13337
- *
13338
- * @description
13339
- * Cancels a task associated with the `promise`.
13340
- *
13341
- * @param {Promise=} promise returned by the `$interval` function.
13342
- * @returns {boolean} Returns `true` if the task was successfully canceled.
13343
- */
13344
- interval.cancel = function(promise) {
13345
- if (promise && promise.$$intervalId in intervals) {
13346
- // Interval cancels should not report as unhandled promise.
13347
- markQExceptionHandled(intervals[promise.$$intervalId].promise);
13348
- intervals[promise.$$intervalId].reject('canceled');
13349
- $window.clearInterval(promise.$$intervalId);
13350
- delete intervals[promise.$$intervalId];
13351
- return true;
13352
- }
13353
- return false;
13354
- };
13355
-
13356
- return interval;
13357
- }];
13358
- }
13359
-
13360
- /**
13361
- * @ngdoc service
13362
- * @name $jsonpCallbacks
13363
- * @requires $window
13364
- * @description
13365
- * This service handles the lifecycle of callbacks to handle JSONP requests.
13366
- * Override this service if you wish to customise where the callbacks are stored and
13367
- * how they vary compared to the requested url.
13368
- */
13369
- var $jsonpCallbacksProvider = /** @this */ function() {
13370
- this.$get = function() {
13371
- var callbacks = angular.callbacks;
13372
- var callbackMap = {};
13373
-
13374
- function createCallback(callbackId) {
13375
- var callback = function(data) {
13376
- callback.data = data;
13377
- callback.called = true;
13378
- };
13379
- callback.id = callbackId;
13380
- return callback;
13381
- }
13382
-
13383
- return {
13384
- /**
13385
- * @ngdoc method
13386
- * @name $jsonpCallbacks#createCallback
13387
- * @param {string} url the url of the JSONP request
13388
- * @returns {string} the callback path to send to the server as part of the JSONP request
13389
- * @description
13390
- * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
13391
- * to pass to the server, which will be used to call the callback with its payload in the JSONP response.
13392
- */
13393
- createCallback: function(url) {
13394
- var callbackId = '_' + (callbacks.$$counter++).toString(36);
13395
- var callbackPath = 'angular.callbacks.' + callbackId;
13396
- var callback = createCallback(callbackId);
13397
- callbackMap[callbackPath] = callbacks[callbackId] = callback;
13398
- return callbackPath;
13399
- },
13400
- /**
13401
- * @ngdoc method
13402
- * @name $jsonpCallbacks#wasCalled
13403
- * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13404
- * @returns {boolean} whether the callback has been called, as a result of the JSONP response
13405
- * @description
13406
- * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
13407
- * callback that was passed in the request.
13408
- */
13409
- wasCalled: function(callbackPath) {
13410
- return callbackMap[callbackPath].called;
13411
- },
13412
- /**
13413
- * @ngdoc method
13414
- * @name $jsonpCallbacks#getResponse
13415
- * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13416
- * @returns {*} the data received from the response via the registered callback
13417
- * @description
13418
- * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
13419
- * in the JSONP response.
13420
- */
13421
- getResponse: function(callbackPath) {
13422
- return callbackMap[callbackPath].data;
13423
- },
13424
- /**
13425
- * @ngdoc method
13426
- * @name $jsonpCallbacks#removeCallback
13427
- * @param {string} callbackPath the path to the callback that was sent in the JSONP request
13428
- * @description
13429
- * {@link $httpBackend} calls this method to remove the callback after the JSONP request has
13430
- * completed or timed-out.
13431
- */
13432
- removeCallback: function(callbackPath) {
13433
- var callback = callbackMap[callbackPath];
13434
- delete callbacks[callback.id];
13435
- delete callbackMap[callbackPath];
13436
- }
13437
- };
13438
- };
13439
- };
13440
-
13441
- /**
13442
- * @ngdoc service
13443
- * @name $locale
13444
- *
13445
- * @description
13446
- * $locale service provides localization rules for various Angular components. As of right now the
13447
- * only public api is:
13448
- *
13449
- * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
13450
- */
13451
-
13452
- var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
13453
- DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
13454
- var $locationMinErr = minErr('$location');
13455
-
13456
-
13457
- /**
13458
- * Encode path using encodeUriSegment, ignoring forward slashes
13459
- *
13460
- * @param {string} path Path to encode
13461
- * @returns {string}
13462
- */
13463
- function encodePath(path) {
13464
- var segments = path.split('/'),
13465
- i = segments.length;
13466
-
13467
- while (i--) {
13468
- segments[i] = encodeUriSegment(segments[i]);
13469
- }
13470
-
13471
- return segments.join('/');
13472
- }
13473
-
13474
- function parseAbsoluteUrl(absoluteUrl, locationObj) {
13475
- var parsedUrl = urlResolve(absoluteUrl);
13476
-
13477
- locationObj.$$protocol = parsedUrl.protocol;
13478
- locationObj.$$host = parsedUrl.hostname;
13479
- locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
13480
- }
13481
-
13482
- var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
13483
- function parseAppUrl(url, locationObj) {
13484
-
13485
- if (DOUBLE_SLASH_REGEX.test(url)) {
13486
- throw $locationMinErr('badpath', 'Invalid url "{0}".', url);
13487
- }
13488
-
13489
- var prefixed = (url.charAt(0) !== '/');
13490
- if (prefixed) {
13491
- url = '/' + url;
13492
- }
13493
- var match = urlResolve(url);
13494
- locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
13495
- match.pathname.substring(1) : match.pathname);
13496
- locationObj.$$search = parseKeyValue(match.search);
13497
- locationObj.$$hash = decodeURIComponent(match.hash);
13498
-
13499
- // make sure path starts with '/';
13500
- if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') {
13501
- locationObj.$$path = '/' + locationObj.$$path;
13502
- }
13503
- }
13504
-
13505
- function startsWith(str, search) {
13506
- return str.slice(0, search.length) === search;
13507
- }
13508
-
13509
- /**
13510
- *
13511
- * @param {string} base
13512
- * @param {string} url
13513
- * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
13514
- * the expected string.
13515
- */
13516
- function stripBaseUrl(base, url) {
13517
- if (startsWith(url, base)) {
13518
- return url.substr(base.length);
13519
- }
13520
- }
13521
-
13522
-
13523
- function stripHash(url) {
13524
- var index = url.indexOf('#');
13525
- return index === -1 ? url : url.substr(0, index);
13526
- }
13527
-
13528
- function trimEmptyHash(url) {
13529
- return url.replace(/(#.+)|#$/, '$1');
13530
- }
13531
-
13532
-
13533
- function stripFile(url) {
13534
- return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
13535
- }
13536
-
13537
- /* return the server only (scheme://host:port) */
13538
- function serverBase(url) {
13539
- return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
13540
- }
13541
-
13542
-
13543
- /**
13544
- * LocationHtml5Url represents a URL
13545
- * This object is exposed as $location service when HTML5 mode is enabled and supported
13546
- *
13547
- * @constructor
13548
- * @param {string} appBase application base URL
13549
- * @param {string} appBaseNoFile application base URL stripped of any filename
13550
- * @param {string} basePrefix URL path prefix
13551
- */
13552
- function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
13553
- this.$$html5 = true;
13554
- basePrefix = basePrefix || '';
13555
- parseAbsoluteUrl(appBase, this);
13556
-
13557
-
13558
- /**
13559
- * Parse given HTML5 (regular) URL string into properties
13560
- * @param {string} url HTML5 URL
13561
- * @private
13562
- */
13563
- this.$$parse = function(url) {
13564
- var pathUrl = stripBaseUrl(appBaseNoFile, url);
13565
- if (!isString(pathUrl)) {
13566
- throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
13567
- appBaseNoFile);
13568
- }
13569
-
13570
- parseAppUrl(pathUrl, this);
13571
-
13572
- if (!this.$$path) {
13573
- this.$$path = '/';
13574
- }
13575
-
13576
- this.$$compose();
13577
- };
13578
-
13579
- /**
13580
- * Compose url and update `absUrl` property
13581
- * @private
13582
- */
13583
- this.$$compose = function() {
13584
- var search = toKeyValue(this.$$search),
13585
- hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13586
-
13587
- this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13588
- this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
13589
-
13590
- this.$$urlUpdatedByLocation = true;
13591
- };
13592
-
13593
- this.$$parseLinkUrl = function(url, relHref) {
13594
- if (relHref && relHref[0] === '#') {
13595
- // special case for links to hash fragments:
13596
- // keep the old url and only replace the hash fragment
13597
- this.hash(relHref.slice(1));
13598
- return true;
13599
- }
13600
- var appUrl, prevAppUrl;
13601
- var rewrittenUrl;
13602
-
13603
-
13604
- if (isDefined(appUrl = stripBaseUrl(appBase, url))) {
13605
- prevAppUrl = appUrl;
13606
- if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) {
13607
- rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl);
13608
- } else {
13609
- rewrittenUrl = appBase + prevAppUrl;
13610
- }
13611
- } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) {
13612
- rewrittenUrl = appBaseNoFile + appUrl;
13613
- } else if (appBaseNoFile === url + '/') {
13614
- rewrittenUrl = appBaseNoFile;
13615
- }
13616
- if (rewrittenUrl) {
13617
- this.$$parse(rewrittenUrl);
13618
- }
13619
- return !!rewrittenUrl;
13620
- };
13621
- }
13622
-
13623
-
13624
- /**
13625
- * LocationHashbangUrl represents URL
13626
- * This object is exposed as $location service when developer doesn't opt into html5 mode.
13627
- * It also serves as the base class for html5 mode fallback on legacy browsers.
13628
- *
13629
- * @constructor
13630
- * @param {string} appBase application base URL
13631
- * @param {string} appBaseNoFile application base URL stripped of any filename
13632
- * @param {string} hashPrefix hashbang prefix
13633
- */
13634
- function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
13635
-
13636
- parseAbsoluteUrl(appBase, this);
13637
-
13638
-
13639
- /**
13640
- * Parse given hashbang URL into properties
13641
- * @param {string} url Hashbang URL
13642
- * @private
13643
- */
13644
- this.$$parse = function(url) {
13645
- var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url);
13646
- var withoutHashUrl;
13647
-
13648
- if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
13649
-
13650
- // The rest of the URL starts with a hash so we have
13651
- // got either a hashbang path or a plain hash fragment
13652
- withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
13653
- if (isUndefined(withoutHashUrl)) {
13654
- // There was no hashbang prefix so we just have a hash fragment
13655
- withoutHashUrl = withoutBaseUrl;
13656
- }
13657
-
13658
- } else {
13659
- // There was no hashbang path nor hash fragment:
13660
- // If we are in HTML5 mode we use what is left as the path;
13661
- // Otherwise we ignore what is left
13662
- if (this.$$html5) {
13663
- withoutHashUrl = withoutBaseUrl;
13664
- } else {
13665
- withoutHashUrl = '';
13666
- if (isUndefined(withoutBaseUrl)) {
13667
- appBase = url;
13668
- /** @type {?} */ (this).replace();
13669
- }
13670
- }
13671
- }
13672
-
13673
- parseAppUrl(withoutHashUrl, this);
13674
-
13675
- this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
13676
-
13677
- this.$$compose();
13678
-
13679
- /*
13680
- * In Windows, on an anchor node on documents loaded from
13681
- * the filesystem, the browser will return a pathname
13682
- * prefixed with the drive name ('/C:/path') when a
13683
- * pathname without a drive is set:
13684
- * * a.setAttribute('href', '/foo')
13685
- * * a.pathname === '/C:/foo' //true
13686
- *
13687
- * Inside of Angular, we're always using pathnames that
13688
- * do not include drive names for routing.
13689
- */
13690
- function removeWindowsDriveName(path, url, base) {
13691
- /*
13692
- Matches paths for file protocol on windows,
13693
- such as /C:/foo/bar, and captures only /foo/bar.
13694
- */
13695
- var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
13696
-
13697
- var firstPathSegmentMatch;
13698
-
13699
- //Get the relative path from the input URL.
13700
- if (startsWith(url, base)) {
13701
- url = url.replace(base, '');
13702
- }
13703
-
13704
- // The input URL intentionally contains a first path segment that ends with a colon.
13705
- if (windowsFilePathExp.exec(url)) {
13706
- return path;
13707
- }
13708
-
13709
- firstPathSegmentMatch = windowsFilePathExp.exec(path);
13710
- return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
13711
- }
13712
- };
13713
-
13714
- /**
13715
- * Compose hashbang URL and update `absUrl` property
13716
- * @private
13717
- */
13718
- this.$$compose = function() {
13719
- var search = toKeyValue(this.$$search),
13720
- hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13721
-
13722
- this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13723
- this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
13724
-
13725
- this.$$urlUpdatedByLocation = true;
13726
- };
13727
-
13728
- this.$$parseLinkUrl = function(url, relHref) {
13729
- if (stripHash(appBase) === stripHash(url)) {
13730
- this.$$parse(url);
13731
- return true;
13732
- }
13733
- return false;
13734
- };
13735
- }
13736
-
13737
-
13738
- /**
13739
- * LocationHashbangUrl represents URL
13740
- * This object is exposed as $location service when html5 history api is enabled but the browser
13741
- * does not support it.
13742
- *
13743
- * @constructor
13744
- * @param {string} appBase application base URL
13745
- * @param {string} appBaseNoFile application base URL stripped of any filename
13746
- * @param {string} hashPrefix hashbang prefix
13747
- */
13748
- function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
13749
- this.$$html5 = true;
13750
- LocationHashbangUrl.apply(this, arguments);
13751
-
13752
- this.$$parseLinkUrl = function(url, relHref) {
13753
- if (relHref && relHref[0] === '#') {
13754
- // special case for links to hash fragments:
13755
- // keep the old url and only replace the hash fragment
13756
- this.hash(relHref.slice(1));
13757
- return true;
13758
- }
13759
-
13760
- var rewrittenUrl;
13761
- var appUrl;
13762
-
13763
- if (appBase === stripHash(url)) {
13764
- rewrittenUrl = url;
13765
- } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) {
13766
- rewrittenUrl = appBase + hashPrefix + appUrl;
13767
- } else if (appBaseNoFile === url + '/') {
13768
- rewrittenUrl = appBaseNoFile;
13769
- }
13770
- if (rewrittenUrl) {
13771
- this.$$parse(rewrittenUrl);
13772
- }
13773
- return !!rewrittenUrl;
13774
- };
13775
-
13776
- this.$$compose = function() {
13777
- var search = toKeyValue(this.$$search),
13778
- hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
13779
-
13780
- this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
13781
- // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
13782
- this.$$absUrl = appBase + hashPrefix + this.$$url;
13783
-
13784
- this.$$urlUpdatedByLocation = true;
13785
- };
13786
-
13787
- }
13788
-
13789
-
13790
- var locationPrototype = {
13791
-
13792
- /**
13793
- * Ensure absolute URL is initialized.
13794
- * @private
13795
- */
13796
- $$absUrl:'',
13797
-
13798
- /**
13799
- * Are we in html5 mode?
13800
- * @private
13801
- */
13802
- $$html5: false,
13803
-
13804
- /**
13805
- * Has any change been replacing?
13806
- * @private
13807
- */
13808
- $$replace: false,
13809
-
13810
- /**
13811
- * @ngdoc method
13812
- * @name $location#absUrl
13813
- *
13814
- * @description
13815
- * This method is getter only.
13816
- *
13817
- * Return full URL representation with all segments encoded according to rules specified in
13818
- * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
13819
- *
13820
- *
13821
- * ```js
13822
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13823
- * var absUrl = $location.absUrl();
13824
- * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
13825
- * ```
13826
- *
13827
- * @return {string} full URL
13828
- */
13829
- absUrl: locationGetter('$$absUrl'),
13830
-
13831
- /**
13832
- * @ngdoc method
13833
- * @name $location#url
13834
- *
13835
- * @description
13836
- * This method is getter / setter.
13837
- *
13838
- * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
13839
- *
13840
- * Change path, search and hash, when called with parameter and return `$location`.
13841
- *
13842
- *
13843
- * ```js
13844
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13845
- * var url = $location.url();
13846
- * // => "/some/path?foo=bar&baz=xoxo"
13847
- * ```
13848
- *
13849
- * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
13850
- * @return {string} url
13851
- */
13852
- url: function(url) {
13853
- if (isUndefined(url)) {
13854
- return this.$$url;
13855
- }
13856
-
13857
- var match = PATH_MATCH.exec(url);
13858
- if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
13859
- if (match[2] || match[1] || url === '') this.search(match[3] || '');
13860
- this.hash(match[5] || '');
13861
-
13862
- return this;
13863
- },
13864
-
13865
- /**
13866
- * @ngdoc method
13867
- * @name $location#protocol
13868
- *
13869
- * @description
13870
- * This method is getter only.
13871
- *
13872
- * Return protocol of current URL.
13873
- *
13874
- *
13875
- * ```js
13876
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13877
- * var protocol = $location.protocol();
13878
- * // => "http"
13879
- * ```
13880
- *
13881
- * @return {string} protocol of current URL
13882
- */
13883
- protocol: locationGetter('$$protocol'),
13884
-
13885
- /**
13886
- * @ngdoc method
13887
- * @name $location#host
13888
- *
13889
- * @description
13890
- * This method is getter only.
13891
- *
13892
- * Return host of current URL.
13893
- *
13894
- * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
13895
- *
13896
- *
13897
- * ```js
13898
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13899
- * var host = $location.host();
13900
- * // => "example.com"
13901
- *
13902
- * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
13903
- * host = $location.host();
13904
- * // => "example.com"
13905
- * host = location.host;
13906
- * // => "example.com:8080"
13907
- * ```
13908
- *
13909
- * @return {string} host of current URL.
13910
- */
13911
- host: locationGetter('$$host'),
13912
-
13913
- /**
13914
- * @ngdoc method
13915
- * @name $location#port
13916
- *
13917
- * @description
13918
- * This method is getter only.
13919
- *
13920
- * Return port of current URL.
13921
- *
13922
- *
13923
- * ```js
13924
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13925
- * var port = $location.port();
13926
- * // => 80
13927
- * ```
13928
- *
13929
- * @return {Number} port
13930
- */
13931
- port: locationGetter('$$port'),
13932
-
13933
- /**
13934
- * @ngdoc method
13935
- * @name $location#path
13936
- *
13937
- * @description
13938
- * This method is getter / setter.
13939
- *
13940
- * Return path of current URL when called without any parameter.
13941
- *
13942
- * Change path when called with parameter and return `$location`.
13943
- *
13944
- * Note: Path should always begin with forward slash (/), this method will add the forward slash
13945
- * if it is missing.
13946
- *
13947
- *
13948
- * ```js
13949
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13950
- * var path = $location.path();
13951
- * // => "/some/path"
13952
- * ```
13953
- *
13954
- * @param {(string|number)=} path New path
13955
- * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
13956
- */
13957
- path: locationGetterSetter('$$path', function(path) {
13958
- path = path !== null ? path.toString() : '';
13959
- return path.charAt(0) === '/' ? path : '/' + path;
13960
- }),
13961
-
13962
- /**
13963
- * @ngdoc method
13964
- * @name $location#search
13965
- *
13966
- * @description
13967
- * This method is getter / setter.
13968
- *
13969
- * Return search part (as object) of current URL when called without any parameter.
13970
- *
13971
- * Change search part when called with parameter and return `$location`.
13972
- *
13973
- *
13974
- * ```js
13975
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
13976
- * var searchObject = $location.search();
13977
- * // => {foo: 'bar', baz: 'xoxo'}
13978
- *
13979
- * // set foo to 'yipee'
13980
- * $location.search('foo', 'yipee');
13981
- * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
13982
- * ```
13983
- *
13984
- * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
13985
- * hash object.
13986
- *
13987
- * When called with a single argument the method acts as a setter, setting the `search` component
13988
- * of `$location` to the specified value.
13989
- *
13990
- * If the argument is a hash object containing an array of values, these values will be encoded
13991
- * as duplicate search parameters in the URL.
13992
- *
13993
- * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
13994
- * will override only a single search property.
13995
- *
13996
- * If `paramValue` is an array, it will override the property of the `search` component of
13997
- * `$location` specified via the first argument.
13998
- *
13999
- * If `paramValue` is `null`, the property specified via the first argument will be deleted.
14000
- *
14001
- * If `paramValue` is `true`, the property specified via the first argument will be added with no
14002
- * value nor trailing equal sign.
14003
- *
14004
- * @return {Object} If called with no arguments returns the parsed `search` object. If called with
14005
- * one or more arguments returns `$location` object itself.
14006
- */
14007
- search: function(search, paramValue) {
14008
- switch (arguments.length) {
14009
- case 0:
14010
- return this.$$search;
14011
- case 1:
14012
- if (isString(search) || isNumber(search)) {
14013
- search = search.toString();
14014
- this.$$search = parseKeyValue(search);
14015
- } else if (isObject(search)) {
14016
- search = copy(search, {});
14017
- // remove object undefined or null properties
14018
- forEach(search, function(value, key) {
14019
- if (value == null) delete search[key];
14020
- });
14021
-
14022
- this.$$search = search;
14023
- } else {
14024
- throw $locationMinErr('isrcharg',
14025
- 'The first argument of the `$location#search()` call must be a string or an object.');
14026
- }
14027
- break;
14028
- default:
14029
- if (isUndefined(paramValue) || paramValue === null) {
14030
- delete this.$$search[search];
14031
- } else {
14032
- this.$$search[search] = paramValue;
14033
- }
14034
- }
14035
-
14036
- this.$$compose();
14037
- return this;
14038
- },
14039
-
14040
- /**
14041
- * @ngdoc method
14042
- * @name $location#hash
14043
- *
14044
- * @description
14045
- * This method is getter / setter.
14046
- *
14047
- * Returns the hash fragment when called without any parameters.
14048
- *
14049
- * Changes the hash fragment when called with a parameter and returns `$location`.
14050
- *
14051
- *
14052
- * ```js
14053
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
14054
- * var hash = $location.hash();
14055
- * // => "hashValue"
14056
- * ```
14057
- *
14058
- * @param {(string|number)=} hash New hash fragment
14059
- * @return {string} hash
14060
- */
14061
- hash: locationGetterSetter('$$hash', function(hash) {
14062
- return hash !== null ? hash.toString() : '';
14063
- }),
14064
-
14065
- /**
14066
- * @ngdoc method
14067
- * @name $location#replace
14068
- *
14069
- * @description
14070
- * If called, all changes to $location during the current `$digest` will replace the current history
14071
- * record, instead of adding a new one.
14072
- */
14073
- replace: function() {
14074
- this.$$replace = true;
14075
- return this;
14076
- }
14077
- };
14078
-
14079
- forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
14080
- Location.prototype = Object.create(locationPrototype);
14081
-
14082
- /**
14083
- * @ngdoc method
14084
- * @name $location#state
14085
- *
14086
- * @description
14087
- * This method is getter / setter.
14088
- *
14089
- * Return the history state object when called without any parameter.
14090
- *
14091
- * Change the history state object when called with one parameter and return `$location`.
14092
- * The state object is later passed to `pushState` or `replaceState`.
14093
- *
14094
- * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
14095
- * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
14096
- * older browsers (like IE9 or Android < 4.0), don't use this method.
14097
- *
14098
- * @param {object=} state State object for pushState or replaceState
14099
- * @return {object} state
14100
- */
14101
- Location.prototype.state = function(state) {
14102
- if (!arguments.length) {
14103
- return this.$$state;
14104
- }
14105
-
14106
- if (Location !== LocationHtml5Url || !this.$$html5) {
14107
- throw $locationMinErr('nostate', 'History API state support is available only ' +
14108
- 'in HTML5 mode and only in browsers supporting HTML5 History API');
14109
- }
14110
- // The user might modify `stateObject` after invoking `$location.state(stateObject)`
14111
- // but we're changing the $$state reference to $browser.state() during the $digest
14112
- // so the modification window is narrow.
14113
- this.$$state = isUndefined(state) ? null : state;
14114
- this.$$urlUpdatedByLocation = true;
14115
-
14116
- return this;
14117
- };
14118
- });
14119
-
14120
-
14121
- function locationGetter(property) {
14122
- return /** @this */ function() {
14123
- return this[property];
14124
- };
14125
- }
14126
-
14127
-
14128
- function locationGetterSetter(property, preprocess) {
14129
- return /** @this */ function(value) {
14130
- if (isUndefined(value)) {
14131
- return this[property];
14132
- }
14133
-
14134
- this[property] = preprocess(value);
14135
- this.$$compose();
14136
-
14137
- return this;
14138
- };
14139
- }
14140
-
14141
-
14142
- /**
14143
- * @ngdoc service
14144
- * @name $location
14145
- *
14146
- * @requires $rootElement
14147
- *
14148
- * @description
14149
- * The $location service parses the URL in the browser address bar (based on the
14150
- * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
14151
- * available to your application. Changes to the URL in the address bar are reflected into
14152
- * $location service and changes to $location are reflected into the browser address bar.
14153
- *
14154
- * **The $location service:**
14155
- *
14156
- * - Exposes the current URL in the browser address bar, so you can
14157
- * - Watch and observe the URL.
14158
- * - Change the URL.
14159
- * - Synchronizes the URL with the browser when the user
14160
- * - Changes the address bar.
14161
- * - Clicks the back or forward button (or clicks a History link).
14162
- * - Clicks on a link.
14163
- * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
14164
- *
14165
- * For more information see {@link guide/$location Developer Guide: Using $location}
14166
- */
14167
-
14168
- /**
14169
- * @ngdoc provider
14170
- * @name $locationProvider
14171
- * @this
14172
- *
14173
- * @description
14174
- * Use the `$locationProvider` to configure how the application deep linking paths are stored.
14175
- */
14176
- function $LocationProvider() {
14177
- var hashPrefix = '!',
14178
- html5Mode = {
14179
- enabled: false,
14180
- requireBase: true,
14181
- rewriteLinks: true
14182
- };
14183
-
14184
- /**
14185
- * @ngdoc method
14186
- * @name $locationProvider#hashPrefix
14187
- * @description
14188
- * The default value for the prefix is `'!'`.
14189
- * @param {string=} prefix Prefix for hash part (containing path and search)
14190
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
14191
- */
14192
- this.hashPrefix = function(prefix) {
14193
- if (isDefined(prefix)) {
14194
- hashPrefix = prefix;
14195
- return this;
14196
- } else {
14197
- return hashPrefix;
14198
- }
14199
- };
14200
-
14201
- /**
14202
- * @ngdoc method
14203
- * @name $locationProvider#html5Mode
14204
- * @description
14205
- * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
14206
- * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
14207
- * properties:
14208
- * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
14209
- * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
14210
- * support `pushState`.
14211
- * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
14212
- * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
14213
- * true, and a base tag is not present, an error will be thrown when `$location` is injected.
14214
- * See the {@link guide/$location $location guide for more information}
14215
- * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
14216
- * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
14217
- * only happen on links with an attribute that matches the given string. For example, if set
14218
- * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
14219
- * Note that [attribute name normalization](guide/directive#normalization) does not apply
14220
- * here, so `'internalLink'` will **not** match `'internal-link'`.
14221
- *
14222
- * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
14223
- */
14224
- this.html5Mode = function(mode) {
14225
- if (isBoolean(mode)) {
14226
- html5Mode.enabled = mode;
14227
- return this;
14228
- } else if (isObject(mode)) {
14229
-
14230
- if (isBoolean(mode.enabled)) {
14231
- html5Mode.enabled = mode.enabled;
14232
- }
14233
-
14234
- if (isBoolean(mode.requireBase)) {
14235
- html5Mode.requireBase = mode.requireBase;
14236
- }
14237
-
14238
- if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
14239
- html5Mode.rewriteLinks = mode.rewriteLinks;
14240
- }
14241
-
14242
- return this;
14243
- } else {
14244
- return html5Mode;
14245
- }
14246
- };
14247
-
14248
- /**
14249
- * @ngdoc event
14250
- * @name $location#$locationChangeStart
14251
- * @eventType broadcast on root scope
14252
- * @description
14253
- * Broadcasted before a URL will change.
14254
- *
14255
- * This change can be prevented by calling
14256
- * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
14257
- * details about event object. Upon successful change
14258
- * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
14259
- *
14260
- * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
14261
- * the browser supports the HTML5 History API.
14262
- *
14263
- * @param {Object} angularEvent Synthetic event object.
14264
- * @param {string} newUrl New URL
14265
- * @param {string=} oldUrl URL that was before it was changed.
14266
- * @param {string=} newState New history state object
14267
- * @param {string=} oldState History state object that was before it was changed.
14268
- */
14269
-
14270
- /**
14271
- * @ngdoc event
14272
- * @name $location#$locationChangeSuccess
14273
- * @eventType broadcast on root scope
14274
- * @description
14275
- * Broadcasted after a URL was changed.
14276
- *
14277
- * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
14278
- * the browser supports the HTML5 History API.
14279
- *
14280
- * @param {Object} angularEvent Synthetic event object.
14281
- * @param {string} newUrl New URL
14282
- * @param {string=} oldUrl URL that was before it was changed.
14283
- * @param {string=} newState New history state object
14284
- * @param {string=} oldState History state object that was before it was changed.
14285
- */
14286
-
14287
- this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
14288
- function($rootScope, $browser, $sniffer, $rootElement, $window) {
14289
- var $location,
14290
- LocationMode,
14291
- baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
14292
- initialUrl = $browser.url(),
14293
- appBase;
14294
-
14295
- if (html5Mode.enabled) {
14296
- if (!baseHref && html5Mode.requireBase) {
14297
- throw $locationMinErr('nobase',
14298
- '$location in HTML5 mode requires a <base> tag to be present!');
14299
- }
14300
- appBase = serverBase(initialUrl) + (baseHref || '/');
14301
- LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
14302
- } else {
14303
- appBase = stripHash(initialUrl);
14304
- LocationMode = LocationHashbangUrl;
14305
- }
14306
- var appBaseNoFile = stripFile(appBase);
14307
-
14308
- $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
14309
- $location.$$parseLinkUrl(initialUrl, initialUrl);
14310
-
14311
- $location.$$state = $browser.state();
14312
-
14313
- var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
14314
-
14315
- function setBrowserUrlWithFallback(url, replace, state) {
14316
- var oldUrl = $location.url();
14317
- var oldState = $location.$$state;
14318
- try {
14319
- $browser.url(url, replace, state);
14320
-
14321
- // Make sure $location.state() returns referentially identical (not just deeply equal)
14322
- // state object; this makes possible quick checking if the state changed in the digest
14323
- // loop. Checking deep equality would be too expensive.
14324
- $location.$$state = $browser.state();
14325
- } catch (e) {
14326
- // Restore old values if pushState fails
14327
- $location.url(oldUrl);
14328
- $location.$$state = oldState;
14329
-
14330
- throw e;
14331
- }
14332
- }
14333
-
14334
- $rootElement.on('click', function(event) {
14335
- var rewriteLinks = html5Mode.rewriteLinks;
14336
- // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
14337
- // currently we open nice url link and redirect then
14338
-
14339
- if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;
14340
-
14341
- var elm = jqLite(event.target);
14342
-
14343
- // traverse the DOM up to find first A tag
14344
- while (nodeName_(elm[0]) !== 'a') {
14345
- // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
14346
- if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
14347
- }
14348
-
14349
- if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return;
14350
-
14351
- var absHref = elm.prop('href');
14352
- // get the actual href attribute - see
14353
- // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
14354
- var relHref = elm.attr('href') || elm.attr('xlink:href');
14355
-
14356
- if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
14357
- // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
14358
- // an animation.
14359
- absHref = urlResolve(absHref.animVal).href;
14360
- }
14361
-
14362
- // Ignore when url is started with javascript: or mailto:
14363
- if (IGNORE_URI_REGEXP.test(absHref)) return;
14364
-
14365
- if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
14366
- if ($location.$$parseLinkUrl(absHref, relHref)) {
14367
- // We do a preventDefault for all urls that are part of the angular application,
14368
- // in html5mode and also without, so that we are able to abort navigation without
14369
- // getting double entries in the location history.
14370
- event.preventDefault();
14371
- // update location manually
14372
- if ($location.absUrl() !== $browser.url()) {
14373
- $rootScope.$apply();
14374
- // hack to work around FF6 bug 684208 when scenario runner clicks on links
14375
- $window.angular['ff-684208-preventDefault'] = true;
14376
- }
14377
- }
14378
- }
14379
- });
14380
-
14381
-
14382
- // rewrite hashbang url <> html5 url
14383
- if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
14384
- $browser.url($location.absUrl(), true);
14385
- }
14386
-
14387
- var initializing = true;
14388
-
14389
- // update $location when $browser url changes
14390
- $browser.onUrlChange(function(newUrl, newState) {
14391
-
14392
- if (!startsWith(newUrl, appBaseNoFile)) {
14393
- // If we are navigating outside of the app then force a reload
14394
- $window.location.href = newUrl;
14395
- return;
14396
- }
14397
-
14398
- $rootScope.$evalAsync(function() {
14399
- var oldUrl = $location.absUrl();
14400
- var oldState = $location.$$state;
14401
- var defaultPrevented;
14402
- newUrl = trimEmptyHash(newUrl);
14403
- $location.$$parse(newUrl);
14404
- $location.$$state = newState;
14405
-
14406
- defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
14407
- newState, oldState).defaultPrevented;
14408
-
14409
- // if the location was changed by a `$locationChangeStart` handler then stop
14410
- // processing this location change
14411
- if ($location.absUrl() !== newUrl) return;
14412
-
14413
- if (defaultPrevented) {
14414
- $location.$$parse(oldUrl);
14415
- $location.$$state = oldState;
14416
- setBrowserUrlWithFallback(oldUrl, false, oldState);
14417
- } else {
14418
- initializing = false;
14419
- afterLocationChange(oldUrl, oldState);
14420
- }
14421
- });
14422
- if (!$rootScope.$$phase) $rootScope.$digest();
14423
- });
14424
-
14425
- // update browser
14426
- $rootScope.$watch(function $locationWatch() {
14427
- if (initializing || $location.$$urlUpdatedByLocation) {
14428
- $location.$$urlUpdatedByLocation = false;
14429
-
14430
- var oldUrl = trimEmptyHash($browser.url());
14431
- var newUrl = trimEmptyHash($location.absUrl());
14432
- var oldState = $browser.state();
14433
- var currentReplace = $location.$$replace;
14434
- var urlOrStateChanged = oldUrl !== newUrl ||
14435
- ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
14436
-
14437
- if (initializing || urlOrStateChanged) {
14438
- initializing = false;
14439
-
14440
- $rootScope.$evalAsync(function() {
14441
- var newUrl = $location.absUrl();
14442
- var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
14443
- $location.$$state, oldState).defaultPrevented;
14444
-
14445
- // if the location was changed by a `$locationChangeStart` handler then stop
14446
- // processing this location change
14447
- if ($location.absUrl() !== newUrl) return;
14448
-
14449
- if (defaultPrevented) {
14450
- $location.$$parse(oldUrl);
14451
- $location.$$state = oldState;
14452
- } else {
14453
- if (urlOrStateChanged) {
14454
- setBrowserUrlWithFallback(newUrl, currentReplace,
14455
- oldState === $location.$$state ? null : $location.$$state);
14456
- }
14457
- afterLocationChange(oldUrl, oldState);
14458
- }
14459
- });
14460
- }
14461
- }
14462
-
14463
- $location.$$replace = false;
14464
-
14465
- // we don't need to return anything because $evalAsync will make the digest loop dirty when
14466
- // there is a change
14467
- });
14468
-
14469
- return $location;
14470
-
14471
- function afterLocationChange(oldUrl, oldState) {
14472
- $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
14473
- $location.$$state, oldState);
14474
- }
14475
- }];
14476
- }
14477
-
14478
- /**
14479
- * @ngdoc service
14480
- * @name $log
14481
- * @requires $window
14482
- *
14483
- * @description
14484
- * Simple service for logging. Default implementation safely writes the message
14485
- * into the browser's console (if present).
14486
- *
14487
- * The main purpose of this service is to simplify debugging and troubleshooting.
14488
- *
14489
- * To reveal the location of the calls to `$log` in the JavaScript console,
14490
- * you can "blackbox" the AngularJS source in your browser:
14491
- *
14492
- * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source).
14493
- * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing).
14494
- *
14495
- * Note: Not all browsers support blackboxing.
14496
- *
14497
- * The default is to log `debug` messages. You can use
14498
- * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
14499
- *
14500
- * @example
14501
- <example module="logExample" name="log-service">
14502
- <file name="script.js">
14503
- angular.module('logExample', [])
14504
- .controller('LogController', ['$scope', '$log', function($scope, $log) {
14505
- $scope.$log = $log;
14506
- $scope.message = 'Hello World!';
14507
- }]);
14508
- </file>
14509
- <file name="index.html">
14510
- <div ng-controller="LogController">
14511
- <p>Reload this page with open console, enter text and hit the log button...</p>
14512
- <label>Message:
14513
- <input type="text" ng-model="message" /></label>
14514
- <button ng-click="$log.log(message)">log</button>
14515
- <button ng-click="$log.warn(message)">warn</button>
14516
- <button ng-click="$log.info(message)">info</button>
14517
- <button ng-click="$log.error(message)">error</button>
14518
- <button ng-click="$log.debug(message)">debug</button>
14519
- </div>
14520
- </file>
14521
- </example>
14522
- */
14523
-
14524
- /**
14525
- * @ngdoc provider
14526
- * @name $logProvider
14527
- * @this
14528
- *
14529
- * @description
14530
- * Use the `$logProvider` to configure how the application logs messages
14531
- */
14532
- function $LogProvider() {
14533
- var debug = true,
14534
- self = this;
14535
-
14536
- /**
14537
- * @ngdoc method
14538
- * @name $logProvider#debugEnabled
14539
- * @description
14540
- * @param {boolean=} flag enable or disable debug level messages
14541
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
14542
- */
14543
- this.debugEnabled = function(flag) {
14544
- if (isDefined(flag)) {
14545
- debug = flag;
14546
- return this;
14547
- } else {
14548
- return debug;
14549
- }
14550
- };
14551
-
14552
- this.$get = ['$window', function($window) {
14553
- // Support: IE 9-11, Edge 12-14+
14554
- // IE/Edge display errors in such a way that it requires the user to click in 4 places
14555
- // to see the stack trace. There is no way to feature-detect it so there's a chance
14556
- // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't
14557
- // break apps. Other browsers display errors in a sensible way and some of them map stack
14558
- // traces along source maps if available so it makes sense to let browsers display it
14559
- // as they want.
14560
- var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent);
14561
-
14562
- return {
14563
- /**
14564
- * @ngdoc method
14565
- * @name $log#log
14566
- *
14567
- * @description
14568
- * Write a log message
14569
- */
14570
- log: consoleLog('log'),
14571
-
14572
- /**
14573
- * @ngdoc method
14574
- * @name $log#info
14575
- *
14576
- * @description
14577
- * Write an information message
14578
- */
14579
- info: consoleLog('info'),
14580
-
14581
- /**
14582
- * @ngdoc method
14583
- * @name $log#warn
14584
- *
14585
- * @description
14586
- * Write a warning message
14587
- */
14588
- warn: consoleLog('warn'),
14589
-
14590
- /**
14591
- * @ngdoc method
14592
- * @name $log#error
14593
- *
14594
- * @description
14595
- * Write an error message
14596
- */
14597
- error: consoleLog('error'),
14598
-
14599
- /**
14600
- * @ngdoc method
14601
- * @name $log#debug
14602
- *
14603
- * @description
14604
- * Write a debug message
14605
- */
14606
- debug: (function() {
14607
- var fn = consoleLog('debug');
14608
-
14609
- return function() {
14610
- if (debug) {
14611
- fn.apply(self, arguments);
14612
- }
14613
- };
14614
- })()
14615
- };
14616
-
14617
- function formatError(arg) {
14618
- if (isError(arg)) {
14619
- if (arg.stack && formatStackTrace) {
14620
- arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
14621
- ? 'Error: ' + arg.message + '\n' + arg.stack
14622
- : arg.stack;
14623
- } else if (arg.sourceURL) {
14624
- arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
14625
- }
14626
- }
14627
- return arg;
14628
- }
14629
-
14630
- function consoleLog(type) {
14631
- var console = $window.console || {},
14632
- logFn = console[type] || console.log || noop;
14633
-
14634
- return function() {
14635
- var args = [];
14636
- forEach(arguments, function(arg) {
14637
- args.push(formatError(arg));
14638
- });
14639
- // Support: IE 9 only
14640
- // console methods don't inherit from Function.prototype in IE 9 so we can't
14641
- // call `logFn.apply(console, args)` directly.
14642
- return Function.prototype.apply.call(logFn, console, args);
14643
- };
14644
- }
14645
- }];
14646
- }
14647
-
14648
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14649
- * Any commits to this file should be reviewed with security in mind. *
14650
- * Changes to this file can potentially create security vulnerabilities. *
14651
- * An approval from 2 Core members with history of modifying *
14652
- * this file is required. *
14653
- * *
14654
- * Does the change somehow allow for arbitrary javascript to be executed? *
14655
- * Or allows for someone to change the prototype of built-in objects? *
14656
- * Or gives undesired access to variables likes document or window? *
14657
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
14658
-
14659
- var $parseMinErr = minErr('$parse');
14660
-
14661
- var objectValueOf = {}.constructor.prototype.valueOf;
14662
-
14663
- // Sandboxing Angular Expressions
14664
- // ------------------------------
14665
- // Angular expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by
14666
- // various means such as obtaining a reference to native JS functions like the Function constructor.
14667
- //
14668
- // As an example, consider the following Angular expression:
14669
- //
14670
- // {}.toString.constructor('alert("evil JS code")')
14671
- //
14672
- // It is important to realize that if you create an expression from a string that contains user provided
14673
- // content then it is possible that your application contains a security vulnerability to an XSS style attack.
14674
- //
14675
- // See https://docs.angularjs.org/guide/security
14676
-
14677
-
14678
- function getStringValue(name) {
14679
- // Property names must be strings. This means that non-string objects cannot be used
14680
- // as keys in an object. Any non-string object, including a number, is typecasted
14681
- // into a string via the toString method.
14682
- // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names
14683
- //
14684
- // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it
14685
- // to a string. It's not always possible. If `name` is an object and its `toString` method is
14686
- // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown:
14687
- //
14688
- // TypeError: Cannot convert object to primitive value
14689
- //
14690
- // For performance reasons, we don't catch this error here and allow it to propagate up the call
14691
- // stack. Note that you'll get the same error in JavaScript if you try to access a property using
14692
- // such a 'broken' object as a key.
14693
- return name + '';
14694
- }
14695
-
14696
-
14697
- var OPERATORS = createMap();
14698
- forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
14699
- var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'};
14700
-
14701
-
14702
- /////////////////////////////////////////
14703
-
14704
-
14705
- /**
14706
- * @constructor
14707
- */
14708
- var Lexer = function Lexer(options) {
14709
- this.options = options;
14710
- };
14711
-
14712
- Lexer.prototype = {
14713
- constructor: Lexer,
14714
-
14715
- lex: function(text) {
14716
- this.text = text;
14717
- this.index = 0;
14718
- this.tokens = [];
14719
-
14720
- while (this.index < this.text.length) {
14721
- var ch = this.text.charAt(this.index);
14722
- if (ch === '"' || ch === '\'') {
14723
- this.readString(ch);
14724
- } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
14725
- this.readNumber();
14726
- } else if (this.isIdentifierStart(this.peekMultichar())) {
14727
- this.readIdent();
14728
- } else if (this.is(ch, '(){}[].,;:?')) {
14729
- this.tokens.push({index: this.index, text: ch});
14730
- this.index++;
14731
- } else if (this.isWhitespace(ch)) {
14732
- this.index++;
14733
- } else {
14734
- var ch2 = ch + this.peek();
14735
- var ch3 = ch2 + this.peek(2);
14736
- var op1 = OPERATORS[ch];
14737
- var op2 = OPERATORS[ch2];
14738
- var op3 = OPERATORS[ch3];
14739
- if (op1 || op2 || op3) {
14740
- var token = op3 ? ch3 : (op2 ? ch2 : ch);
14741
- this.tokens.push({index: this.index, text: token, operator: true});
14742
- this.index += token.length;
14743
- } else {
14744
- this.throwError('Unexpected next character ', this.index, this.index + 1);
14745
- }
14746
- }
14747
- }
14748
- return this.tokens;
14749
- },
14750
-
14751
- is: function(ch, chars) {
14752
- return chars.indexOf(ch) !== -1;
14753
- },
14754
-
14755
- peek: function(i) {
14756
- var num = i || 1;
14757
- return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
14758
- },
14759
-
14760
- isNumber: function(ch) {
14761
- return ('0' <= ch && ch <= '9') && typeof ch === 'string';
14762
- },
14763
-
14764
- isWhitespace: function(ch) {
14765
- // IE treats non-breaking space as \u00A0
14766
- return (ch === ' ' || ch === '\r' || ch === '\t' ||
14767
- ch === '\n' || ch === '\v' || ch === '\u00A0');
14768
- },
14769
-
14770
- isIdentifierStart: function(ch) {
14771
- return this.options.isIdentifierStart ?
14772
- this.options.isIdentifierStart(ch, this.codePointAt(ch)) :
14773
- this.isValidIdentifierStart(ch);
14774
- },
14775
-
14776
- isValidIdentifierStart: function(ch) {
14777
- return ('a' <= ch && ch <= 'z' ||
14778
- 'A' <= ch && ch <= 'Z' ||
14779
- '_' === ch || ch === '$');
14780
- },
14781
-
14782
- isIdentifierContinue: function(ch) {
14783
- return this.options.isIdentifierContinue ?
14784
- this.options.isIdentifierContinue(ch, this.codePointAt(ch)) :
14785
- this.isValidIdentifierContinue(ch);
14786
- },
14787
-
14788
- isValidIdentifierContinue: function(ch, cp) {
14789
- return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch);
14790
- },
14791
-
14792
- codePointAt: function(ch) {
14793
- if (ch.length === 1) return ch.charCodeAt(0);
14794
- // eslint-disable-next-line no-bitwise
14795
- return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
14796
- },
14797
-
14798
- peekMultichar: function() {
14799
- var ch = this.text.charAt(this.index);
14800
- var peek = this.peek();
14801
- if (!peek) {
14802
- return ch;
14803
- }
14804
- var cp1 = ch.charCodeAt(0);
14805
- var cp2 = peek.charCodeAt(0);
14806
- if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
14807
- return ch + peek;
14808
- }
14809
- return ch;
14810
- },
14811
-
14812
- isExpOperator: function(ch) {
14813
- return (ch === '-' || ch === '+' || this.isNumber(ch));
14814
- },
14815
-
14816
- throwError: function(error, start, end) {
14817
- end = end || this.index;
14818
- var colStr = (isDefined(start)
14819
- ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
14820
- : ' ' + end);
14821
- throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
14822
- error, colStr, this.text);
14823
- },
14824
-
14825
- readNumber: function() {
14826
- var number = '';
14827
- var start = this.index;
14828
- while (this.index < this.text.length) {
14829
- var ch = lowercase(this.text.charAt(this.index));
14830
- if (ch === '.' || this.isNumber(ch)) {
14831
- number += ch;
14832
- } else {
14833
- var peekCh = this.peek();
14834
- if (ch === 'e' && this.isExpOperator(peekCh)) {
14835
- number += ch;
14836
- } else if (this.isExpOperator(ch) &&
14837
- peekCh && this.isNumber(peekCh) &&
14838
- number.charAt(number.length - 1) === 'e') {
14839
- number += ch;
14840
- } else if (this.isExpOperator(ch) &&
14841
- (!peekCh || !this.isNumber(peekCh)) &&
14842
- number.charAt(number.length - 1) === 'e') {
14843
- this.throwError('Invalid exponent');
14844
- } else {
14845
- break;
14846
- }
14847
- }
14848
- this.index++;
14849
- }
14850
- this.tokens.push({
14851
- index: start,
14852
- text: number,
14853
- constant: true,
14854
- value: Number(number)
14855
- });
14856
- },
14857
-
14858
- readIdent: function() {
14859
- var start = this.index;
14860
- this.index += this.peekMultichar().length;
14861
- while (this.index < this.text.length) {
14862
- var ch = this.peekMultichar();
14863
- if (!this.isIdentifierContinue(ch)) {
14864
- break;
14865
- }
14866
- this.index += ch.length;
14867
- }
14868
- this.tokens.push({
14869
- index: start,
14870
- text: this.text.slice(start, this.index),
14871
- identifier: true
14872
- });
14873
- },
14874
-
14875
- readString: function(quote) {
14876
- var start = this.index;
14877
- this.index++;
14878
- var string = '';
14879
- var rawString = quote;
14880
- var escape = false;
14881
- while (this.index < this.text.length) {
14882
- var ch = this.text.charAt(this.index);
14883
- rawString += ch;
14884
- if (escape) {
14885
- if (ch === 'u') {
14886
- var hex = this.text.substring(this.index + 1, this.index + 5);
14887
- if (!hex.match(/[\da-f]{4}/i)) {
14888
- this.throwError('Invalid unicode escape [\\u' + hex + ']');
14889
- }
14890
- this.index += 4;
14891
- string += String.fromCharCode(parseInt(hex, 16));
14892
- } else {
14893
- var rep = ESCAPE[ch];
14894
- string = string + (rep || ch);
14895
- }
14896
- escape = false;
14897
- } else if (ch === '\\') {
14898
- escape = true;
14899
- } else if (ch === quote) {
14900
- this.index++;
14901
- this.tokens.push({
14902
- index: start,
14903
- text: rawString,
14904
- constant: true,
14905
- value: string
14906
- });
14907
- return;
14908
- } else {
14909
- string += ch;
14910
- }
14911
- this.index++;
14912
- }
14913
- this.throwError('Unterminated quote', start);
14914
- }
14915
- };
14916
-
14917
- var AST = function AST(lexer, options) {
14918
- this.lexer = lexer;
14919
- this.options = options;
14920
- };
14921
-
14922
- AST.Program = 'Program';
14923
- AST.ExpressionStatement = 'ExpressionStatement';
14924
- AST.AssignmentExpression = 'AssignmentExpression';
14925
- AST.ConditionalExpression = 'ConditionalExpression';
14926
- AST.LogicalExpression = 'LogicalExpression';
14927
- AST.BinaryExpression = 'BinaryExpression';
14928
- AST.UnaryExpression = 'UnaryExpression';
14929
- AST.CallExpression = 'CallExpression';
14930
- AST.MemberExpression = 'MemberExpression';
14931
- AST.Identifier = 'Identifier';
14932
- AST.Literal = 'Literal';
14933
- AST.ArrayExpression = 'ArrayExpression';
14934
- AST.Property = 'Property';
14935
- AST.ObjectExpression = 'ObjectExpression';
14936
- AST.ThisExpression = 'ThisExpression';
14937
- AST.LocalsExpression = 'LocalsExpression';
14938
-
14939
- // Internal use only
14940
- AST.NGValueParameter = 'NGValueParameter';
14941
-
14942
- AST.prototype = {
14943
- ast: function(text) {
14944
- this.text = text;
14945
- this.tokens = this.lexer.lex(text);
14946
-
14947
- var value = this.program();
14948
-
14949
- if (this.tokens.length !== 0) {
14950
- this.throwError('is an unexpected token', this.tokens[0]);
14951
- }
14952
-
14953
- return value;
14954
- },
14955
-
14956
- program: function() {
14957
- var body = [];
14958
- while (true) {
14959
- if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
14960
- body.push(this.expressionStatement());
14961
- if (!this.expect(';')) {
14962
- return { type: AST.Program, body: body};
14963
- }
14964
- }
14965
- },
14966
-
14967
- expressionStatement: function() {
14968
- return { type: AST.ExpressionStatement, expression: this.filterChain() };
14969
- },
14970
-
14971
- filterChain: function() {
14972
- var left = this.expression();
14973
- while (this.expect('|')) {
14974
- left = this.filter(left);
14975
- }
14976
- return left;
14977
- },
14978
-
14979
- expression: function() {
14980
- return this.assignment();
14981
- },
14982
-
14983
- assignment: function() {
14984
- var result = this.ternary();
14985
- if (this.expect('=')) {
14986
- if (!isAssignable(result)) {
14987
- throw $parseMinErr('lval', 'Trying to assign a value to a non l-value');
14988
- }
14989
-
14990
- result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
14991
- }
14992
- return result;
14993
- },
14994
-
14995
- ternary: function() {
14996
- var test = this.logicalOR();
14997
- var alternate;
14998
- var consequent;
14999
- if (this.expect('?')) {
15000
- alternate = this.expression();
15001
- if (this.consume(':')) {
15002
- consequent = this.expression();
15003
- return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
15004
- }
15005
- }
15006
- return test;
15007
- },
15008
-
15009
- logicalOR: function() {
15010
- var left = this.logicalAND();
15011
- while (this.expect('||')) {
15012
- left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
15013
- }
15014
- return left;
15015
- },
15016
-
15017
- logicalAND: function() {
15018
- var left = this.equality();
15019
- while (this.expect('&&')) {
15020
- left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
15021
- }
15022
- return left;
15023
- },
15024
-
15025
- equality: function() {
15026
- var left = this.relational();
15027
- var token;
15028
- while ((token = this.expect('==','!=','===','!=='))) {
15029
- left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
15030
- }
15031
- return left;
15032
- },
15033
-
15034
- relational: function() {
15035
- var left = this.additive();
15036
- var token;
15037
- while ((token = this.expect('<', '>', '<=', '>='))) {
15038
- left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
15039
- }
15040
- return left;
15041
- },
15042
-
15043
- additive: function() {
15044
- var left = this.multiplicative();
15045
- var token;
15046
- while ((token = this.expect('+','-'))) {
15047
- left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
15048
- }
15049
- return left;
15050
- },
15051
-
15052
- multiplicative: function() {
15053
- var left = this.unary();
15054
- var token;
15055
- while ((token = this.expect('*','/','%'))) {
15056
- left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
15057
- }
15058
- return left;
15059
- },
15060
-
15061
- unary: function() {
15062
- var token;
15063
- if ((token = this.expect('+', '-', '!'))) {
15064
- return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
15065
- } else {
15066
- return this.primary();
15067
- }
15068
- },
15069
-
15070
- primary: function() {
15071
- var primary;
15072
- if (this.expect('(')) {
15073
- primary = this.filterChain();
15074
- this.consume(')');
15075
- } else if (this.expect('[')) {
15076
- primary = this.arrayDeclaration();
15077
- } else if (this.expect('{')) {
15078
- primary = this.object();
15079
- } else if (this.selfReferential.hasOwnProperty(this.peek().text)) {
15080
- primary = copy(this.selfReferential[this.consume().text]);
15081
- } else if (this.options.literals.hasOwnProperty(this.peek().text)) {
15082
- primary = { type: AST.Literal, value: this.options.literals[this.consume().text]};
15083
- } else if (this.peek().identifier) {
15084
- primary = this.identifier();
15085
- } else if (this.peek().constant) {
15086
- primary = this.constant();
15087
- } else {
15088
- this.throwError('not a primary expression', this.peek());
15089
- }
15090
-
15091
- var next;
15092
- while ((next = this.expect('(', '[', '.'))) {
15093
- if (next.text === '(') {
15094
- primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
15095
- this.consume(')');
15096
- } else if (next.text === '[') {
15097
- primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
15098
- this.consume(']');
15099
- } else if (next.text === '.') {
15100
- primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
15101
- } else {
15102
- this.throwError('IMPOSSIBLE');
15103
- }
15104
- }
15105
- return primary;
15106
- },
15107
-
15108
- filter: function(baseExpression) {
15109
- var args = [baseExpression];
15110
- var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
15111
-
15112
- while (this.expect(':')) {
15113
- args.push(this.expression());
15114
- }
15115
-
15116
- return result;
15117
- },
15118
-
15119
- parseArguments: function() {
15120
- var args = [];
15121
- if (this.peekToken().text !== ')') {
15122
- do {
15123
- args.push(this.filterChain());
15124
- } while (this.expect(','));
15125
- }
15126
- return args;
15127
- },
15128
-
15129
- identifier: function() {
15130
- var token = this.consume();
15131
- if (!token.identifier) {
15132
- this.throwError('is not a valid identifier', token);
15133
- }
15134
- return { type: AST.Identifier, name: token.text };
15135
- },
15136
-
15137
- constant: function() {
15138
- // TODO check that it is a constant
15139
- return { type: AST.Literal, value: this.consume().value };
15140
- },
15141
-
15142
- arrayDeclaration: function() {
15143
- var elements = [];
15144
- if (this.peekToken().text !== ']') {
15145
- do {
15146
- if (this.peek(']')) {
15147
- // Support trailing commas per ES5.1.
15148
- break;
15149
- }
15150
- elements.push(this.expression());
15151
- } while (this.expect(','));
15152
- }
15153
- this.consume(']');
15154
-
15155
- return { type: AST.ArrayExpression, elements: elements };
15156
- },
15157
-
15158
- object: function() {
15159
- var properties = [], property;
15160
- if (this.peekToken().text !== '}') {
15161
- do {
15162
- if (this.peek('}')) {
15163
- // Support trailing commas per ES5.1.
15164
- break;
15165
- }
15166
- property = {type: AST.Property, kind: 'init'};
15167
- if (this.peek().constant) {
15168
- property.key = this.constant();
15169
- property.computed = false;
15170
- this.consume(':');
15171
- property.value = this.expression();
15172
- } else if (this.peek().identifier) {
15173
- property.key = this.identifier();
15174
- property.computed = false;
15175
- if (this.peek(':')) {
15176
- this.consume(':');
15177
- property.value = this.expression();
15178
- } else {
15179
- property.value = property.key;
15180
- }
15181
- } else if (this.peek('[')) {
15182
- this.consume('[');
15183
- property.key = this.expression();
15184
- this.consume(']');
15185
- property.computed = true;
15186
- this.consume(':');
15187
- property.value = this.expression();
15188
- } else {
15189
- this.throwError('invalid key', this.peek());
15190
- }
15191
- properties.push(property);
15192
- } while (this.expect(','));
15193
- }
15194
- this.consume('}');
15195
-
15196
- return {type: AST.ObjectExpression, properties: properties };
15197
- },
15198
-
15199
- throwError: function(msg, token) {
15200
- throw $parseMinErr('syntax',
15201
- 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
15202
- token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
15203
- },
15204
-
15205
- consume: function(e1) {
15206
- if (this.tokens.length === 0) {
15207
- throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
15208
- }
15209
-
15210
- var token = this.expect(e1);
15211
- if (!token) {
15212
- this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
15213
- }
15214
- return token;
15215
- },
15216
-
15217
- peekToken: function() {
15218
- if (this.tokens.length === 0) {
15219
- throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
15220
- }
15221
- return this.tokens[0];
15222
- },
15223
-
15224
- peek: function(e1, e2, e3, e4) {
15225
- return this.peekAhead(0, e1, e2, e3, e4);
15226
- },
15227
-
15228
- peekAhead: function(i, e1, e2, e3, e4) {
15229
- if (this.tokens.length > i) {
15230
- var token = this.tokens[i];
15231
- var t = token.text;
15232
- if (t === e1 || t === e2 || t === e3 || t === e4 ||
15233
- (!e1 && !e2 && !e3 && !e4)) {
15234
- return token;
15235
- }
15236
- }
15237
- return false;
15238
- },
15239
-
15240
- expect: function(e1, e2, e3, e4) {
15241
- var token = this.peek(e1, e2, e3, e4);
15242
- if (token) {
15243
- this.tokens.shift();
15244
- return token;
15245
- }
15246
- return false;
15247
- },
15248
-
15249
- selfReferential: {
15250
- 'this': {type: AST.ThisExpression },
15251
- '$locals': {type: AST.LocalsExpression }
15252
- }
15253
- };
15254
-
15255
- function ifDefined(v, d) {
15256
- return typeof v !== 'undefined' ? v : d;
15257
- }
15258
-
15259
- function plusFn(l, r) {
15260
- if (typeof l === 'undefined') return r;
15261
- if (typeof r === 'undefined') return l;
15262
- return l + r;
15263
- }
15264
-
15265
- function isStateless($filter, filterName) {
15266
- var fn = $filter(filterName);
15267
- return !fn.$stateful;
15268
- }
15269
-
15270
- var PURITY_ABSOLUTE = 1;
15271
- var PURITY_RELATIVE = 2;
15272
-
15273
- // Detect nodes which could depend on non-shallow state of objects
15274
- function isPure(node, parentIsPure) {
15275
- switch (node.type) {
15276
- // Computed members might invoke a stateful toString()
15277
- case AST.MemberExpression:
15278
- if (node.computed) {
15279
- return false;
15280
- }
15281
- break;
15282
-
15283
- // Unary always convert to primative
15284
- case AST.UnaryExpression:
15285
- return PURITY_ABSOLUTE;
15286
-
15287
- // The binary + operator can invoke a stateful toString().
15288
- case AST.BinaryExpression:
15289
- return node.operator !== '+' ? PURITY_ABSOLUTE : false;
15290
-
15291
- // Functions / filters probably read state from within objects
15292
- case AST.CallExpression:
15293
- return false;
15294
- }
15295
-
15296
- return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure;
15297
- }
15298
-
15299
- function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
15300
- var allConstants;
15301
- var argsToWatch;
15302
- var isStatelessFilter;
15303
-
15304
- var astIsPure = ast.isPure = isPure(ast, parentIsPure);
15305
-
15306
- switch (ast.type) {
15307
- case AST.Program:
15308
- allConstants = true;
15309
- forEach(ast.body, function(expr) {
15310
- findConstantAndWatchExpressions(expr.expression, $filter, astIsPure);
15311
- allConstants = allConstants && expr.expression.constant;
15312
- });
15313
- ast.constant = allConstants;
15314
- break;
15315
- case AST.Literal:
15316
- ast.constant = true;
15317
- ast.toWatch = [];
15318
- break;
15319
- case AST.UnaryExpression:
15320
- findConstantAndWatchExpressions(ast.argument, $filter, astIsPure);
15321
- ast.constant = ast.argument.constant;
15322
- ast.toWatch = ast.argument.toWatch;
15323
- break;
15324
- case AST.BinaryExpression:
15325
- findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
15326
- findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
15327
- ast.constant = ast.left.constant && ast.right.constant;
15328
- ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
15329
- break;
15330
- case AST.LogicalExpression:
15331
- findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
15332
- findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
15333
- ast.constant = ast.left.constant && ast.right.constant;
15334
- ast.toWatch = ast.constant ? [] : [ast];
15335
- break;
15336
- case AST.ConditionalExpression:
15337
- findConstantAndWatchExpressions(ast.test, $filter, astIsPure);
15338
- findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure);
15339
- findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure);
15340
- ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
15341
- ast.toWatch = ast.constant ? [] : [ast];
15342
- break;
15343
- case AST.Identifier:
15344
- ast.constant = false;
15345
- ast.toWatch = [ast];
15346
- break;
15347
- case AST.MemberExpression:
15348
- findConstantAndWatchExpressions(ast.object, $filter, astIsPure);
15349
- if (ast.computed) {
15350
- findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
15351
- }
15352
- ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
15353
- ast.toWatch = [ast];
15354
- break;
15355
- case AST.CallExpression:
15356
- isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false;
15357
- allConstants = isStatelessFilter;
15358
- argsToWatch = [];
15359
- forEach(ast.arguments, function(expr) {
15360
- findConstantAndWatchExpressions(expr, $filter, astIsPure);
15361
- allConstants = allConstants && expr.constant;
15362
- if (!expr.constant) {
15363
- argsToWatch.push.apply(argsToWatch, expr.toWatch);
15364
- }
15365
- });
15366
- ast.constant = allConstants;
15367
- ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
15368
- break;
15369
- case AST.AssignmentExpression:
15370
- findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
15371
- findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
15372
- ast.constant = ast.left.constant && ast.right.constant;
15373
- ast.toWatch = [ast];
15374
- break;
15375
- case AST.ArrayExpression:
15376
- allConstants = true;
15377
- argsToWatch = [];
15378
- forEach(ast.elements, function(expr) {
15379
- findConstantAndWatchExpressions(expr, $filter, astIsPure);
15380
- allConstants = allConstants && expr.constant;
15381
- if (!expr.constant) {
15382
- argsToWatch.push.apply(argsToWatch, expr.toWatch);
15383
- }
15384
- });
15385
- ast.constant = allConstants;
15386
- ast.toWatch = argsToWatch;
15387
- break;
15388
- case AST.ObjectExpression:
15389
- allConstants = true;
15390
- argsToWatch = [];
15391
- forEach(ast.properties, function(property) {
15392
- findConstantAndWatchExpressions(property.value, $filter, astIsPure);
15393
- allConstants = allConstants && property.value.constant && !property.computed;
15394
- if (!property.value.constant) {
15395
- argsToWatch.push.apply(argsToWatch, property.value.toWatch);
15396
- }
15397
- if (property.computed) {
15398
- findConstantAndWatchExpressions(property.key, $filter, astIsPure);
15399
- if (!property.key.constant) {
15400
- argsToWatch.push.apply(argsToWatch, property.key.toWatch);
15401
- }
15402
- }
15403
-
15404
- });
15405
- ast.constant = allConstants;
15406
- ast.toWatch = argsToWatch;
15407
- break;
15408
- case AST.ThisExpression:
15409
- ast.constant = false;
15410
- ast.toWatch = [];
15411
- break;
15412
- case AST.LocalsExpression:
15413
- ast.constant = false;
15414
- ast.toWatch = [];
15415
- break;
15416
- }
15417
- }
15418
-
15419
- function getInputs(body) {
15420
- if (body.length !== 1) return;
15421
- var lastExpression = body[0].expression;
15422
- var candidate = lastExpression.toWatch;
15423
- if (candidate.length !== 1) return candidate;
15424
- return candidate[0] !== lastExpression ? candidate : undefined;
15425
- }
15426
-
15427
- function isAssignable(ast) {
15428
- return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
15429
- }
15430
-
15431
- function assignableAST(ast) {
15432
- if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
15433
- return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
15434
- }
15435
- }
15436
-
15437
- function isLiteral(ast) {
15438
- return ast.body.length === 0 ||
15439
- ast.body.length === 1 && (
15440
- ast.body[0].expression.type === AST.Literal ||
15441
- ast.body[0].expression.type === AST.ArrayExpression ||
15442
- ast.body[0].expression.type === AST.ObjectExpression);
15443
- }
15444
-
15445
- function isConstant(ast) {
15446
- return ast.constant;
15447
- }
15448
-
15449
- function ASTCompiler($filter) {
15450
- this.$filter = $filter;
15451
- }
15452
-
15453
- ASTCompiler.prototype = {
15454
- compile: function(ast) {
15455
- var self = this;
15456
- this.state = {
15457
- nextId: 0,
15458
- filters: {},
15459
- fn: {vars: [], body: [], own: {}},
15460
- assign: {vars: [], body: [], own: {}},
15461
- inputs: []
15462
- };
15463
- findConstantAndWatchExpressions(ast, self.$filter);
15464
- var extra = '';
15465
- var assignable;
15466
- this.stage = 'assign';
15467
- if ((assignable = assignableAST(ast))) {
15468
- this.state.computing = 'assign';
15469
- var result = this.nextId();
15470
- this.recurse(assignable, result);
15471
- this.return_(result);
15472
- extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
15473
- }
15474
- var toWatch = getInputs(ast.body);
15475
- self.stage = 'inputs';
15476
- forEach(toWatch, function(watch, key) {
15477
- var fnKey = 'fn' + key;
15478
- self.state[fnKey] = {vars: [], body: [], own: {}};
15479
- self.state.computing = fnKey;
15480
- var intoId = self.nextId();
15481
- self.recurse(watch, intoId);
15482
- self.return_(intoId);
15483
- self.state.inputs.push({name: fnKey, isPure: watch.isPure});
15484
- watch.watchId = key;
15485
- });
15486
- this.state.computing = 'fn';
15487
- this.stage = 'main';
15488
- this.recurse(ast);
15489
- var fnString =
15490
- // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
15491
- // This is a workaround for this until we do a better job at only removing the prefix only when we should.
15492
- '"' + this.USE + ' ' + this.STRICT + '";\n' +
15493
- this.filterPrefix() +
15494
- 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
15495
- extra +
15496
- this.watchFns() +
15497
- 'return fn;';
15498
-
15499
- // eslint-disable-next-line no-new-func
15500
- var fn = (new Function('$filter',
15501
- 'getStringValue',
15502
- 'ifDefined',
15503
- 'plus',
15504
- fnString))(
15505
- this.$filter,
15506
- getStringValue,
15507
- ifDefined,
15508
- plusFn);
15509
- this.state = this.stage = undefined;
15510
- return fn;
15511
- },
15512
-
15513
- USE: 'use',
15514
-
15515
- STRICT: 'strict',
15516
-
15517
- watchFns: function() {
15518
- var result = [];
15519
- var inputs = this.state.inputs;
15520
- var self = this;
15521
- forEach(inputs, function(input) {
15522
- result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's'));
15523
- if (input.isPure) {
15524
- result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';');
15525
- }
15526
- });
15527
- if (inputs.length) {
15528
- result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];');
15529
- }
15530
- return result.join('');
15531
- },
15532
-
15533
- generateFunction: function(name, params) {
15534
- return 'function(' + params + '){' +
15535
- this.varsPrefix(name) +
15536
- this.body(name) +
15537
- '};';
15538
- },
15539
-
15540
- filterPrefix: function() {
15541
- var parts = [];
15542
- var self = this;
15543
- forEach(this.state.filters, function(id, filter) {
15544
- parts.push(id + '=$filter(' + self.escape(filter) + ')');
15545
- });
15546
- if (parts.length) return 'var ' + parts.join(',') + ';';
15547
- return '';
15548
- },
15549
-
15550
- varsPrefix: function(section) {
15551
- return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
15552
- },
15553
-
15554
- body: function(section) {
15555
- return this.state[section].body.join('');
15556
- },
15557
-
15558
- recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
15559
- var left, right, self = this, args, expression, computed;
15560
- recursionFn = recursionFn || noop;
15561
- if (!skipWatchIdCheck && isDefined(ast.watchId)) {
15562
- intoId = intoId || this.nextId();
15563
- this.if_('i',
15564
- this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
15565
- this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
15566
- );
15567
- return;
15568
- }
15569
- switch (ast.type) {
15570
- case AST.Program:
15571
- forEach(ast.body, function(expression, pos) {
15572
- self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
15573
- if (pos !== ast.body.length - 1) {
15574
- self.current().body.push(right, ';');
15575
- } else {
15576
- self.return_(right);
15577
- }
15578
- });
15579
- break;
15580
- case AST.Literal:
15581
- expression = this.escape(ast.value);
15582
- this.assign(intoId, expression);
15583
- recursionFn(intoId || expression);
15584
- break;
15585
- case AST.UnaryExpression:
15586
- this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
15587
- expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
15588
- this.assign(intoId, expression);
15589
- recursionFn(expression);
15590
- break;
15591
- case AST.BinaryExpression:
15592
- this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
15593
- this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
15594
- if (ast.operator === '+') {
15595
- expression = this.plus(left, right);
15596
- } else if (ast.operator === '-') {
15597
- expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
15598
- } else {
15599
- expression = '(' + left + ')' + ast.operator + '(' + right + ')';
15600
- }
15601
- this.assign(intoId, expression);
15602
- recursionFn(expression);
15603
- break;
15604
- case AST.LogicalExpression:
15605
- intoId = intoId || this.nextId();
15606
- self.recurse(ast.left, intoId);
15607
- self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
15608
- recursionFn(intoId);
15609
- break;
15610
- case AST.ConditionalExpression:
15611
- intoId = intoId || this.nextId();
15612
- self.recurse(ast.test, intoId);
15613
- self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
15614
- recursionFn(intoId);
15615
- break;
15616
- case AST.Identifier:
15617
- intoId = intoId || this.nextId();
15618
- if (nameId) {
15619
- nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
15620
- nameId.computed = false;
15621
- nameId.name = ast.name;
15622
- }
15623
- self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
15624
- function() {
15625
- self.if_(self.stage === 'inputs' || 's', function() {
15626
- if (create && create !== 1) {
15627
- self.if_(
15628
- self.isNull(self.nonComputedMember('s', ast.name)),
15629
- self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
15630
- }
15631
- self.assign(intoId, self.nonComputedMember('s', ast.name));
15632
- });
15633
- }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
15634
- );
15635
- recursionFn(intoId);
15636
- break;
15637
- case AST.MemberExpression:
15638
- left = nameId && (nameId.context = this.nextId()) || this.nextId();
15639
- intoId = intoId || this.nextId();
15640
- self.recurse(ast.object, left, undefined, function() {
15641
- self.if_(self.notNull(left), function() {
15642
- if (ast.computed) {
15643
- right = self.nextId();
15644
- self.recurse(ast.property, right);
15645
- self.getStringValue(right);
15646
- if (create && create !== 1) {
15647
- self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
15648
- }
15649
- expression = self.computedMember(left, right);
15650
- self.assign(intoId, expression);
15651
- if (nameId) {
15652
- nameId.computed = true;
15653
- nameId.name = right;
15654
- }
15655
- } else {
15656
- if (create && create !== 1) {
15657
- self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
15658
- }
15659
- expression = self.nonComputedMember(left, ast.property.name);
15660
- self.assign(intoId, expression);
15661
- if (nameId) {
15662
- nameId.computed = false;
15663
- nameId.name = ast.property.name;
15664
- }
15665
- }
15666
- }, function() {
15667
- self.assign(intoId, 'undefined');
15668
- });
15669
- recursionFn(intoId);
15670
- }, !!create);
15671
- break;
15672
- case AST.CallExpression:
15673
- intoId = intoId || this.nextId();
15674
- if (ast.filter) {
15675
- right = self.filter(ast.callee.name);
15676
- args = [];
15677
- forEach(ast.arguments, function(expr) {
15678
- var argument = self.nextId();
15679
- self.recurse(expr, argument);
15680
- args.push(argument);
15681
- });
15682
- expression = right + '(' + args.join(',') + ')';
15683
- self.assign(intoId, expression);
15684
- recursionFn(intoId);
15685
- } else {
15686
- right = self.nextId();
15687
- left = {};
15688
- args = [];
15689
- self.recurse(ast.callee, right, left, function() {
15690
- self.if_(self.notNull(right), function() {
15691
- forEach(ast.arguments, function(expr) {
15692
- self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
15693
- args.push(argument);
15694
- });
15695
- });
15696
- if (left.name) {
15697
- expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
15698
- } else {
15699
- expression = right + '(' + args.join(',') + ')';
15700
- }
15701
- self.assign(intoId, expression);
15702
- }, function() {
15703
- self.assign(intoId, 'undefined');
15704
- });
15705
- recursionFn(intoId);
15706
- });
15707
- }
15708
- break;
15709
- case AST.AssignmentExpression:
15710
- right = this.nextId();
15711
- left = {};
15712
- this.recurse(ast.left, undefined, left, function() {
15713
- self.if_(self.notNull(left.context), function() {
15714
- self.recurse(ast.right, right);
15715
- expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
15716
- self.assign(intoId, expression);
15717
- recursionFn(intoId || expression);
15718
- });
15719
- }, 1);
15720
- break;
15721
- case AST.ArrayExpression:
15722
- args = [];
15723
- forEach(ast.elements, function(expr) {
15724
- self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
15725
- args.push(argument);
15726
- });
15727
- });
15728
- expression = '[' + args.join(',') + ']';
15729
- this.assign(intoId, expression);
15730
- recursionFn(intoId || expression);
15731
- break;
15732
- case AST.ObjectExpression:
15733
- args = [];
15734
- computed = false;
15735
- forEach(ast.properties, function(property) {
15736
- if (property.computed) {
15737
- computed = true;
15738
- }
15739
- });
15740
- if (computed) {
15741
- intoId = intoId || this.nextId();
15742
- this.assign(intoId, '{}');
15743
- forEach(ast.properties, function(property) {
15744
- if (property.computed) {
15745
- left = self.nextId();
15746
- self.recurse(property.key, left);
15747
- } else {
15748
- left = property.key.type === AST.Identifier ?
15749
- property.key.name :
15750
- ('' + property.key.value);
15751
- }
15752
- right = self.nextId();
15753
- self.recurse(property.value, right);
15754
- self.assign(self.member(intoId, left, property.computed), right);
15755
- });
15756
- } else {
15757
- forEach(ast.properties, function(property) {
15758
- self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) {
15759
- args.push(self.escape(
15760
- property.key.type === AST.Identifier ? property.key.name :
15761
- ('' + property.key.value)) +
15762
- ':' + expr);
15763
- });
15764
- });
15765
- expression = '{' + args.join(',') + '}';
15766
- this.assign(intoId, expression);
15767
- }
15768
- recursionFn(intoId || expression);
15769
- break;
15770
- case AST.ThisExpression:
15771
- this.assign(intoId, 's');
15772
- recursionFn(intoId || 's');
15773
- break;
15774
- case AST.LocalsExpression:
15775
- this.assign(intoId, 'l');
15776
- recursionFn(intoId || 'l');
15777
- break;
15778
- case AST.NGValueParameter:
15779
- this.assign(intoId, 'v');
15780
- recursionFn(intoId || 'v');
15781
- break;
15782
- }
15783
- },
15784
-
15785
- getHasOwnProperty: function(element, property) {
15786
- var key = element + '.' + property;
15787
- var own = this.current().own;
15788
- if (!own.hasOwnProperty(key)) {
15789
- own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
15790
- }
15791
- return own[key];
15792
- },
15793
-
15794
- assign: function(id, value) {
15795
- if (!id) return;
15796
- this.current().body.push(id, '=', value, ';');
15797
- return id;
15798
- },
15799
-
15800
- filter: function(filterName) {
15801
- if (!this.state.filters.hasOwnProperty(filterName)) {
15802
- this.state.filters[filterName] = this.nextId(true);
15803
- }
15804
- return this.state.filters[filterName];
15805
- },
15806
-
15807
- ifDefined: function(id, defaultValue) {
15808
- return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
15809
- },
15810
-
15811
- plus: function(left, right) {
15812
- return 'plus(' + left + ',' + right + ')';
15813
- },
15814
-
15815
- return_: function(id) {
15816
- this.current().body.push('return ', id, ';');
15817
- },
15818
-
15819
- if_: function(test, alternate, consequent) {
15820
- if (test === true) {
15821
- alternate();
15822
- } else {
15823
- var body = this.current().body;
15824
- body.push('if(', test, '){');
15825
- alternate();
15826
- body.push('}');
15827
- if (consequent) {
15828
- body.push('else{');
15829
- consequent();
15830
- body.push('}');
15831
- }
15832
- }
15833
- },
15834
-
15835
- not: function(expression) {
15836
- return '!(' + expression + ')';
15837
- },
15838
-
15839
- isNull: function(expression) {
15840
- return expression + '==null';
15841
- },
15842
-
15843
- notNull: function(expression) {
15844
- return expression + '!=null';
15845
- },
15846
-
15847
- nonComputedMember: function(left, right) {
15848
- var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
15849
- var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
15850
- if (SAFE_IDENTIFIER.test(right)) {
15851
- return left + '.' + right;
15852
- } else {
15853
- return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]';
15854
- }
15855
- },
15856
-
15857
- computedMember: function(left, right) {
15858
- return left + '[' + right + ']';
15859
- },
15860
-
15861
- member: function(left, right, computed) {
15862
- if (computed) return this.computedMember(left, right);
15863
- return this.nonComputedMember(left, right);
15864
- },
15865
-
15866
- getStringValue: function(item) {
15867
- this.assign(item, 'getStringValue(' + item + ')');
15868
- },
15869
-
15870
- lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
15871
- var self = this;
15872
- return function() {
15873
- self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
15874
- };
15875
- },
15876
-
15877
- lazyAssign: function(id, value) {
15878
- var self = this;
15879
- return function() {
15880
- self.assign(id, value);
15881
- };
15882
- },
15883
-
15884
- stringEscapeRegex: /[^ a-zA-Z0-9]/g,
15885
-
15886
- stringEscapeFn: function(c) {
15887
- return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
15888
- },
15889
-
15890
- escape: function(value) {
15891
- if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\'';
15892
- if (isNumber(value)) return value.toString();
15893
- if (value === true) return 'true';
15894
- if (value === false) return 'false';
15895
- if (value === null) return 'null';
15896
- if (typeof value === 'undefined') return 'undefined';
15897
-
15898
- throw $parseMinErr('esc', 'IMPOSSIBLE');
15899
- },
15900
-
15901
- nextId: function(skip, init) {
15902
- var id = 'v' + (this.state.nextId++);
15903
- if (!skip) {
15904
- this.current().vars.push(id + (init ? '=' + init : ''));
15905
- }
15906
- return id;
15907
- },
15908
-
15909
- current: function() {
15910
- return this.state[this.state.computing];
15911
- }
15912
- };
15913
-
15914
-
15915
- function ASTInterpreter($filter) {
15916
- this.$filter = $filter;
15917
- }
15918
-
15919
- ASTInterpreter.prototype = {
15920
- compile: function(ast) {
15921
- var self = this;
15922
- findConstantAndWatchExpressions(ast, self.$filter);
15923
- var assignable;
15924
- var assign;
15925
- if ((assignable = assignableAST(ast))) {
15926
- assign = this.recurse(assignable);
15927
- }
15928
- var toWatch = getInputs(ast.body);
15929
- var inputs;
15930
- if (toWatch) {
15931
- inputs = [];
15932
- forEach(toWatch, function(watch, key) {
15933
- var input = self.recurse(watch);
15934
- input.isPure = watch.isPure;
15935
- watch.input = input;
15936
- inputs.push(input);
15937
- watch.watchId = key;
15938
- });
15939
- }
15940
- var expressions = [];
15941
- forEach(ast.body, function(expression) {
15942
- expressions.push(self.recurse(expression.expression));
15943
- });
15944
- var fn = ast.body.length === 0 ? noop :
15945
- ast.body.length === 1 ? expressions[0] :
15946
- function(scope, locals) {
15947
- var lastValue;
15948
- forEach(expressions, function(exp) {
15949
- lastValue = exp(scope, locals);
15950
- });
15951
- return lastValue;
15952
- };
15953
- if (assign) {
15954
- fn.assign = function(scope, value, locals) {
15955
- return assign(scope, locals, value);
15956
- };
15957
- }
15958
- if (inputs) {
15959
- fn.inputs = inputs;
15960
- }
15961
- return fn;
15962
- },
15963
-
15964
- recurse: function(ast, context, create) {
15965
- var left, right, self = this, args;
15966
- if (ast.input) {
15967
- return this.inputs(ast.input, ast.watchId);
15968
- }
15969
- switch (ast.type) {
15970
- case AST.Literal:
15971
- return this.value(ast.value, context);
15972
- case AST.UnaryExpression:
15973
- right = this.recurse(ast.argument);
15974
- return this['unary' + ast.operator](right, context);
15975
- case AST.BinaryExpression:
15976
- left = this.recurse(ast.left);
15977
- right = this.recurse(ast.right);
15978
- return this['binary' + ast.operator](left, right, context);
15979
- case AST.LogicalExpression:
15980
- left = this.recurse(ast.left);
15981
- right = this.recurse(ast.right);
15982
- return this['binary' + ast.operator](left, right, context);
15983
- case AST.ConditionalExpression:
15984
- return this['ternary?:'](
15985
- this.recurse(ast.test),
15986
- this.recurse(ast.alternate),
15987
- this.recurse(ast.consequent),
15988
- context
15989
- );
15990
- case AST.Identifier:
15991
- return self.identifier(ast.name, context, create);
15992
- case AST.MemberExpression:
15993
- left = this.recurse(ast.object, false, !!create);
15994
- if (!ast.computed) {
15995
- right = ast.property.name;
15996
- }
15997
- if (ast.computed) right = this.recurse(ast.property);
15998
- return ast.computed ?
15999
- this.computedMember(left, right, context, create) :
16000
- this.nonComputedMember(left, right, context, create);
16001
- case AST.CallExpression:
16002
- args = [];
16003
- forEach(ast.arguments, function(expr) {
16004
- args.push(self.recurse(expr));
16005
- });
16006
- if (ast.filter) right = this.$filter(ast.callee.name);
16007
- if (!ast.filter) right = this.recurse(ast.callee, true);
16008
- return ast.filter ?
16009
- function(scope, locals, assign, inputs) {
16010
- var values = [];
16011
- for (var i = 0; i < args.length; ++i) {
16012
- values.push(args[i](scope, locals, assign, inputs));
16013
- }
16014
- var value = right.apply(undefined, values, inputs);
16015
- return context ? {context: undefined, name: undefined, value: value} : value;
16016
- } :
16017
- function(scope, locals, assign, inputs) {
16018
- var rhs = right(scope, locals, assign, inputs);
16019
- var value;
16020
- if (rhs.value != null) {
16021
- var values = [];
16022
- for (var i = 0; i < args.length; ++i) {
16023
- values.push(args[i](scope, locals, assign, inputs));
16024
- }
16025
- value = rhs.value.apply(rhs.context, values);
16026
- }
16027
- return context ? {value: value} : value;
16028
- };
16029
- case AST.AssignmentExpression:
16030
- left = this.recurse(ast.left, true, 1);
16031
- right = this.recurse(ast.right);
16032
- return function(scope, locals, assign, inputs) {
16033
- var lhs = left(scope, locals, assign, inputs);
16034
- var rhs = right(scope, locals, assign, inputs);
16035
- lhs.context[lhs.name] = rhs;
16036
- return context ? {value: rhs} : rhs;
16037
- };
16038
- case AST.ArrayExpression:
16039
- args = [];
16040
- forEach(ast.elements, function(expr) {
16041
- args.push(self.recurse(expr));
16042
- });
16043
- return function(scope, locals, assign, inputs) {
16044
- var value = [];
16045
- for (var i = 0; i < args.length; ++i) {
16046
- value.push(args[i](scope, locals, assign, inputs));
16047
- }
16048
- return context ? {value: value} : value;
16049
- };
16050
- case AST.ObjectExpression:
16051
- args = [];
16052
- forEach(ast.properties, function(property) {
16053
- if (property.computed) {
16054
- args.push({key: self.recurse(property.key),
16055
- computed: true,
16056
- value: self.recurse(property.value)
16057
- });
16058
- } else {
16059
- args.push({key: property.key.type === AST.Identifier ?
16060
- property.key.name :
16061
- ('' + property.key.value),
16062
- computed: false,
16063
- value: self.recurse(property.value)
16064
- });
16065
- }
16066
- });
16067
- return function(scope, locals, assign, inputs) {
16068
- var value = {};
16069
- for (var i = 0; i < args.length; ++i) {
16070
- if (args[i].computed) {
16071
- value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs);
16072
- } else {
16073
- value[args[i].key] = args[i].value(scope, locals, assign, inputs);
16074
- }
16075
- }
16076
- return context ? {value: value} : value;
16077
- };
16078
- case AST.ThisExpression:
16079
- return function(scope) {
16080
- return context ? {value: scope} : scope;
16081
- };
16082
- case AST.LocalsExpression:
16083
- return function(scope, locals) {
16084
- return context ? {value: locals} : locals;
16085
- };
16086
- case AST.NGValueParameter:
16087
- return function(scope, locals, assign) {
16088
- return context ? {value: assign} : assign;
16089
- };
16090
- }
16091
- },
16092
-
16093
- 'unary+': function(argument, context) {
16094
- return function(scope, locals, assign, inputs) {
16095
- var arg = argument(scope, locals, assign, inputs);
16096
- if (isDefined(arg)) {
16097
- arg = +arg;
16098
- } else {
16099
- arg = 0;
16100
- }
16101
- return context ? {value: arg} : arg;
16102
- };
16103
- },
16104
- 'unary-': function(argument, context) {
16105
- return function(scope, locals, assign, inputs) {
16106
- var arg = argument(scope, locals, assign, inputs);
16107
- if (isDefined(arg)) {
16108
- arg = -arg;
16109
- } else {
16110
- arg = -0;
16111
- }
16112
- return context ? {value: arg} : arg;
16113
- };
16114
- },
16115
- 'unary!': function(argument, context) {
16116
- return function(scope, locals, assign, inputs) {
16117
- var arg = !argument(scope, locals, assign, inputs);
16118
- return context ? {value: arg} : arg;
16119
- };
16120
- },
16121
- 'binary+': function(left, right, context) {
16122
- return function(scope, locals, assign, inputs) {
16123
- var lhs = left(scope, locals, assign, inputs);
16124
- var rhs = right(scope, locals, assign, inputs);
16125
- var arg = plusFn(lhs, rhs);
16126
- return context ? {value: arg} : arg;
16127
- };
16128
- },
16129
- 'binary-': function(left, right, context) {
16130
- return function(scope, locals, assign, inputs) {
16131
- var lhs = left(scope, locals, assign, inputs);
16132
- var rhs = right(scope, locals, assign, inputs);
16133
- var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
16134
- return context ? {value: arg} : arg;
16135
- };
16136
- },
16137
- 'binary*': function(left, right, context) {
16138
- return function(scope, locals, assign, inputs) {
16139
- var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
16140
- return context ? {value: arg} : arg;
16141
- };
16142
- },
16143
- 'binary/': function(left, right, context) {
16144
- return function(scope, locals, assign, inputs) {
16145
- var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
16146
- return context ? {value: arg} : arg;
16147
- };
16148
- },
16149
- 'binary%': function(left, right, context) {
16150
- return function(scope, locals, assign, inputs) {
16151
- var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
16152
- return context ? {value: arg} : arg;
16153
- };
16154
- },
16155
- 'binary===': function(left, right, context) {
16156
- return function(scope, locals, assign, inputs) {
16157
- var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
16158
- return context ? {value: arg} : arg;
16159
- };
16160
- },
16161
- 'binary!==': function(left, right, context) {
16162
- return function(scope, locals, assign, inputs) {
16163
- var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
16164
- return context ? {value: arg} : arg;
16165
- };
16166
- },
16167
- 'binary==': function(left, right, context) {
16168
- return function(scope, locals, assign, inputs) {
16169
- // eslint-disable-next-line eqeqeq
16170
- var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
16171
- return context ? {value: arg} : arg;
16172
- };
16173
- },
16174
- 'binary!=': function(left, right, context) {
16175
- return function(scope, locals, assign, inputs) {
16176
- // eslint-disable-next-line eqeqeq
16177
- var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
16178
- return context ? {value: arg} : arg;
16179
- };
16180
- },
16181
- 'binary<': function(left, right, context) {
16182
- return function(scope, locals, assign, inputs) {
16183
- var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
16184
- return context ? {value: arg} : arg;
16185
- };
16186
- },
16187
- 'binary>': function(left, right, context) {
16188
- return function(scope, locals, assign, inputs) {
16189
- var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
16190
- return context ? {value: arg} : arg;
16191
- };
16192
- },
16193
- 'binary<=': function(left, right, context) {
16194
- return function(scope, locals, assign, inputs) {
16195
- var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
16196
- return context ? {value: arg} : arg;
16197
- };
16198
- },
16199
- 'binary>=': function(left, right, context) {
16200
- return function(scope, locals, assign, inputs) {
16201
- var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
16202
- return context ? {value: arg} : arg;
16203
- };
16204
- },
16205
- 'binary&&': function(left, right, context) {
16206
- return function(scope, locals, assign, inputs) {
16207
- var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
16208
- return context ? {value: arg} : arg;
16209
- };
16210
- },
16211
- 'binary||': function(left, right, context) {
16212
- return function(scope, locals, assign, inputs) {
16213
- var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
16214
- return context ? {value: arg} : arg;
16215
- };
16216
- },
16217
- 'ternary?:': function(test, alternate, consequent, context) {
16218
- return function(scope, locals, assign, inputs) {
16219
- var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
16220
- return context ? {value: arg} : arg;
16221
- };
16222
- },
16223
- value: function(value, context) {
16224
- return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
16225
- },
16226
- identifier: function(name, context, create) {
16227
- return function(scope, locals, assign, inputs) {
16228
- var base = locals && (name in locals) ? locals : scope;
16229
- if (create && create !== 1 && base && base[name] == null) {
16230
- base[name] = {};
16231
- }
16232
- var value = base ? base[name] : undefined;
16233
- if (context) {
16234
- return {context: base, name: name, value: value};
16235
- } else {
16236
- return value;
16237
- }
16238
- };
16239
- },
16240
- computedMember: function(left, right, context, create) {
16241
- return function(scope, locals, assign, inputs) {
16242
- var lhs = left(scope, locals, assign, inputs);
16243
- var rhs;
16244
- var value;
16245
- if (lhs != null) {
16246
- rhs = right(scope, locals, assign, inputs);
16247
- rhs = getStringValue(rhs);
16248
- if (create && create !== 1) {
16249
- if (lhs && !(lhs[rhs])) {
16250
- lhs[rhs] = {};
16251
- }
16252
- }
16253
- value = lhs[rhs];
16254
- }
16255
- if (context) {
16256
- return {context: lhs, name: rhs, value: value};
16257
- } else {
16258
- return value;
16259
- }
16260
- };
16261
- },
16262
- nonComputedMember: function(left, right, context, create) {
16263
- return function(scope, locals, assign, inputs) {
16264
- var lhs = left(scope, locals, assign, inputs);
16265
- if (create && create !== 1) {
16266
- if (lhs && lhs[right] == null) {
16267
- lhs[right] = {};
16268
- }
16269
- }
16270
- var value = lhs != null ? lhs[right] : undefined;
16271
- if (context) {
16272
- return {context: lhs, name: right, value: value};
16273
- } else {
16274
- return value;
16275
- }
16276
- };
16277
- },
16278
- inputs: function(input, watchId) {
16279
- return function(scope, value, locals, inputs) {
16280
- if (inputs) return inputs[watchId];
16281
- return input(scope, value, locals);
16282
- };
16283
- }
16284
- };
16285
-
16286
- /**
16287
- * @constructor
16288
- */
16289
- function Parser(lexer, $filter, options) {
16290
- this.ast = new AST(lexer, options);
16291
- this.astCompiler = options.csp ? new ASTInterpreter($filter) :
16292
- new ASTCompiler($filter);
16293
- }
16294
-
16295
- Parser.prototype = {
16296
- constructor: Parser,
16297
-
16298
- parse: function(text) {
16299
- var ast = this.ast.ast(text);
16300
- var fn = this.astCompiler.compile(ast);
16301
- fn.literal = isLiteral(ast);
16302
- fn.constant = isConstant(ast);
16303
- return fn;
16304
- }
16305
- };
16306
-
16307
- function getValueOf(value) {
16308
- return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
16309
- }
16310
-
16311
- ///////////////////////////////////
16312
-
16313
- /**
16314
- * @ngdoc service
16315
- * @name $parse
16316
- * @kind function
16317
- *
16318
- * @description
16319
- *
16320
- * Converts Angular {@link guide/expression expression} into a function.
16321
- *
16322
- * ```js
16323
- * var getter = $parse('user.name');
16324
- * var setter = getter.assign;
16325
- * var context = {user:{name:'angular'}};
16326
- * var locals = {user:{name:'local'}};
16327
- *
16328
- * expect(getter(context)).toEqual('angular');
16329
- * setter(context, 'newValue');
16330
- * expect(context.user.name).toEqual('newValue');
16331
- * expect(getter(context, locals)).toEqual('local');
16332
- * ```
16333
- *
16334
- *
16335
- * @param {string} expression String expression to compile.
16336
- * @returns {function(context, locals)} a function which represents the compiled expression:
16337
- *
16338
- * * `context` – `{object}` – an object against which any expressions embedded in the strings
16339
- * are evaluated against (typically a scope object).
16340
- * * `locals` – `{object=}` – local variables context object, useful for overriding values in
16341
- * `context`.
16342
- *
16343
- * The returned function also has the following properties:
16344
- * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
16345
- * literal.
16346
- * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
16347
- * constant literals.
16348
- * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
16349
- * set to a function to change its value on the given context.
16350
- *
16351
- */
16352
-
16353
-
16354
- /**
16355
- * @ngdoc provider
16356
- * @name $parseProvider
16357
- * @this
16358
- *
16359
- * @description
16360
- * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
16361
- * service.
16362
- */
16363
- function $ParseProvider() {
16364
- var cache = createMap();
16365
- var literals = {
16366
- 'true': true,
16367
- 'false': false,
16368
- 'null': null,
16369
- 'undefined': undefined
16370
- };
16371
- var identStart, identContinue;
16372
-
16373
- /**
16374
- * @ngdoc method
16375
- * @name $parseProvider#addLiteral
16376
- * @description
16377
- *
16378
- * Configure $parse service to add literal values that will be present as literal at expressions.
16379
- *
16380
- * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name.
16381
- * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`.
16382
- *
16383
- **/
16384
- this.addLiteral = function(literalName, literalValue) {
16385
- literals[literalName] = literalValue;
16386
- };
16387
-
16388
- /**
16389
- * @ngdoc method
16390
- * @name $parseProvider#setIdentifierFns
16391
- *
16392
- * @description
16393
- *
16394
- * Allows defining the set of characters that are allowed in Angular expressions. The function
16395
- * `identifierStart` will get called to know if a given character is a valid character to be the
16396
- * first character for an identifier. The function `identifierContinue` will get called to know if
16397
- * a given character is a valid character to be a follow-up identifier character. The functions
16398
- * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
16399
- * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
16400
- * mind that the `string` parameter can be two characters long depending on the character
16401
- * representation. It is expected for the function to return `true` or `false`, whether that
16402
- * character is allowed or not.
16403
- *
16404
- * Since this function will be called extensively, keep the implementation of these functions fast,
16405
- * as the performance of these functions have a direct impact on the expressions parsing speed.
16406
- *
16407
- * @param {function=} identifierStart The function that will decide whether the given character is
16408
- * a valid identifier start character.
16409
- * @param {function=} identifierContinue The function that will decide whether the given character is
16410
- * a valid identifier continue character.
16411
- */
16412
- this.setIdentifierFns = function(identifierStart, identifierContinue) {
16413
- identStart = identifierStart;
16414
- identContinue = identifierContinue;
16415
- return this;
16416
- };
16417
-
16418
- this.$get = ['$filter', function($filter) {
16419
- var noUnsafeEval = csp().noUnsafeEval;
16420
- var $parseOptions = {
16421
- csp: noUnsafeEval,
16422
- literals: copy(literals),
16423
- isIdentifierStart: isFunction(identStart) && identStart,
16424
- isIdentifierContinue: isFunction(identContinue) && identContinue
16425
- };
16426
- return $parse;
16427
-
16428
- function $parse(exp, interceptorFn) {
16429
- var parsedExpression, oneTime, cacheKey;
16430
-
16431
- switch (typeof exp) {
16432
- case 'string':
16433
- exp = exp.trim();
16434
- cacheKey = exp;
16435
-
16436
- parsedExpression = cache[cacheKey];
16437
-
16438
- if (!parsedExpression) {
16439
- if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
16440
- oneTime = true;
16441
- exp = exp.substring(2);
16442
- }
16443
- var lexer = new Lexer($parseOptions);
16444
- var parser = new Parser(lexer, $filter, $parseOptions);
16445
- parsedExpression = parser.parse(exp);
16446
- if (parsedExpression.constant) {
16447
- parsedExpression.$$watchDelegate = constantWatchDelegate;
16448
- } else if (oneTime) {
16449
- parsedExpression.$$watchDelegate = parsedExpression.literal ?
16450
- oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
16451
- } else if (parsedExpression.inputs) {
16452
- parsedExpression.$$watchDelegate = inputsWatchDelegate;
16453
- }
16454
- cache[cacheKey] = parsedExpression;
16455
- }
16456
- return addInterceptor(parsedExpression, interceptorFn);
16457
-
16458
- case 'function':
16459
- return addInterceptor(exp, interceptorFn);
16460
-
16461
- default:
16462
- return addInterceptor(noop, interceptorFn);
16463
- }
16464
- }
16465
-
16466
- function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
16467
-
16468
- if (newValue == null || oldValueOfValue == null) { // null/undefined
16469
- return newValue === oldValueOfValue;
16470
- }
16471
-
16472
- if (typeof newValue === 'object') {
16473
-
16474
- // attempt to convert the value to a primitive type
16475
- // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
16476
- // be cheaply dirty-checked
16477
- newValue = getValueOf(newValue);
16478
-
16479
- if (typeof newValue === 'object' && !compareObjectIdentity) {
16480
- // objects/arrays are not supported - deep-watching them would be too expensive
16481
- return false;
16482
- }
16483
-
16484
- // fall-through to the primitive equality check
16485
- }
16486
-
16487
- //Primitive or NaN
16488
- // eslint-disable-next-line no-self-compare
16489
- return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
16490
- }
16491
-
16492
- function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
16493
- var inputExpressions = parsedExpression.inputs;
16494
- var lastResult;
16495
-
16496
- if (inputExpressions.length === 1) {
16497
- var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
16498
- inputExpressions = inputExpressions[0];
16499
- return scope.$watch(function expressionInputWatch(scope) {
16500
- var newInputValue = inputExpressions(scope);
16501
- if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) {
16502
- lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
16503
- oldInputValueOf = newInputValue && getValueOf(newInputValue);
16504
- }
16505
- return lastResult;
16506
- }, listener, objectEquality, prettyPrintExpression);
16507
- }
16508
-
16509
- var oldInputValueOfValues = [];
16510
- var oldInputValues = [];
16511
- for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
16512
- oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
16513
- oldInputValues[i] = null;
16514
- }
16515
-
16516
- return scope.$watch(function expressionInputsWatch(scope) {
16517
- var changed = false;
16518
-
16519
- for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
16520
- var newInputValue = inputExpressions[i](scope);
16521
- if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) {
16522
- oldInputValues[i] = newInputValue;
16523
- oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
16524
- }
16525
- }
16526
-
16527
- if (changed) {
16528
- lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
16529
- }
16530
-
16531
- return lastResult;
16532
- }, listener, objectEquality, prettyPrintExpression);
16533
- }
16534
-
16535
- function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
16536
- var unwatch, lastValue;
16537
- if (parsedExpression.inputs) {
16538
- unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression);
16539
- } else {
16540
- unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality);
16541
- }
16542
- return unwatch;
16543
-
16544
- function oneTimeWatch(scope) {
16545
- return parsedExpression(scope);
16546
- }
16547
- function oneTimeListener(value, old, scope) {
16548
- lastValue = value;
16549
- if (isFunction(listener)) {
16550
- listener(value, old, scope);
16551
- }
16552
- if (isDefined(value)) {
16553
- scope.$$postDigest(function() {
16554
- if (isDefined(lastValue)) {
16555
- unwatch();
16556
- }
16557
- });
16558
- }
16559
- }
16560
- }
16561
-
16562
- function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
16563
- var unwatch, lastValue;
16564
- unwatch = scope.$watch(function oneTimeWatch(scope) {
16565
- return parsedExpression(scope);
16566
- }, function oneTimeListener(value, old, scope) {
16567
- lastValue = value;
16568
- if (isFunction(listener)) {
16569
- listener(value, old, scope);
16570
- }
16571
- if (isAllDefined(value)) {
16572
- scope.$$postDigest(function() {
16573
- if (isAllDefined(lastValue)) unwatch();
16574
- });
16575
- }
16576
- }, objectEquality);
16577
-
16578
- return unwatch;
16579
-
16580
- function isAllDefined(value) {
16581
- var allDefined = true;
16582
- forEach(value, function(val) {
16583
- if (!isDefined(val)) allDefined = false;
16584
- });
16585
- return allDefined;
16586
- }
16587
- }
16588
-
16589
- function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
16590
- var unwatch = scope.$watch(function constantWatch(scope) {
16591
- unwatch();
16592
- return parsedExpression(scope);
16593
- }, listener, objectEquality);
16594
- return unwatch;
16595
- }
16596
-
16597
- function addInterceptor(parsedExpression, interceptorFn) {
16598
- if (!interceptorFn) return parsedExpression;
16599
- var watchDelegate = parsedExpression.$$watchDelegate;
16600
- var useInputs = false;
16601
-
16602
- var regularWatch =
16603
- watchDelegate !== oneTimeLiteralWatchDelegate &&
16604
- watchDelegate !== oneTimeWatchDelegate;
16605
-
16606
- var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
16607
- var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
16608
- return interceptorFn(value, scope, locals);
16609
- } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
16610
- var value = parsedExpression(scope, locals, assign, inputs);
16611
- var result = interceptorFn(value, scope, locals);
16612
- // we only return the interceptor's result if the
16613
- // initial value is defined (for bind-once)
16614
- return isDefined(value) ? result : value;
16615
- };
16616
-
16617
- // Propagate $$watchDelegates other then inputsWatchDelegate
16618
- useInputs = !parsedExpression.inputs;
16619
- if (watchDelegate && watchDelegate !== inputsWatchDelegate) {
16620
- fn.$$watchDelegate = watchDelegate;
16621
- fn.inputs = parsedExpression.inputs;
16622
- } else if (!interceptorFn.$stateful) {
16623
- // Treat interceptor like filters - assume non-stateful by default and use the inputsWatchDelegate
16624
- fn.$$watchDelegate = inputsWatchDelegate;
16625
- fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
16626
- }
16627
-
16628
- if (fn.inputs) {
16629
- fn.inputs = fn.inputs.map(function(e) {
16630
- // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
16631
- // potentially non-pure interceptor function.
16632
- if (e.isPure === PURITY_RELATIVE) {
16633
- return function depurifier(s) { return e(s); };
16634
- }
16635
- return e;
16636
- });
16637
- }
16638
-
16639
- return fn;
16640
- }
16641
- }];
16642
- }
16643
-
16644
- /**
16645
- * @ngdoc service
16646
- * @name $q
16647
- * @requires $rootScope
16648
- *
16649
- * @description
16650
- * A service that helps you run functions asynchronously, and use their return values (or exceptions)
16651
- * when they are done processing.
16652
- *
16653
- * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred
16654
- * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
16655
- *
16656
- * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
16657
- * implementations, and the other which resembles ES6 (ES2015) promises to some degree.
16658
- *
16659
- * # $q constructor
16660
- *
16661
- * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
16662
- * function as the first argument. This is similar to the native Promise implementation from ES6,
16663
- * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
16664
- *
16665
- * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
16666
- * available yet.
16667
- *
16668
- * It can be used like so:
16669
- *
16670
- * ```js
16671
- * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
16672
- * // are available in the current lexical scope (they could have been injected or passed in).
16673
- *
16674
- * function asyncGreet(name) {
16675
- * // perform some asynchronous operation, resolve or reject the promise when appropriate.
16676
- * return $q(function(resolve, reject) {
16677
- * setTimeout(function() {
16678
- * if (okToGreet(name)) {
16679
- * resolve('Hello, ' + name + '!');
16680
- * } else {
16681
- * reject('Greeting ' + name + ' is not allowed.');
16682
- * }
16683
- * }, 1000);
16684
- * });
16685
- * }
16686
- *
16687
- * var promise = asyncGreet('Robin Hood');
16688
- * promise.then(function(greeting) {
16689
- * alert('Success: ' + greeting);
16690
- * }, function(reason) {
16691
- * alert('Failed: ' + reason);
16692
- * });
16693
- * ```
16694
- *
16695
- * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
16696
- *
16697
- * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise.
16698
- *
16699
- * However, the more traditional CommonJS-style usage is still available, and documented below.
16700
- *
16701
- * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
16702
- * interface for interacting with an object that represents the result of an action that is
16703
- * performed asynchronously, and may or may not be finished at any given point in time.
16704
- *
16705
- * From the perspective of dealing with error handling, deferred and promise APIs are to
16706
- * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
16707
- *
16708
- * ```js
16709
- * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
16710
- * // are available in the current lexical scope (they could have been injected or passed in).
16711
- *
16712
- * function asyncGreet(name) {
16713
- * var deferred = $q.defer();
16714
- *
16715
- * setTimeout(function() {
16716
- * deferred.notify('About to greet ' + name + '.');
16717
- *
16718
- * if (okToGreet(name)) {
16719
- * deferred.resolve('Hello, ' + name + '!');
16720
- * } else {
16721
- * deferred.reject('Greeting ' + name + ' is not allowed.');
16722
- * }
16723
- * }, 1000);
16724
- *
16725
- * return deferred.promise;
16726
- * }
16727
- *
16728
- * var promise = asyncGreet('Robin Hood');
16729
- * promise.then(function(greeting) {
16730
- * alert('Success: ' + greeting);
16731
- * }, function(reason) {
16732
- * alert('Failed: ' + reason);
16733
- * }, function(update) {
16734
- * alert('Got notification: ' + update);
16735
- * });
16736
- * ```
16737
- *
16738
- * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
16739
- * comes in the way of guarantees that promise and deferred APIs make, see
16740
- * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
16741
- *
16742
- * Additionally the promise api allows for composition that is very hard to do with the
16743
- * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
16744
- * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
16745
- * section on serial or parallel joining of promises.
16746
- *
16747
- * # The Deferred API
16748
- *
16749
- * A new instance of deferred is constructed by calling `$q.defer()`.
16750
- *
16751
- * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
16752
- * that can be used for signaling the successful or unsuccessful completion, as well as the status
16753
- * of the task.
16754
- *
16755
- * **Methods**
16756
- *
16757
- * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
16758
- * constructed via `$q.reject`, the promise will be rejected instead.
16759
- * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
16760
- * resolving it with a rejection constructed via `$q.reject`.
16761
- * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
16762
- * multiple times before the promise is either resolved or rejected.
16763
- *
16764
- * **Properties**
16765
- *
16766
- * - promise – `{Promise}` – promise object associated with this deferred.
16767
- *
16768
- *
16769
- * # The Promise API
16770
- *
16771
- * A new promise instance is created when a deferred instance is created and can be retrieved by
16772
- * calling `deferred.promise`.
16773
- *
16774
- * The purpose of the promise object is to allow for interested parties to get access to the result
16775
- * of the deferred task when it completes.
16776
- *
16777
- * **Methods**
16778
- *
16779
- * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or
16780
- * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
16781
- * as soon as the result is available. The callbacks are called with a single argument: the result
16782
- * or rejection reason. Additionally, the notify callback may be called zero or more times to
16783
- * provide a progress indication, before the promise is resolved or rejected.
16784
- *
16785
- * This method *returns a new promise* which is resolved or rejected via the return value of the
16786
- * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
16787
- * with the value which is resolved in that promise using
16788
- * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
16789
- * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
16790
- * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback
16791
- * arguments are optional.
16792
- *
16793
- * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
16794
- *
16795
- * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
16796
- * but to do so without modifying the final value. This is useful to release resources or do some
16797
- * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
16798
- * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
16799
- * more information.
16800
- *
16801
- * # Chaining promises
16802
- *
16803
- * Because calling the `then` method of a promise returns a new derived promise, it is easily
16804
- * possible to create a chain of promises:
16805
- *
16806
- * ```js
16807
- * promiseB = promiseA.then(function(result) {
16808
- * return result + 1;
16809
- * });
16810
- *
16811
- * // promiseB will be resolved immediately after promiseA is resolved and its value
16812
- * // will be the result of promiseA incremented by 1
16813
- * ```
16814
- *
16815
- * It is possible to create chains of any length and since a promise can be resolved with another
16816
- * promise (which will defer its resolution further), it is possible to pause/defer resolution of
16817
- * the promises at any point in the chain. This makes it possible to implement powerful APIs like
16818
- * $http's response interceptors.
16819
- *
16820
- *
16821
- * # Differences between Kris Kowal's Q and $q
16822
- *
16823
- * There are two main differences:
16824
- *
16825
- * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
16826
- * mechanism in angular, which means faster propagation of resolution or rejection into your
16827
- * models and avoiding unnecessary browser repaints, which would result in flickering UI.
16828
- * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
16829
- * all the important functionality needed for common async tasks.
16830
- *
16831
- * # Testing
16832
- *
16833
- * ```js
16834
- * it('should simulate promise', inject(function($q, $rootScope) {
16835
- * var deferred = $q.defer();
16836
- * var promise = deferred.promise;
16837
- * var resolvedValue;
16838
- *
16839
- * promise.then(function(value) { resolvedValue = value; });
16840
- * expect(resolvedValue).toBeUndefined();
16841
- *
16842
- * // Simulate resolving of promise
16843
- * deferred.resolve(123);
16844
- * // Note that the 'then' function does not get called synchronously.
16845
- * // This is because we want the promise API to always be async, whether or not
16846
- * // it got called synchronously or asynchronously.
16847
- * expect(resolvedValue).toBeUndefined();
16848
- *
16849
- * // Propagate promise resolution to 'then' functions using $apply().
16850
- * $rootScope.$apply();
16851
- * expect(resolvedValue).toEqual(123);
16852
- * }));
16853
- * ```
16854
- *
16855
- * @param {function(function, function)} resolver Function which is responsible for resolving or
16856
- * rejecting the newly created promise. The first parameter is a function which resolves the
16857
- * promise, the second parameter is a function which rejects the promise.
16858
- *
16859
- * @returns {Promise} The newly created promise.
16860
- */
16861
- /**
16862
- * @ngdoc provider
16863
- * @name $qProvider
16864
- * @this
16865
- *
16866
- * @description
16867
- */
16868
- function $QProvider() {
16869
- var errorOnUnhandledRejections = true;
16870
- this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
16871
- return qFactory(function(callback) {
16872
- $rootScope.$evalAsync(callback);
16873
- }, $exceptionHandler, errorOnUnhandledRejections);
16874
- }];
16875
-
16876
- /**
16877
- * @ngdoc method
16878
- * @name $qProvider#errorOnUnhandledRejections
16879
- * @kind function
16880
- *
16881
- * @description
16882
- * Retrieves or overrides whether to generate an error when a rejected promise is not handled.
16883
- * This feature is enabled by default.
16884
- *
16885
- * @param {boolean=} value Whether to generate an error when a rejected promise is not handled.
16886
- * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for
16887
- * chaining otherwise.
16888
- */
16889
- this.errorOnUnhandledRejections = function(value) {
16890
- if (isDefined(value)) {
16891
- errorOnUnhandledRejections = value;
16892
- return this;
16893
- } else {
16894
- return errorOnUnhandledRejections;
16895
- }
16896
- };
16897
- }
16898
-
16899
- /** @this */
16900
- function $$QProvider() {
16901
- var errorOnUnhandledRejections = true;
16902
- this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
16903
- return qFactory(function(callback) {
16904
- $browser.defer(callback);
16905
- }, $exceptionHandler, errorOnUnhandledRejections);
16906
- }];
16907
-
16908
- this.errorOnUnhandledRejections = function(value) {
16909
- if (isDefined(value)) {
16910
- errorOnUnhandledRejections = value;
16911
- return this;
16912
- } else {
16913
- return errorOnUnhandledRejections;
16914
- }
16915
- };
16916
- }
16917
-
16918
- /**
16919
- * Constructs a promise manager.
16920
- *
16921
- * @param {function(function)} nextTick Function for executing functions in the next turn.
16922
- * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
16923
- * debugging purposes.
16924
- * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled
16925
- * promises rejections.
16926
- * @returns {object} Promise manager.
16927
- */
16928
- function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
16929
- var $qMinErr = minErr('$q', TypeError);
16930
- var queueSize = 0;
16931
- var checkQueue = [];
16932
-
16933
- /**
16934
- * @ngdoc method
16935
- * @name ng.$q#defer
16936
- * @kind function
16937
- *
16938
- * @description
16939
- * Creates a `Deferred` object which represents a task which will finish in the future.
16940
- *
16941
- * @returns {Deferred} Returns a new instance of deferred.
16942
- */
16943
- function defer() {
16944
- return new Deferred();
16945
- }
16946
-
16947
- function Deferred() {
16948
- var promise = this.promise = new Promise();
16949
- //Non prototype methods necessary to support unbound execution :/
16950
- this.resolve = function(val) { resolvePromise(promise, val); };
16951
- this.reject = function(reason) { rejectPromise(promise, reason); };
16952
- this.notify = function(progress) { notifyPromise(promise, progress); };
16953
- }
16954
-
16955
-
16956
- function Promise() {
16957
- this.$$state = { status: 0 };
16958
- }
16959
-
16960
- extend(Promise.prototype, {
16961
- then: function(onFulfilled, onRejected, progressBack) {
16962
- if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
16963
- return this;
16964
- }
16965
- var result = new Promise();
16966
-
16967
- this.$$state.pending = this.$$state.pending || [];
16968
- this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
16969
- if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
16970
-
16971
- return result;
16972
- },
16973
-
16974
- 'catch': function(callback) {
16975
- return this.then(null, callback);
16976
- },
16977
-
16978
- 'finally': function(callback, progressBack) {
16979
- return this.then(function(value) {
16980
- return handleCallback(value, resolve, callback);
16981
- }, function(error) {
16982
- return handleCallback(error, reject, callback);
16983
- }, progressBack);
16984
- }
16985
- });
16986
-
16987
- function processQueue(state) {
16988
- var fn, promise, pending;
16989
-
16990
- pending = state.pending;
16991
- state.processScheduled = false;
16992
- state.pending = undefined;
16993
- try {
16994
- for (var i = 0, ii = pending.length; i < ii; ++i) {
16995
- markQStateExceptionHandled(state);
16996
- promise = pending[i][0];
16997
- fn = pending[i][state.status];
16998
- try {
16999
- if (isFunction(fn)) {
17000
- resolvePromise(promise, fn(state.value));
17001
- } else if (state.status === 1) {
17002
- resolvePromise(promise, state.value);
17003
- } else {
17004
- rejectPromise(promise, state.value);
17005
- }
17006
- } catch (e) {
17007
- rejectPromise(promise, e);
17008
- }
17009
- }
17010
- } finally {
17011
- --queueSize;
17012
- if (errorOnUnhandledRejections && queueSize === 0) {
17013
- nextTick(processChecks);
17014
- }
17015
- }
17016
- }
17017
-
17018
- function processChecks() {
17019
- // eslint-disable-next-line no-unmodified-loop-condition
17020
- while (!queueSize && checkQueue.length) {
17021
- var toCheck = checkQueue.shift();
17022
- if (!isStateExceptionHandled(toCheck)) {
17023
- markQStateExceptionHandled(toCheck);
17024
- var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
17025
- if (isError(toCheck.value)) {
17026
- exceptionHandler(toCheck.value, errorMessage);
17027
- } else {
17028
- exceptionHandler(errorMessage);
17029
- }
17030
- }
17031
- }
17032
- }
17033
-
17034
- function scheduleProcessQueue(state) {
17035
- if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) {
17036
- if (queueSize === 0 && checkQueue.length === 0) {
17037
- nextTick(processChecks);
17038
- }
17039
- checkQueue.push(state);
17040
- }
17041
- if (state.processScheduled || !state.pending) return;
17042
- state.processScheduled = true;
17043
- ++queueSize;
17044
- nextTick(function() { processQueue(state); });
17045
- }
17046
-
17047
- function resolvePromise(promise, val) {
17048
- if (promise.$$state.status) return;
17049
- if (val === promise) {
17050
- $$reject(promise, $qMinErr(
17051
- 'qcycle',
17052
- 'Expected promise to be resolved with value other than itself \'{0}\'',
17053
- val));
17054
- } else {
17055
- $$resolve(promise, val);
17056
- }
17057
-
17058
- }
17059
-
17060
- function $$resolve(promise, val) {
17061
- var then;
17062
- var done = false;
17063
- try {
17064
- if (isObject(val) || isFunction(val)) then = val.then;
17065
- if (isFunction(then)) {
17066
- promise.$$state.status = -1;
17067
- then.call(val, doResolve, doReject, doNotify);
17068
- } else {
17069
- promise.$$state.value = val;
17070
- promise.$$state.status = 1;
17071
- scheduleProcessQueue(promise.$$state);
17072
- }
17073
- } catch (e) {
17074
- doReject(e);
17075
- }
17076
-
17077
- function doResolve(val) {
17078
- if (done) return;
17079
- done = true;
17080
- $$resolve(promise, val);
17081
- }
17082
- function doReject(val) {
17083
- if (done) return;
17084
- done = true;
17085
- $$reject(promise, val);
17086
- }
17087
- function doNotify(progress) {
17088
- notifyPromise(promise, progress);
17089
- }
17090
- }
17091
-
17092
- function rejectPromise(promise, reason) {
17093
- if (promise.$$state.status) return;
17094
- $$reject(promise, reason);
17095
- }
17096
-
17097
- function $$reject(promise, reason) {
17098
- promise.$$state.value = reason;
17099
- promise.$$state.status = 2;
17100
- scheduleProcessQueue(promise.$$state);
17101
- }
17102
-
17103
- function notifyPromise(promise, progress) {
17104
- var callbacks = promise.$$state.pending;
17105
-
17106
- if ((promise.$$state.status <= 0) && callbacks && callbacks.length) {
17107
- nextTick(function() {
17108
- var callback, result;
17109
- for (var i = 0, ii = callbacks.length; i < ii; i++) {
17110
- result = callbacks[i][0];
17111
- callback = callbacks[i][3];
17112
- try {
17113
- notifyPromise(result, isFunction(callback) ? callback(progress) : progress);
17114
- } catch (e) {
17115
- exceptionHandler(e);
17116
- }
17117
- }
17118
- });
17119
- }
17120
- }
17121
-
17122
- /**
17123
- * @ngdoc method
17124
- * @name $q#reject
17125
- * @kind function
17126
- *
17127
- * @description
17128
- * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
17129
- * used to forward rejection in a chain of promises. If you are dealing with the last promise in
17130
- * a promise chain, you don't need to worry about it.
17131
- *
17132
- * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
17133
- * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
17134
- * a promise error callback and you want to forward the error to the promise derived from the
17135
- * current promise, you have to "rethrow" the error by returning a rejection constructed via
17136
- * `reject`.
17137
- *
17138
- * ```js
17139
- * promiseB = promiseA.then(function(result) {
17140
- * // success: do something and resolve promiseB
17141
- * // with the old or a new result
17142
- * return result;
17143
- * }, function(reason) {
17144
- * // error: handle the error if possible and
17145
- * // resolve promiseB with newPromiseOrValue,
17146
- * // otherwise forward the rejection to promiseB
17147
- * if (canHandle(reason)) {
17148
- * // handle the error and recover
17149
- * return newPromiseOrValue;
17150
- * }
17151
- * return $q.reject(reason);
17152
- * });
17153
- * ```
17154
- *
17155
- * @param {*} reason Constant, message, exception or an object representing the rejection reason.
17156
- * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
17157
- */
17158
- function reject(reason) {
17159
- var result = new Promise();
17160
- rejectPromise(result, reason);
17161
- return result;
17162
- }
17163
-
17164
- function handleCallback(value, resolver, callback) {
17165
- var callbackOutput = null;
17166
- try {
17167
- if (isFunction(callback)) callbackOutput = callback();
17168
- } catch (e) {
17169
- return reject(e);
17170
- }
17171
- if (isPromiseLike(callbackOutput)) {
17172
- return callbackOutput.then(function() {
17173
- return resolver(value);
17174
- }, reject);
17175
- } else {
17176
- return resolver(value);
17177
- }
17178
- }
17179
-
17180
- /**
17181
- * @ngdoc method
17182
- * @name $q#when
17183
- * @kind function
17184
- *
17185
- * @description
17186
- * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
17187
- * This is useful when you are dealing with an object that might or might not be a promise, or if
17188
- * the promise comes from a source that can't be trusted.
17189
- *
17190
- * @param {*} value Value or a promise
17191
- * @param {Function=} successCallback
17192
- * @param {Function=} errorCallback
17193
- * @param {Function=} progressCallback
17194
- * @returns {Promise} Returns a promise of the passed value or promise
17195
- */
17196
-
17197
-
17198
- function when(value, callback, errback, progressBack) {
17199
- var result = new Promise();
17200
- resolvePromise(result, value);
17201
- return result.then(callback, errback, progressBack);
17202
- }
17203
-
17204
- /**
17205
- * @ngdoc method
17206
- * @name $q#resolve
17207
- * @kind function
17208
- *
17209
- * @description
17210
- * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
17211
- *
17212
- * @param {*} value Value or a promise
17213
- * @param {Function=} successCallback
17214
- * @param {Function=} errorCallback
17215
- * @param {Function=} progressCallback
17216
- * @returns {Promise} Returns a promise of the passed value or promise
17217
- */
17218
- var resolve = when;
17219
-
17220
- /**
17221
- * @ngdoc method
17222
- * @name $q#all
17223
- * @kind function
17224
- *
17225
- * @description
17226
- * Combines multiple promises into a single promise that is resolved when all of the input
17227
- * promises are resolved.
17228
- *
17229
- * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
17230
- * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
17231
- * each value corresponding to the promise at the same index/key in the `promises` array/hash.
17232
- * If any of the promises is resolved with a rejection, this resulting promise will be rejected
17233
- * with the same rejection value.
17234
- */
17235
-
17236
- function all(promises) {
17237
- var result = new Promise(),
17238
- counter = 0,
17239
- results = isArray(promises) ? [] : {};
17240
-
17241
- forEach(promises, function(promise, key) {
17242
- counter++;
17243
- when(promise).then(function(value) {
17244
- results[key] = value;
17245
- if (!(--counter)) resolvePromise(result, results);
17246
- }, function(reason) {
17247
- rejectPromise(result, reason);
17248
- });
17249
- });
17250
-
17251
- if (counter === 0) {
17252
- resolvePromise(result, results);
17253
- }
17254
-
17255
- return result;
17256
- }
17257
-
17258
- /**
17259
- * @ngdoc method
17260
- * @name $q#race
17261
- * @kind function
17262
- *
17263
- * @description
17264
- * Returns a promise that resolves or rejects as soon as one of those promises
17265
- * resolves or rejects, with the value or reason from that promise.
17266
- *
17267
- * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
17268
- * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises`
17269
- * resolves or rejects, with the value or reason from that promise.
17270
- */
17271
-
17272
- function race(promises) {
17273
- var deferred = defer();
17274
-
17275
- forEach(promises, function(promise) {
17276
- when(promise).then(deferred.resolve, deferred.reject);
17277
- });
17278
-
17279
- return deferred.promise;
17280
- }
17281
-
17282
- function $Q(resolver) {
17283
- if (!isFunction(resolver)) {
17284
- throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver);
17285
- }
17286
-
17287
- var promise = new Promise();
17288
-
17289
- function resolveFn(value) {
17290
- resolvePromise(promise, value);
17291
- }
17292
-
17293
- function rejectFn(reason) {
17294
- rejectPromise(promise, reason);
17295
- }
17296
-
17297
- resolver(resolveFn, rejectFn);
17298
-
17299
- return promise;
17300
- }
17301
-
17302
- // Let's make the instanceof operator work for promises, so that
17303
- // `new $q(fn) instanceof $q` would evaluate to true.
17304
- $Q.prototype = Promise.prototype;
17305
-
17306
- $Q.defer = defer;
17307
- $Q.reject = reject;
17308
- $Q.when = when;
17309
- $Q.resolve = resolve;
17310
- $Q.all = all;
17311
- $Q.race = race;
17312
-
17313
- return $Q;
17314
- }
17315
-
17316
- function isStateExceptionHandled(state) {
17317
- return !!state.pur;
17318
- }
17319
- function markQStateExceptionHandled(state) {
17320
- state.pur = true;
17321
- }
17322
- function markQExceptionHandled(q) {
17323
- markQStateExceptionHandled(q.$$state);
17324
- }
17325
-
17326
- /** @this */
17327
- function $$RAFProvider() { //rAF
17328
- this.$get = ['$window', '$timeout', function($window, $timeout) {
17329
- var requestAnimationFrame = $window.requestAnimationFrame ||
17330
- $window.webkitRequestAnimationFrame;
17331
-
17332
- var cancelAnimationFrame = $window.cancelAnimationFrame ||
17333
- $window.webkitCancelAnimationFrame ||
17334
- $window.webkitCancelRequestAnimationFrame;
17335
-
17336
- var rafSupported = !!requestAnimationFrame;
17337
- var raf = rafSupported
17338
- ? function(fn) {
17339
- var id = requestAnimationFrame(fn);
17340
- return function() {
17341
- cancelAnimationFrame(id);
17342
- };
17343
- }
17344
- : function(fn) {
17345
- var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
17346
- return function() {
17347
- $timeout.cancel(timer);
17348
- };
17349
- };
17350
-
17351
- raf.supported = rafSupported;
17352
-
17353
- return raf;
17354
- }];
17355
- }
17356
-
17357
- /**
17358
- * DESIGN NOTES
17359
- *
17360
- * The design decisions behind the scope are heavily favored for speed and memory consumption.
17361
- *
17362
- * The typical use of scope is to watch the expressions, which most of the time return the same
17363
- * value as last time so we optimize the operation.
17364
- *
17365
- * Closures construction is expensive in terms of speed as well as memory:
17366
- * - No closures, instead use prototypical inheritance for API
17367
- * - Internal state needs to be stored on scope directly, which means that private state is
17368
- * exposed as $$____ properties
17369
- *
17370
- * Loop operations are optimized by using while(count--) { ... }
17371
- * - This means that in order to keep the same order of execution as addition we have to add
17372
- * items to the array at the beginning (unshift) instead of at the end (push)
17373
- *
17374
- * Child scopes are created and removed often
17375
- * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
17376
- *
17377
- * There are fewer watches than observers. This is why you don't want the observer to be implemented
17378
- * in the same way as watch. Watch requires return of the initialization function which is expensive
17379
- * to construct.
17380
- */
17381
-
17382
-
17383
- /**
17384
- * @ngdoc provider
17385
- * @name $rootScopeProvider
17386
- * @description
17387
- *
17388
- * Provider for the $rootScope service.
17389
- */
17390
-
17391
- /**
17392
- * @ngdoc method
17393
- * @name $rootScopeProvider#digestTtl
17394
- * @description
17395
- *
17396
- * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
17397
- * assuming that the model is unstable.
17398
- *
17399
- * The current default is 10 iterations.
17400
- *
17401
- * In complex applications it's possible that the dependencies between `$watch`s will result in
17402
- * several digest iterations. However if an application needs more than the default 10 digest
17403
- * iterations for its model to stabilize then you should investigate what is causing the model to
17404
- * continuously change during the digest.
17405
- *
17406
- * Increasing the TTL could have performance implications, so you should not change it without
17407
- * proper justification.
17408
- *
17409
- * @param {number} limit The number of digest iterations.
17410
- */
17411
-
17412
-
17413
- /**
17414
- * @ngdoc service
17415
- * @name $rootScope
17416
- * @this
17417
- *
17418
- * @description
17419
- *
17420
- * Every application has a single root {@link ng.$rootScope.Scope scope}.
17421
- * All other scopes are descendant scopes of the root scope. Scopes provide separation
17422
- * between the model and the view, via a mechanism for watching the model for changes.
17423
- * They also provide event emission/broadcast and subscription facility. See the
17424
- * {@link guide/scope developer guide on scopes}.
17425
- */
17426
- function $RootScopeProvider() {
17427
- var TTL = 10;
17428
- var $rootScopeMinErr = minErr('$rootScope');
17429
- var lastDirtyWatch = null;
17430
- var applyAsyncId = null;
17431
-
17432
- this.digestTtl = function(value) {
17433
- if (arguments.length) {
17434
- TTL = value;
17435
- }
17436
- return TTL;
17437
- };
17438
-
17439
- function createChildScopeClass(parent) {
17440
- function ChildScope() {
17441
- this.$$watchers = this.$$nextSibling =
17442
- this.$$childHead = this.$$childTail = null;
17443
- this.$$listeners = {};
17444
- this.$$listenerCount = {};
17445
- this.$$watchersCount = 0;
17446
- this.$id = nextUid();
17447
- this.$$ChildScope = null;
17448
- }
17449
- ChildScope.prototype = parent;
17450
- return ChildScope;
17451
- }
17452
-
17453
- this.$get = ['$exceptionHandler', '$parse', '$browser',
17454
- function($exceptionHandler, $parse, $browser) {
17455
-
17456
- function destroyChildScope($event) {
17457
- $event.currentScope.$$destroyed = true;
17458
- }
17459
-
17460
- function cleanUpScope($scope) {
17461
-
17462
- // Support: IE 9 only
17463
- if (msie === 9) {
17464
- // There is a memory leak in IE9 if all child scopes are not disconnected
17465
- // completely when a scope is destroyed. So this code will recurse up through
17466
- // all this scopes children
17467
- //
17468
- // See issue https://github.com/angular/angular.js/issues/10706
17469
- if ($scope.$$childHead) {
17470
- cleanUpScope($scope.$$childHead);
17471
- }
17472
- if ($scope.$$nextSibling) {
17473
- cleanUpScope($scope.$$nextSibling);
17474
- }
17475
- }
17476
-
17477
- // The code below works around IE9 and V8's memory leaks
17478
- //
17479
- // See:
17480
- // - https://code.google.com/p/v8/issues/detail?id=2073#c26
17481
- // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
17482
- // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
17483
-
17484
- $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
17485
- $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
17486
- }
17487
-
17488
- /**
17489
- * @ngdoc type
17490
- * @name $rootScope.Scope
17491
- *
17492
- * @description
17493
- * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
17494
- * {@link auto.$injector $injector}. Child scopes are created using the
17495
- * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
17496
- * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
17497
- * an in-depth introduction and usage examples.
17498
- *
17499
- *
17500
- * # Inheritance
17501
- * A scope can inherit from a parent scope, as in this example:
17502
- * ```js
17503
- var parent = $rootScope;
17504
- var child = parent.$new();
17505
-
17506
- parent.salutation = "Hello";
17507
- expect(child.salutation).toEqual('Hello');
17508
-
17509
- child.salutation = "Welcome";
17510
- expect(child.salutation).toEqual('Welcome');
17511
- expect(parent.salutation).toEqual('Hello');
17512
- * ```
17513
- *
17514
- * When interacting with `Scope` in tests, additional helper methods are available on the
17515
- * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
17516
- * details.
17517
- *
17518
- *
17519
- * @param {Object.<string, function()>=} providers Map of service factory which need to be
17520
- * provided for the current scope. Defaults to {@link ng}.
17521
- * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
17522
- * append/override services provided by `providers`. This is handy
17523
- * when unit-testing and having the need to override a default
17524
- * service.
17525
- * @returns {Object} Newly created scope.
17526
- *
17527
- */
17528
- function Scope() {
17529
- this.$id = nextUid();
17530
- this.$$phase = this.$parent = this.$$watchers =
17531
- this.$$nextSibling = this.$$prevSibling =
17532
- this.$$childHead = this.$$childTail = null;
17533
- this.$root = this;
17534
- this.$$destroyed = false;
17535
- this.$$listeners = {};
17536
- this.$$listenerCount = {};
17537
- this.$$watchersCount = 0;
17538
- this.$$isolateBindings = null;
17539
- }
17540
-
17541
- /**
17542
- * @ngdoc property
17543
- * @name $rootScope.Scope#$id
17544
- *
17545
- * @description
17546
- * Unique scope ID (monotonically increasing) useful for debugging.
17547
- */
17548
-
17549
- /**
17550
- * @ngdoc property
17551
- * @name $rootScope.Scope#$parent
17552
- *
17553
- * @description
17554
- * Reference to the parent scope.
17555
- */
17556
-
17557
- /**
17558
- * @ngdoc property
17559
- * @name $rootScope.Scope#$root
17560
- *
17561
- * @description
17562
- * Reference to the root scope.
17563
- */
17564
-
17565
- Scope.prototype = {
17566
- constructor: Scope,
17567
- /**
17568
- * @ngdoc method
17569
- * @name $rootScope.Scope#$new
17570
- * @kind function
17571
- *
17572
- * @description
17573
- * Creates a new child {@link ng.$rootScope.Scope scope}.
17574
- *
17575
- * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
17576
- * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
17577
- *
17578
- * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
17579
- * desired for the scope and its child scopes to be permanently detached from the parent and
17580
- * thus stop participating in model change detection and listener notification by invoking.
17581
- *
17582
- * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
17583
- * parent scope. The scope is isolated, as it can not see parent scope properties.
17584
- * When creating widgets, it is useful for the widget to not accidentally read parent
17585
- * state.
17586
- *
17587
- * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
17588
- * of the newly created scope. Defaults to `this` scope if not provided.
17589
- * This is used when creating a transclude scope to correctly place it
17590
- * in the scope hierarchy while maintaining the correct prototypical
17591
- * inheritance.
17592
- *
17593
- * @returns {Object} The newly created child scope.
17594
- *
17595
- */
17596
- $new: function(isolate, parent) {
17597
- var child;
17598
-
17599
- parent = parent || this;
17600
-
17601
- if (isolate) {
17602
- child = new Scope();
17603
- child.$root = this.$root;
17604
- } else {
17605
- // Only create a child scope class if somebody asks for one,
17606
- // but cache it to allow the VM to optimize lookups.
17607
- if (!this.$$ChildScope) {
17608
- this.$$ChildScope = createChildScopeClass(this);
17609
- }
17610
- child = new this.$$ChildScope();
17611
- }
17612
- child.$parent = parent;
17613
- child.$$prevSibling = parent.$$childTail;
17614
- if (parent.$$childHead) {
17615
- parent.$$childTail.$$nextSibling = child;
17616
- parent.$$childTail = child;
17617
- } else {
17618
- parent.$$childHead = parent.$$childTail = child;
17619
- }
17620
-
17621
- // When the new scope is not isolated or we inherit from `this`, and
17622
- // the parent scope is destroyed, the property `$$destroyed` is inherited
17623
- // prototypically. In all other cases, this property needs to be set
17624
- // when the parent scope is destroyed.
17625
- // The listener needs to be added after the parent is set
17626
- if (isolate || parent !== this) child.$on('$destroy', destroyChildScope);
17627
-
17628
- return child;
17629
- },
17630
-
17631
- /**
17632
- * @ngdoc method
17633
- * @name $rootScope.Scope#$watch
17634
- * @kind function
17635
- *
17636
- * @description
17637
- * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
17638
- *
17639
- * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
17640
- * $digest()} and should return the value that will be watched. (`watchExpression` should not change
17641
- * its value when executed multiple times with the same input because it may be executed multiple
17642
- * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
17643
- * [idempotent](http://en.wikipedia.org/wiki/Idempotence).)
17644
- * - The `listener` is called only when the value from the current `watchExpression` and the
17645
- * previous call to `watchExpression` are not equal (with the exception of the initial run,
17646
- * see below). Inequality is determined according to reference inequality,
17647
- * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
17648
- * via the `!==` Javascript operator, unless `objectEquality == true`
17649
- * (see next point)
17650
- * - When `objectEquality == true`, inequality of the `watchExpression` is determined
17651
- * according to the {@link angular.equals} function. To save the value of the object for
17652
- * later comparison, the {@link angular.copy} function is used. This therefore means that
17653
- * watching complex objects will have adverse memory and performance implications.
17654
- * - This should not be used to watch for changes in objects that are
17655
- * or contain [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
17656
- * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
17657
- * This is achieved by rerunning the watchers until no changes are detected. The rerun
17658
- * iteration limit is 10 to prevent an infinite loop deadlock.
17659
- *
17660
- *
17661
- * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
17662
- * you can register a `watchExpression` function with no `listener`. (Be prepared for
17663
- * multiple calls to your `watchExpression` because it will execute multiple times in a
17664
- * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
17665
- *
17666
- * After a watcher is registered with the scope, the `listener` fn is called asynchronously
17667
- * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
17668
- * watcher. In rare cases, this is undesirable because the listener is called when the result
17669
- * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
17670
- * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
17671
- * listener was called due to initialization.
17672
- *
17673
- *
17674
- *
17675
- * # Example
17676
- * ```js
17677
- // let's assume that scope was dependency injected as the $rootScope
17678
- var scope = $rootScope;
17679
- scope.name = 'misko';
17680
- scope.counter = 0;
17681
-
17682
- expect(scope.counter).toEqual(0);
17683
- scope.$watch('name', function(newValue, oldValue) {
17684
- scope.counter = scope.counter + 1;
17685
- });
17686
- expect(scope.counter).toEqual(0);
17687
-
17688
- scope.$digest();
17689
- // the listener is always called during the first $digest loop after it was registered
17690
- expect(scope.counter).toEqual(1);
17691
-
17692
- scope.$digest();
17693
- // but now it will not be called unless the value changes
17694
- expect(scope.counter).toEqual(1);
17695
-
17696
- scope.name = 'adam';
17697
- scope.$digest();
17698
- expect(scope.counter).toEqual(2);
17699
-
17700
-
17701
-
17702
- // Using a function as a watchExpression
17703
- var food;
17704
- scope.foodCounter = 0;
17705
- expect(scope.foodCounter).toEqual(0);
17706
- scope.$watch(
17707
- // This function returns the value being watched. It is called for each turn of the $digest loop
17708
- function() { return food; },
17709
- // This is the change listener, called when the value returned from the above function changes
17710
- function(newValue, oldValue) {
17711
- if ( newValue !== oldValue ) {
17712
- // Only increment the counter if the value changed
17713
- scope.foodCounter = scope.foodCounter + 1;
17714
- }
17715
- }
17716
- );
17717
- // No digest has been run so the counter will be zero
17718
- expect(scope.foodCounter).toEqual(0);
17719
-
17720
- // Run the digest but since food has not changed count will still be zero
17721
- scope.$digest();
17722
- expect(scope.foodCounter).toEqual(0);
17723
-
17724
- // Update food and run digest. Now the counter will increment
17725
- food = 'cheeseburger';
17726
- scope.$digest();
17727
- expect(scope.foodCounter).toEqual(1);
17728
-
17729
- * ```
17730
- *
17731
- *
17732
- *
17733
- * @param {(function()|string)} watchExpression Expression that is evaluated on each
17734
- * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
17735
- * a call to the `listener`.
17736
- *
17737
- * - `string`: Evaluated as {@link guide/expression expression}
17738
- * - `function(scope)`: called with current `scope` as a parameter.
17739
- * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
17740
- * of `watchExpression` changes.
17741
- *
17742
- * - `newVal` contains the current value of the `watchExpression`
17743
- * - `oldVal` contains the previous value of the `watchExpression`
17744
- * - `scope` refers to the current scope
17745
- * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
17746
- * comparing for reference equality.
17747
- * @returns {function()} Returns a deregistration function for this listener.
17748
- */
17749
- $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
17750
- var get = $parse(watchExp);
17751
-
17752
- if (get.$$watchDelegate) {
17753
- return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
17754
- }
17755
- var scope = this,
17756
- array = scope.$$watchers,
17757
- watcher = {
17758
- fn: listener,
17759
- last: initWatchVal,
17760
- get: get,
17761
- exp: prettyPrintExpression || watchExp,
17762
- eq: !!objectEquality
17763
- };
17764
-
17765
- lastDirtyWatch = null;
17766
-
17767
- if (!isFunction(listener)) {
17768
- watcher.fn = noop;
17769
- }
17770
-
17771
- if (!array) {
17772
- array = scope.$$watchers = [];
17773
- array.$$digestWatchIndex = -1;
17774
- }
17775
- // we use unshift since we use a while loop in $digest for speed.
17776
- // the while loop reads in reverse order.
17777
- array.unshift(watcher);
17778
- array.$$digestWatchIndex++;
17779
- incrementWatchersCount(this, 1);
17780
-
17781
- return function deregisterWatch() {
17782
- var index = arrayRemove(array, watcher);
17783
- if (index >= 0) {
17784
- incrementWatchersCount(scope, -1);
17785
- if (index < array.$$digestWatchIndex) {
17786
- array.$$digestWatchIndex--;
17787
- }
17788
- }
17789
- lastDirtyWatch = null;
17790
- };
17791
- },
17792
-
17793
- /**
17794
- * @ngdoc method
17795
- * @name $rootScope.Scope#$watchGroup
17796
- * @kind function
17797
- *
17798
- * @description
17799
- * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
17800
- * If any one expression in the collection changes the `listener` is executed.
17801
- *
17802
- * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return
17803
- * values are examined for changes on every call to `$digest`.
17804
- * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
17805
- *
17806
- * `$watchGroup` is more performant than watching each expression individually, and should be
17807
- * used when the listener does not need to know which expression has changed.
17808
- * If the listener needs to know which expression has changed,
17809
- * {@link ng.$rootScope.Scope#$watch $watch()} or
17810
- * {@link ng.$rootScope.Scope#$watchCollection $watchCollection()} should be used.
17811
- *
17812
- * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
17813
- * watched using {@link ng.$rootScope.Scope#$watch $watch()}
17814
- *
17815
- * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
17816
- * expression in `watchExpressions` changes
17817
- * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
17818
- * those of `watchExpression`
17819
- * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
17820
- * those of `watchExpression`.
17821
- *
17822
- * Note that `newValues` and `oldValues` reflect the differences in each **individual**
17823
- * expression, and not the difference of the values between each call of the listener.
17824
- * That means the difference between `newValues` and `oldValues` cannot be used to determine
17825
- * which expression has changed / remained stable:
17826
- *
17827
- * ```js
17828
- *
17829
- * $scope.$watchGroup(['v1', 'v2'], function(newValues, oldValues) {
17830
- * console.log(newValues, oldValues);
17831
- * });
17832
- *
17833
- * // newValues, oldValues initially
17834
- * // [undefined, undefined], [undefined, undefined]
17835
- *
17836
- * $scope.v1 = 'a';
17837
- * $scope.v2 = 'a';
17838
- *
17839
- * // ['a', 'a'], [undefined, undefined]
17840
- *
17841
- * $scope.v2 = 'b'
17842
- *
17843
- * // v1 hasn't changed since it became `'a'`, therefore its oldValue is still `undefined`
17844
- * // ['a', 'b'], [undefined, 'a']
17845
- *
17846
- * ```
17847
- *
17848
- * The `scope` refers to the current scope.
17849
- * @returns {function()} Returns a de-registration function for all listeners.
17850
- */
17851
- $watchGroup: function(watchExpressions, listener) {
17852
- var oldValues = new Array(watchExpressions.length);
17853
- var newValues = new Array(watchExpressions.length);
17854
- var deregisterFns = [];
17855
- var self = this;
17856
- var changeReactionScheduled = false;
17857
- var firstRun = true;
17858
-
17859
- if (!watchExpressions.length) {
17860
- // No expressions means we call the listener ASAP
17861
- var shouldCall = true;
17862
- self.$evalAsync(function() {
17863
- if (shouldCall) listener(newValues, newValues, self);
17864
- });
17865
- return function deregisterWatchGroup() {
17866
- shouldCall = false;
17867
- };
17868
- }
17869
-
17870
- if (watchExpressions.length === 1) {
17871
- // Special case size of one
17872
- return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
17873
- newValues[0] = value;
17874
- oldValues[0] = oldValue;
17875
- listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
17876
- });
17877
- }
17878
-
17879
- forEach(watchExpressions, function(expr, i) {
17880
- var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
17881
- newValues[i] = value;
17882
- oldValues[i] = oldValue;
17883
- if (!changeReactionScheduled) {
17884
- changeReactionScheduled = true;
17885
- self.$evalAsync(watchGroupAction);
17886
- }
17887
- });
17888
- deregisterFns.push(unwatchFn);
17889
- });
17890
-
17891
- function watchGroupAction() {
17892
- changeReactionScheduled = false;
17893
-
17894
- if (firstRun) {
17895
- firstRun = false;
17896
- listener(newValues, newValues, self);
17897
- } else {
17898
- listener(newValues, oldValues, self);
17899
- }
17900
- }
17901
-
17902
- return function deregisterWatchGroup() {
17903
- while (deregisterFns.length) {
17904
- deregisterFns.shift()();
17905
- }
17906
- };
17907
- },
17908
-
17909
-
17910
- /**
17911
- * @ngdoc method
17912
- * @name $rootScope.Scope#$watchCollection
17913
- * @kind function
17914
- *
17915
- * @description
17916
- * Shallow watches the properties of an object and fires whenever any of the properties change
17917
- * (for arrays, this implies watching the array items; for object maps, this implies watching
17918
- * the properties). If a change is detected, the `listener` callback is fired.
17919
- *
17920
- * - The `obj` collection is observed via standard $watch operation and is examined on every
17921
- * call to $digest() to see if any items have been added, removed, or moved.
17922
- * - The `listener` is called whenever anything within the `obj` has changed. Examples include
17923
- * adding, removing, and moving items belonging to an object or array.
17924
- *
17925
- *
17926
- * # Example
17927
- * ```js
17928
- $scope.names = ['igor', 'matias', 'misko', 'james'];
17929
- $scope.dataCount = 4;
17930
-
17931
- $scope.$watchCollection('names', function(newNames, oldNames) {
17932
- $scope.dataCount = newNames.length;
17933
- });
17934
-
17935
- expect($scope.dataCount).toEqual(4);
17936
- $scope.$digest();
17937
-
17938
- //still at 4 ... no changes
17939
- expect($scope.dataCount).toEqual(4);
17940
-
17941
- $scope.names.pop();
17942
- $scope.$digest();
17943
-
17944
- //now there's been a change
17945
- expect($scope.dataCount).toEqual(3);
17946
- * ```
17947
- *
17948
- *
17949
- * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
17950
- * expression value should evaluate to an object or an array which is observed on each
17951
- * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
17952
- * collection will trigger a call to the `listener`.
17953
- *
17954
- * @param {function(newCollection, oldCollection, scope)} listener a callback function called
17955
- * when a change is detected.
17956
- * - The `newCollection` object is the newly modified data obtained from the `obj` expression
17957
- * - The `oldCollection` object is a copy of the former collection data.
17958
- * Due to performance considerations, the`oldCollection` value is computed only if the
17959
- * `listener` function declares two or more arguments.
17960
- * - The `scope` argument refers to the current scope.
17961
- *
17962
- * @returns {function()} Returns a de-registration function for this listener. When the
17963
- * de-registration function is executed, the internal watch operation is terminated.
17964
- */
17965
- $watchCollection: function(obj, listener) {
17966
- $watchCollectionInterceptor.$stateful = true;
17967
-
17968
- var self = this;
17969
- // the current value, updated on each dirty-check run
17970
- var newValue;
17971
- // a shallow copy of the newValue from the last dirty-check run,
17972
- // updated to match newValue during dirty-check run
17973
- var oldValue;
17974
- // a shallow copy of the newValue from when the last change happened
17975
- var veryOldValue;
17976
- // only track veryOldValue if the listener is asking for it
17977
- var trackVeryOldValue = (listener.length > 1);
17978
- var changeDetected = 0;
17979
- var changeDetector = $parse(obj, $watchCollectionInterceptor);
17980
- var internalArray = [];
17981
- var internalObject = {};
17982
- var initRun = true;
17983
- var oldLength = 0;
17984
-
17985
- function $watchCollectionInterceptor(_value) {
17986
- newValue = _value;
17987
- var newLength, key, bothNaN, newItem, oldItem;
17988
-
17989
- // If the new value is undefined, then return undefined as the watch may be a one-time watch
17990
- if (isUndefined(newValue)) return;
17991
-
17992
- if (!isObject(newValue)) { // if primitive
17993
- if (oldValue !== newValue) {
17994
- oldValue = newValue;
17995
- changeDetected++;
17996
- }
17997
- } else if (isArrayLike(newValue)) {
17998
- if (oldValue !== internalArray) {
17999
- // we are transitioning from something which was not an array into array.
18000
- oldValue = internalArray;
18001
- oldLength = oldValue.length = 0;
18002
- changeDetected++;
18003
- }
18004
-
18005
- newLength = newValue.length;
18006
-
18007
- if (oldLength !== newLength) {
18008
- // if lengths do not match we need to trigger change notification
18009
- changeDetected++;
18010
- oldValue.length = oldLength = newLength;
18011
- }
18012
- // copy the items to oldValue and look for changes.
18013
- for (var i = 0; i < newLength; i++) {
18014
- oldItem = oldValue[i];
18015
- newItem = newValue[i];
18016
-
18017
- // eslint-disable-next-line no-self-compare
18018
- bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
18019
- if (!bothNaN && (oldItem !== newItem)) {
18020
- changeDetected++;
18021
- oldValue[i] = newItem;
18022
- }
18023
- }
18024
- } else {
18025
- if (oldValue !== internalObject) {
18026
- // we are transitioning from something which was not an object into object.
18027
- oldValue = internalObject = {};
18028
- oldLength = 0;
18029
- changeDetected++;
18030
- }
18031
- // copy the items to oldValue and look for changes.
18032
- newLength = 0;
18033
- for (key in newValue) {
18034
- if (hasOwnProperty.call(newValue, key)) {
18035
- newLength++;
18036
- newItem = newValue[key];
18037
- oldItem = oldValue[key];
18038
-
18039
- if (key in oldValue) {
18040
- // eslint-disable-next-line no-self-compare
18041
- bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
18042
- if (!bothNaN && (oldItem !== newItem)) {
18043
- changeDetected++;
18044
- oldValue[key] = newItem;
18045
- }
18046
- } else {
18047
- oldLength++;
18048
- oldValue[key] = newItem;
18049
- changeDetected++;
18050
- }
18051
- }
18052
- }
18053
- if (oldLength > newLength) {
18054
- // we used to have more keys, need to find them and destroy them.
18055
- changeDetected++;
18056
- for (key in oldValue) {
18057
- if (!hasOwnProperty.call(newValue, key)) {
18058
- oldLength--;
18059
- delete oldValue[key];
18060
- }
18061
- }
18062
- }
18063
- }
18064
- return changeDetected;
18065
- }
18066
-
18067
- function $watchCollectionAction() {
18068
- if (initRun) {
18069
- initRun = false;
18070
- listener(newValue, newValue, self);
18071
- } else {
18072
- listener(newValue, veryOldValue, self);
18073
- }
18074
-
18075
- // make a copy for the next time a collection is changed
18076
- if (trackVeryOldValue) {
18077
- if (!isObject(newValue)) {
18078
- //primitive
18079
- veryOldValue = newValue;
18080
- } else if (isArrayLike(newValue)) {
18081
- veryOldValue = new Array(newValue.length);
18082
- for (var i = 0; i < newValue.length; i++) {
18083
- veryOldValue[i] = newValue[i];
18084
- }
18085
- } else { // if object
18086
- veryOldValue = {};
18087
- for (var key in newValue) {
18088
- if (hasOwnProperty.call(newValue, key)) {
18089
- veryOldValue[key] = newValue[key];
18090
- }
18091
- }
18092
- }
18093
- }
18094
- }
18095
-
18096
- return this.$watch(changeDetector, $watchCollectionAction);
18097
- },
18098
-
18099
- /**
18100
- * @ngdoc method
18101
- * @name $rootScope.Scope#$digest
18102
- * @kind function
18103
- *
18104
- * @description
18105
- * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
18106
- * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
18107
- * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
18108
- * until no more listeners are firing. This means that it is possible to get into an infinite
18109
- * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
18110
- * iterations exceeds 10.
18111
- *
18112
- * Usually, you don't call `$digest()` directly in
18113
- * {@link ng.directive:ngController controllers} or in
18114
- * {@link ng.$compileProvider#directive directives}.
18115
- * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
18116
- * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
18117
- *
18118
- * If you want to be notified whenever `$digest()` is called,
18119
- * you can register a `watchExpression` function with
18120
- * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
18121
- *
18122
- * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
18123
- *
18124
- * # Example
18125
- * ```js
18126
- var scope = ...;
18127
- scope.name = 'misko';
18128
- scope.counter = 0;
18129
-
18130
- expect(scope.counter).toEqual(0);
18131
- scope.$watch('name', function(newValue, oldValue) {
18132
- scope.counter = scope.counter + 1;
18133
- });
18134
- expect(scope.counter).toEqual(0);
18135
-
18136
- scope.$digest();
18137
- // the listener is always called during the first $digest loop after it was registered
18138
- expect(scope.counter).toEqual(1);
18139
-
18140
- scope.$digest();
18141
- // but now it will not be called unless the value changes
18142
- expect(scope.counter).toEqual(1);
18143
-
18144
- scope.name = 'adam';
18145
- scope.$digest();
18146
- expect(scope.counter).toEqual(2);
18147
- * ```
18148
- *
18149
- */
18150
- $digest: function() {
18151
- var watch, value, last, fn, get,
18152
- watchers,
18153
- dirty, ttl = TTL,
18154
- next, current, target = this,
18155
- watchLog = [],
18156
- logIdx, asyncTask;
18157
-
18158
- beginPhase('$digest');
18159
- // Check for changes to browser url that happened in sync before the call to $digest
18160
- $browser.$$checkUrlChange();
18161
-
18162
- if (this === $rootScope && applyAsyncId !== null) {
18163
- // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
18164
- // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
18165
- $browser.defer.cancel(applyAsyncId);
18166
- flushApplyAsync();
18167
- }
18168
-
18169
- lastDirtyWatch = null;
18170
-
18171
- do { // "while dirty" loop
18172
- dirty = false;
18173
- current = target;
18174
-
18175
- // It's safe for asyncQueuePosition to be a local variable here because this loop can't
18176
- // be reentered recursively. Calling $digest from a function passed to $evalAsync would
18177
- // lead to a '$digest already in progress' error.
18178
- for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) {
18179
- try {
18180
- asyncTask = asyncQueue[asyncQueuePosition];
18181
- fn = asyncTask.fn;
18182
- fn(asyncTask.scope, asyncTask.locals);
18183
- } catch (e) {
18184
- $exceptionHandler(e);
18185
- }
18186
- lastDirtyWatch = null;
18187
- }
18188
- asyncQueue.length = 0;
18189
-
18190
- traverseScopesLoop:
18191
- do { // "traverse the scopes" loop
18192
- if ((watchers = current.$$watchers)) {
18193
- // process our watches
18194
- watchers.$$digestWatchIndex = watchers.length;
18195
- while (watchers.$$digestWatchIndex--) {
18196
- try {
18197
- watch = watchers[watchers.$$digestWatchIndex];
18198
- // Most common watches are on primitives, in which case we can short
18199
- // circuit it with === operator, only when === fails do we use .equals
18200
- if (watch) {
18201
- get = watch.get;
18202
- if ((value = get(current)) !== (last = watch.last) &&
18203
- !(watch.eq
18204
- ? equals(value, last)
18205
- : (isNumberNaN(value) && isNumberNaN(last)))) {
18206
- dirty = true;
18207
- lastDirtyWatch = watch;
18208
- watch.last = watch.eq ? copy(value, null) : value;
18209
- fn = watch.fn;
18210
- fn(value, ((last === initWatchVal) ? value : last), current);
18211
- if (ttl < 5) {
18212
- logIdx = 4 - ttl;
18213
- if (!watchLog[logIdx]) watchLog[logIdx] = [];
18214
- watchLog[logIdx].push({
18215
- msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
18216
- newVal: value,
18217
- oldVal: last
18218
- });
18219
- }
18220
- } else if (watch === lastDirtyWatch) {
18221
- // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
18222
- // have already been tested.
18223
- dirty = false;
18224
- break traverseScopesLoop;
18225
- }
18226
- }
18227
- } catch (e) {
18228
- $exceptionHandler(e);
18229
- }
18230
- }
18231
- }
18232
-
18233
- // Insanity Warning: scope depth-first traversal
18234
- // yes, this code is a bit crazy, but it works and we have tests to prove it!
18235
- // this piece should be kept in sync with the traversal in $broadcast
18236
- if (!(next = ((current.$$watchersCount && current.$$childHead) ||
18237
- (current !== target && current.$$nextSibling)))) {
18238
- while (current !== target && !(next = current.$$nextSibling)) {
18239
- current = current.$parent;
18240
- }
18241
- }
18242
- } while ((current = next));
18243
-
18244
- // `break traverseScopesLoop;` takes us to here
18245
-
18246
- if ((dirty || asyncQueue.length) && !(ttl--)) {
18247
- clearPhase();
18248
- throw $rootScopeMinErr('infdig',
18249
- '{0} $digest() iterations reached. Aborting!\n' +
18250
- 'Watchers fired in the last 5 iterations: {1}',
18251
- TTL, watchLog);
18252
- }
18253
-
18254
- } while (dirty || asyncQueue.length);
18255
-
18256
- clearPhase();
18257
-
18258
- // postDigestQueuePosition isn't local here because this loop can be reentered recursively.
18259
- while (postDigestQueuePosition < postDigestQueue.length) {
18260
- try {
18261
- postDigestQueue[postDigestQueuePosition++]();
18262
- } catch (e) {
18263
- $exceptionHandler(e);
18264
- }
18265
- }
18266
- postDigestQueue.length = postDigestQueuePosition = 0;
18267
-
18268
- // Check for changes to browser url that happened during the $digest
18269
- // (for which no event is fired; e.g. via `history.pushState()`)
18270
- $browser.$$checkUrlChange();
18271
- },
18272
-
18273
-
18274
- /**
18275
- * @ngdoc event
18276
- * @name $rootScope.Scope#$destroy
18277
- * @eventType broadcast on scope being destroyed
18278
- *
18279
- * @description
18280
- * Broadcasted when a scope and its children are being destroyed.
18281
- *
18282
- * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
18283
- * clean up DOM bindings before an element is removed from the DOM.
18284
- */
18285
-
18286
- /**
18287
- * @ngdoc method
18288
- * @name $rootScope.Scope#$destroy
18289
- * @kind function
18290
- *
18291
- * @description
18292
- * Removes the current scope (and all of its children) from the parent scope. Removal implies
18293
- * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
18294
- * propagate to the current scope and its children. Removal also implies that the current
18295
- * scope is eligible for garbage collection.
18296
- *
18297
- * The `$destroy()` is usually used by directives such as
18298
- * {@link ng.directive:ngRepeat ngRepeat} for managing the
18299
- * unrolling of the loop.
18300
- *
18301
- * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
18302
- * Application code can register a `$destroy` event handler that will give it a chance to
18303
- * perform any necessary cleanup.
18304
- *
18305
- * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
18306
- * clean up DOM bindings before an element is removed from the DOM.
18307
- */
18308
- $destroy: function() {
18309
- // We can't destroy a scope that has been already destroyed.
18310
- if (this.$$destroyed) return;
18311
- var parent = this.$parent;
18312
-
18313
- this.$broadcast('$destroy');
18314
- this.$$destroyed = true;
18315
-
18316
- if (this === $rootScope) {
18317
- //Remove handlers attached to window when $rootScope is removed
18318
- $browser.$$applicationDestroyed();
18319
- }
18320
-
18321
- incrementWatchersCount(this, -this.$$watchersCount);
18322
- for (var eventName in this.$$listenerCount) {
18323
- decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
18324
- }
18325
-
18326
- // sever all the references to parent scopes (after this cleanup, the current scope should
18327
- // not be retained by any of our references and should be eligible for garbage collection)
18328
- if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling;
18329
- if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling;
18330
- if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
18331
- if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
18332
-
18333
- // Disable listeners, watchers and apply/digest methods
18334
- this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
18335
- this.$on = this.$watch = this.$watchGroup = function() { return noop; };
18336
- this.$$listeners = {};
18337
-
18338
- // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
18339
- this.$$nextSibling = null;
18340
- cleanUpScope(this);
18341
- },
18342
-
18343
- /**
18344
- * @ngdoc method
18345
- * @name $rootScope.Scope#$eval
18346
- * @kind function
18347
- *
18348
- * @description
18349
- * Executes the `expression` on the current scope and returns the result. Any exceptions in
18350
- * the expression are propagated (uncaught). This is useful when evaluating Angular
18351
- * expressions.
18352
- *
18353
- * # Example
18354
- * ```js
18355
- var scope = ng.$rootScope.Scope();
18356
- scope.a = 1;
18357
- scope.b = 2;
18358
-
18359
- expect(scope.$eval('a+b')).toEqual(3);
18360
- expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
18361
- * ```
18362
- *
18363
- * @param {(string|function())=} expression An angular expression to be executed.
18364
- *
18365
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18366
- * - `function(scope)`: execute the function with the current `scope` parameter.
18367
- *
18368
- * @param {(object)=} locals Local variables object, useful for overriding values in scope.
18369
- * @returns {*} The result of evaluating the expression.
18370
- */
18371
- $eval: function(expr, locals) {
18372
- return $parse(expr)(this, locals);
18373
- },
18374
-
18375
- /**
18376
- * @ngdoc method
18377
- * @name $rootScope.Scope#$evalAsync
18378
- * @kind function
18379
- *
18380
- * @description
18381
- * Executes the expression on the current scope at a later point in time.
18382
- *
18383
- * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
18384
- * that:
18385
- *
18386
- * - it will execute after the function that scheduled the evaluation (preferably before DOM
18387
- * rendering).
18388
- * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
18389
- * `expression` execution.
18390
- *
18391
- * Any exceptions from the execution of the expression are forwarded to the
18392
- * {@link ng.$exceptionHandler $exceptionHandler} service.
18393
- *
18394
- * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
18395
- * will be scheduled. However, it is encouraged to always call code that changes the model
18396
- * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
18397
- *
18398
- * @param {(string|function())=} expression An angular expression to be executed.
18399
- *
18400
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18401
- * - `function(scope)`: execute the function with the current `scope` parameter.
18402
- *
18403
- * @param {(object)=} locals Local variables object, useful for overriding values in scope.
18404
- */
18405
- $evalAsync: function(expr, locals) {
18406
- // if we are outside of an $digest loop and this is the first time we are scheduling async
18407
- // task also schedule async auto-flush
18408
- if (!$rootScope.$$phase && !asyncQueue.length) {
18409
- $browser.defer(function() {
18410
- if (asyncQueue.length) {
18411
- $rootScope.$digest();
18412
- }
18413
- });
18414
- }
18415
-
18416
- asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
18417
- },
18418
-
18419
- $$postDigest: function(fn) {
18420
- postDigestQueue.push(fn);
18421
- },
18422
-
18423
- /**
18424
- * @ngdoc method
18425
- * @name $rootScope.Scope#$apply
18426
- * @kind function
18427
- *
18428
- * @description
18429
- * `$apply()` is used to execute an expression in angular from outside of the angular
18430
- * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
18431
- * Because we are calling into the angular framework we need to perform proper scope life
18432
- * cycle of {@link ng.$exceptionHandler exception handling},
18433
- * {@link ng.$rootScope.Scope#$digest executing watches}.
18434
- *
18435
- * ## Life cycle
18436
- *
18437
- * # Pseudo-Code of `$apply()`
18438
- * ```js
18439
- function $apply(expr) {
18440
- try {
18441
- return $eval(expr);
18442
- } catch (e) {
18443
- $exceptionHandler(e);
18444
- } finally {
18445
- $root.$digest();
18446
- }
18447
- }
18448
- * ```
18449
- *
18450
- *
18451
- * Scope's `$apply()` method transitions through the following stages:
18452
- *
18453
- * 1. The {@link guide/expression expression} is executed using the
18454
- * {@link ng.$rootScope.Scope#$eval $eval()} method.
18455
- * 2. Any exceptions from the execution of the expression are forwarded to the
18456
- * {@link ng.$exceptionHandler $exceptionHandler} service.
18457
- * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
18458
- * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
18459
- *
18460
- *
18461
- * @param {(string|function())=} exp An angular expression to be executed.
18462
- *
18463
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18464
- * - `function(scope)`: execute the function with current `scope` parameter.
18465
- *
18466
- * @returns {*} The result of evaluating the expression.
18467
- */
18468
- $apply: function(expr) {
18469
- try {
18470
- beginPhase('$apply');
18471
- try {
18472
- return this.$eval(expr);
18473
- } finally {
18474
- clearPhase();
18475
- }
18476
- } catch (e) {
18477
- $exceptionHandler(e);
18478
- } finally {
18479
- try {
18480
- $rootScope.$digest();
18481
- } catch (e) {
18482
- $exceptionHandler(e);
18483
- // eslint-disable-next-line no-unsafe-finally
18484
- throw e;
18485
- }
18486
- }
18487
- },
18488
-
18489
- /**
18490
- * @ngdoc method
18491
- * @name $rootScope.Scope#$applyAsync
18492
- * @kind function
18493
- *
18494
- * @description
18495
- * Schedule the invocation of $apply to occur at a later time. The actual time difference
18496
- * varies across browsers, but is typically around ~10 milliseconds.
18497
- *
18498
- * This can be used to queue up multiple expressions which need to be evaluated in the same
18499
- * digest.
18500
- *
18501
- * @param {(string|function())=} exp An angular expression to be executed.
18502
- *
18503
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
18504
- * - `function(scope)`: execute the function with current `scope` parameter.
18505
- */
18506
- $applyAsync: function(expr) {
18507
- var scope = this;
18508
- if (expr) {
18509
- applyAsyncQueue.push($applyAsyncExpression);
18510
- }
18511
- expr = $parse(expr);
18512
- scheduleApplyAsync();
18513
-
18514
- function $applyAsyncExpression() {
18515
- scope.$eval(expr);
18516
- }
18517
- },
18518
-
18519
- /**
18520
- * @ngdoc method
18521
- * @name $rootScope.Scope#$on
18522
- * @kind function
18523
- *
18524
- * @description
18525
- * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
18526
- * discussion of event life cycle.
18527
- *
18528
- * The event listener function format is: `function(event, args...)`. The `event` object
18529
- * passed into the listener has the following attributes:
18530
- *
18531
- * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
18532
- * `$broadcast`-ed.
18533
- * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
18534
- * event propagates through the scope hierarchy, this property is set to null.
18535
- * - `name` - `{string}`: name of the event.
18536
- * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
18537
- * further event propagation (available only for events that were `$emit`-ed).
18538
- * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
18539
- * to true.
18540
- * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
18541
- *
18542
- * @param {string} name Event name to listen on.
18543
- * @param {function(event, ...args)} listener Function to call when the event is emitted.
18544
- * @returns {function()} Returns a deregistration function for this listener.
18545
- */
18546
- $on: function(name, listener) {
18547
- var namedListeners = this.$$listeners[name];
18548
- if (!namedListeners) {
18549
- this.$$listeners[name] = namedListeners = [];
18550
- }
18551
- namedListeners.push(listener);
18552
-
18553
- var current = this;
18554
- do {
18555
- if (!current.$$listenerCount[name]) {
18556
- current.$$listenerCount[name] = 0;
18557
- }
18558
- current.$$listenerCount[name]++;
18559
- } while ((current = current.$parent));
18560
-
18561
- var self = this;
18562
- return function() {
18563
- var indexOfListener = namedListeners.indexOf(listener);
18564
- if (indexOfListener !== -1) {
18565
- namedListeners[indexOfListener] = null;
18566
- decrementListenerCount(self, 1, name);
18567
- }
18568
- };
18569
- },
18570
-
18571
-
18572
- /**
18573
- * @ngdoc method
18574
- * @name $rootScope.Scope#$emit
18575
- * @kind function
18576
- *
18577
- * @description
18578
- * Dispatches an event `name` upwards through the scope hierarchy notifying the
18579
- * registered {@link ng.$rootScope.Scope#$on} listeners.
18580
- *
18581
- * The event life cycle starts at the scope on which `$emit` was called. All
18582
- * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
18583
- * notified. Afterwards, the event traverses upwards toward the root scope and calls all
18584
- * registered listeners along the way. The event will stop propagating if one of the listeners
18585
- * cancels it.
18586
- *
18587
- * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
18588
- * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
18589
- *
18590
- * @param {string} name Event name to emit.
18591
- * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
18592
- * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
18593
- */
18594
- $emit: function(name, args) {
18595
- var empty = [],
18596
- namedListeners,
18597
- scope = this,
18598
- stopPropagation = false,
18599
- event = {
18600
- name: name,
18601
- targetScope: scope,
18602
- stopPropagation: function() {stopPropagation = true;},
18603
- preventDefault: function() {
18604
- event.defaultPrevented = true;
18605
- },
18606
- defaultPrevented: false
18607
- },
18608
- listenerArgs = concat([event], arguments, 1),
18609
- i, length;
18610
-
18611
- do {
18612
- namedListeners = scope.$$listeners[name] || empty;
18613
- event.currentScope = scope;
18614
- for (i = 0, length = namedListeners.length; i < length; i++) {
18615
-
18616
- // if listeners were deregistered, defragment the array
18617
- if (!namedListeners[i]) {
18618
- namedListeners.splice(i, 1);
18619
- i--;
18620
- length--;
18621
- continue;
18622
- }
18623
- try {
18624
- //allow all listeners attached to the current scope to run
18625
- namedListeners[i].apply(null, listenerArgs);
18626
- } catch (e) {
18627
- $exceptionHandler(e);
18628
- }
18629
- }
18630
- //if any listener on the current scope stops propagation, prevent bubbling
18631
- if (stopPropagation) {
18632
- event.currentScope = null;
18633
- return event;
18634
- }
18635
- //traverse upwards
18636
- scope = scope.$parent;
18637
- } while (scope);
18638
-
18639
- event.currentScope = null;
18640
-
18641
- return event;
18642
- },
18643
-
18644
-
18645
- /**
18646
- * @ngdoc method
18647
- * @name $rootScope.Scope#$broadcast
18648
- * @kind function
18649
- *
18650
- * @description
18651
- * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
18652
- * registered {@link ng.$rootScope.Scope#$on} listeners.
18653
- *
18654
- * The event life cycle starts at the scope on which `$broadcast` was called. All
18655
- * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
18656
- * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
18657
- * scope and calls all registered listeners along the way. The event cannot be canceled.
18658
- *
18659
- * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
18660
- * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
18661
- *
18662
- * @param {string} name Event name to broadcast.
18663
- * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
18664
- * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
18665
- */
18666
- $broadcast: function(name, args) {
18667
- var target = this,
18668
- current = target,
18669
- next = target,
18670
- event = {
18671
- name: name,
18672
- targetScope: target,
18673
- preventDefault: function() {
18674
- event.defaultPrevented = true;
18675
- },
18676
- defaultPrevented: false
18677
- };
18678
-
18679
- if (!target.$$listenerCount[name]) return event;
18680
-
18681
- var listenerArgs = concat([event], arguments, 1),
18682
- listeners, i, length;
18683
-
18684
- //down while you can, then up and next sibling or up and next sibling until back at root
18685
- while ((current = next)) {
18686
- event.currentScope = current;
18687
- listeners = current.$$listeners[name] || [];
18688
- for (i = 0, length = listeners.length; i < length; i++) {
18689
- // if listeners were deregistered, defragment the array
18690
- if (!listeners[i]) {
18691
- listeners.splice(i, 1);
18692
- i--;
18693
- length--;
18694
- continue;
18695
- }
18696
-
18697
- try {
18698
- listeners[i].apply(null, listenerArgs);
18699
- } catch (e) {
18700
- $exceptionHandler(e);
18701
- }
18702
- }
18703
-
18704
- // Insanity Warning: scope depth-first traversal
18705
- // yes, this code is a bit crazy, but it works and we have tests to prove it!
18706
- // this piece should be kept in sync with the traversal in $digest
18707
- // (though it differs due to having the extra check for $$listenerCount)
18708
- if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
18709
- (current !== target && current.$$nextSibling)))) {
18710
- while (current !== target && !(next = current.$$nextSibling)) {
18711
- current = current.$parent;
18712
- }
18713
- }
18714
- }
18715
-
18716
- event.currentScope = null;
18717
- return event;
18718
- }
18719
- };
18720
-
18721
- var $rootScope = new Scope();
18722
-
18723
- //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
18724
- var asyncQueue = $rootScope.$$asyncQueue = [];
18725
- var postDigestQueue = $rootScope.$$postDigestQueue = [];
18726
- var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
18727
-
18728
- var postDigestQueuePosition = 0;
18729
-
18730
- return $rootScope;
18731
-
18732
-
18733
- function beginPhase(phase) {
18734
- if ($rootScope.$$phase) {
18735
- throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
18736
- }
18737
-
18738
- $rootScope.$$phase = phase;
18739
- }
18740
-
18741
- function clearPhase() {
18742
- $rootScope.$$phase = null;
18743
- }
18744
-
18745
- function incrementWatchersCount(current, count) {
18746
- do {
18747
- current.$$watchersCount += count;
18748
- } while ((current = current.$parent));
18749
- }
18750
-
18751
- function decrementListenerCount(current, count, name) {
18752
- do {
18753
- current.$$listenerCount[name] -= count;
18754
-
18755
- if (current.$$listenerCount[name] === 0) {
18756
- delete current.$$listenerCount[name];
18757
- }
18758
- } while ((current = current.$parent));
18759
- }
18760
-
18761
- /**
18762
- * function used as an initial value for watchers.
18763
- * because it's unique we can easily tell it apart from other values
18764
- */
18765
- function initWatchVal() {}
18766
-
18767
- function flushApplyAsync() {
18768
- while (applyAsyncQueue.length) {
18769
- try {
18770
- applyAsyncQueue.shift()();
18771
- } catch (e) {
18772
- $exceptionHandler(e);
18773
- }
18774
- }
18775
- applyAsyncId = null;
18776
- }
18777
-
18778
- function scheduleApplyAsync() {
18779
- if (applyAsyncId === null) {
18780
- applyAsyncId = $browser.defer(function() {
18781
- $rootScope.$apply(flushApplyAsync);
18782
- });
18783
- }
18784
- }
18785
- }];
18786
- }
18787
-
18788
- /**
18789
- * @ngdoc service
18790
- * @name $rootElement
18791
- *
18792
- * @description
18793
- * The root element of Angular application. This is either the element where {@link
18794
- * ng.directive:ngApp ngApp} was declared or the element passed into
18795
- * {@link angular.bootstrap}. The element represents the root element of application. It is also the
18796
- * location where the application's {@link auto.$injector $injector} service gets
18797
- * published, and can be retrieved using `$rootElement.injector()`.
18798
- */
18799
-
18800
-
18801
- // the implementation is in angular.bootstrap
18802
-
18803
- /**
18804
- * @this
18805
- * @description
18806
- * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
18807
- */
18808
- function $$SanitizeUriProvider() {
18809
- var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
18810
- imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
18811
-
18812
- /**
18813
- * @description
18814
- * Retrieves or overrides the default regular expression that is used for whitelisting of safe
18815
- * urls during a[href] sanitization.
18816
- *
18817
- * The sanitization is a security measure aimed at prevent XSS attacks via html links.
18818
- *
18819
- * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
18820
- * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
18821
- * regular expression. If a match is found, the original url is written into the dom. Otherwise,
18822
- * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
18823
- *
18824
- * @param {RegExp=} regexp New regexp to whitelist urls with.
18825
- * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
18826
- * chaining otherwise.
18827
- */
18828
- this.aHrefSanitizationWhitelist = function(regexp) {
18829
- if (isDefined(regexp)) {
18830
- aHrefSanitizationWhitelist = regexp;
18831
- return this;
18832
- }
18833
- return aHrefSanitizationWhitelist;
18834
- };
18835
-
18836
-
18837
- /**
18838
- * @description
18839
- * Retrieves or overrides the default regular expression that is used for whitelisting of safe
18840
- * urls during img[src] sanitization.
18841
- *
18842
- * The sanitization is a security measure aimed at prevent XSS attacks via html links.
18843
- *
18844
- * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
18845
- * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
18846
- * regular expression. If a match is found, the original url is written into the dom. Otherwise,
18847
- * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
18848
- *
18849
- * @param {RegExp=} regexp New regexp to whitelist urls with.
18850
- * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
18851
- * chaining otherwise.
18852
- */
18853
- this.imgSrcSanitizationWhitelist = function(regexp) {
18854
- if (isDefined(regexp)) {
18855
- imgSrcSanitizationWhitelist = regexp;
18856
- return this;
18857
- }
18858
- return imgSrcSanitizationWhitelist;
18859
- };
18860
-
18861
- this.$get = function() {
18862
- return function sanitizeUri(uri, isImage) {
18863
- var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
18864
- var normalizedVal;
18865
- normalizedVal = urlResolve(uri).href;
18866
- if (normalizedVal !== '' && !normalizedVal.match(regex)) {
18867
- return 'unsafe:' + normalizedVal;
18868
- }
18869
- return uri;
18870
- };
18871
- };
18872
- }
18873
-
18874
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
18875
- * Any commits to this file should be reviewed with security in mind. *
18876
- * Changes to this file can potentially create security vulnerabilities. *
18877
- * An approval from 2 Core members with history of modifying *
18878
- * this file is required. *
18879
- * *
18880
- * Does the change somehow allow for arbitrary javascript to be executed? *
18881
- * Or allows for someone to change the prototype of built-in objects? *
18882
- * Or gives undesired access to variables likes document or window? *
18883
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
18884
-
18885
- /* exported $SceProvider, $SceDelegateProvider */
18886
-
18887
- var $sceMinErr = minErr('$sce');
18888
-
18889
- var SCE_CONTEXTS = {
18890
- // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding).
18891
- HTML: 'html',
18892
-
18893
- // Style statements or stylesheets. Currently unused in AngularJS.
18894
- CSS: 'css',
18895
-
18896
- // An URL used in a context where it does not refer to a resource that loads code. Currently
18897
- // unused in AngularJS.
18898
- URL: 'url',
18899
-
18900
- // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
18901
- // code. (e.g. ng-include, script src binding, templateUrl)
18902
- RESOURCE_URL: 'resourceUrl',
18903
-
18904
- // Script. Currently unused in AngularJS.
18905
- JS: 'js'
18906
- };
18907
-
18908
- // Helper functions follow.
18909
-
18910
- var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
18911
-
18912
- function snakeToCamel(name) {
18913
- return name
18914
- .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
18915
- }
18916
-
18917
- function adjustMatcher(matcher) {
18918
- if (matcher === 'self') {
18919
- return matcher;
18920
- } else if (isString(matcher)) {
18921
- // Strings match exactly except for 2 wildcards - '*' and '**'.
18922
- // '*' matches any character except those from the set ':/.?&'.
18923
- // '**' matches any character (like .* in a RegExp).
18924
- // More than 2 *'s raises an error as it's ill defined.
18925
- if (matcher.indexOf('***') > -1) {
18926
- throw $sceMinErr('iwcard',
18927
- 'Illegal sequence *** in string matcher. String: {0}', matcher);
18928
- }
18929
- matcher = escapeForRegexp(matcher).
18930
- replace(/\\\*\\\*/g, '.*').
18931
- replace(/\\\*/g, '[^:/.?&;]*');
18932
- return new RegExp('^' + matcher + '$');
18933
- } else if (isRegExp(matcher)) {
18934
- // The only other type of matcher allowed is a Regexp.
18935
- // Match entire URL / disallow partial matches.
18936
- // Flags are reset (i.e. no global, ignoreCase or multiline)
18937
- return new RegExp('^' + matcher.source + '$');
18938
- } else {
18939
- throw $sceMinErr('imatcher',
18940
- 'Matchers may only be "self", string patterns or RegExp objects');
18941
- }
18942
- }
18943
-
18944
-
18945
- function adjustMatchers(matchers) {
18946
- var adjustedMatchers = [];
18947
- if (isDefined(matchers)) {
18948
- forEach(matchers, function(matcher) {
18949
- adjustedMatchers.push(adjustMatcher(matcher));
18950
- });
18951
- }
18952
- return adjustedMatchers;
18953
- }
18954
-
18955
-
18956
- /**
18957
- * @ngdoc service
18958
- * @name $sceDelegate
18959
- * @kind function
18960
- *
18961
- * @description
18962
- *
18963
- * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
18964
- * Contextual Escaping (SCE)} services to AngularJS.
18965
- *
18966
- * For an overview of this service and the functionnality it provides in AngularJS, see the main
18967
- * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how
18968
- * SCE works in their application, which shouldn't be needed in most cases.
18969
- *
18970
- * <div class="alert alert-danger">
18971
- * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or
18972
- * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners,
18973
- * changes to this service will also influence users, so be extra careful and document your changes.
18974
- * </div>
18975
- *
18976
- * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
18977
- * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
18978
- * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
18979
- * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
18980
- * work because `$sce` delegates to `$sceDelegate` for these operations.
18981
- *
18982
- * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
18983
- *
18984
- * The default instance of `$sceDelegate` should work out of the box with little pain. While you
18985
- * can override it completely to change the behavior of `$sce`, the common case would
18986
- * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
18987
- * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
18988
- * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
18989
- * $sceDelegateProvider.resourceUrlWhitelist} and {@link
18990
- * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
18991
- */
18992
-
18993
- /**
18994
- * @ngdoc provider
18995
- * @name $sceDelegateProvider
18996
- * @this
18997
- *
18998
- * @description
18999
- *
19000
- * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
19001
- * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}.
19002
- *
19003
- * The `$sceDelegateProvider` allows one to get/set the whitelists and blacklists used to ensure
19004
- * that the URLs used for sourcing AngularJS templates and other script-running URLs are safe (all
19005
- * places that use the `$sce.RESOURCE_URL` context). See
19006
- * {@link ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist}
19007
- * and
19008
- * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist},
19009
- *
19010
- * For the general details about this service in Angular, read the main page for {@link ng.$sce
19011
- * Strict Contextual Escaping (SCE)}.
19012
- *
19013
- * **Example**: Consider the following case. <a name="example"></a>
19014
- *
19015
- * - your app is hosted at url `http://myapp.example.com/`
19016
- * - but some of your templates are hosted on other domains you control such as
19017
- * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
19018
- * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
19019
- *
19020
- * Here is what a secure configuration for this scenario might look like:
19021
- *
19022
- * ```
19023
- * angular.module('myApp', []).config(function($sceDelegateProvider) {
19024
- * $sceDelegateProvider.resourceUrlWhitelist([
19025
- * // Allow same origin resource loads.
19026
- * 'self',
19027
- * // Allow loading from our assets domain. Notice the difference between * and **.
19028
- * 'http://srv*.assets.example.com/**'
19029
- * ]);
19030
- *
19031
- * // The blacklist overrides the whitelist so the open redirect here is blocked.
19032
- * $sceDelegateProvider.resourceUrlBlacklist([
19033
- * 'http://myapp.example.com/clickThru**'
19034
- * ]);
19035
- * });
19036
- * ```
19037
- * Note that an empty whitelist will block every resource URL from being loaded, and will require
19038
- * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates
19039
- * requested by {@link ng.$templateRequest $templateRequest} that are present in
19040
- * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism
19041
- * to populate your templates in that cache at config time, then it is a good idea to remove 'self'
19042
- * from that whitelist. This helps to mitigate the security impact of certain types of issues, like
19043
- * for instance attacker-controlled `ng-includes`.
19044
- */
19045
-
19046
- function $SceDelegateProvider() {
19047
- this.SCE_CONTEXTS = SCE_CONTEXTS;
19048
-
19049
- // Resource URLs can also be trusted by policy.
19050
- var resourceUrlWhitelist = ['self'],
19051
- resourceUrlBlacklist = [];
19052
-
19053
- /**
19054
- * @ngdoc method
19055
- * @name $sceDelegateProvider#resourceUrlWhitelist
19056
- * @kind function
19057
- *
19058
- * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
19059
- * provided. This must be an array or null. A snapshot of this array is used so further
19060
- * changes to the array are ignored.
19061
- * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
19062
- * allowed in this array.
19063
- *
19064
- * @return {Array} The currently set whitelist array.
19065
- *
19066
- * @description
19067
- * Sets/Gets the whitelist of trusted resource URLs.
19068
- *
19069
- * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
19070
- * same origin resource requests.
19071
- *
19072
- * <div class="alert alert-warning">
19073
- * **Note:** the default whitelist of 'self' is not recommended if your app shares its origin
19074
- * with other apps! It is a good idea to limit it to only your application's directory.
19075
- * </div>
19076
- */
19077
- this.resourceUrlWhitelist = function(value) {
19078
- if (arguments.length) {
19079
- resourceUrlWhitelist = adjustMatchers(value);
19080
- }
19081
- return resourceUrlWhitelist;
19082
- };
19083
-
19084
- /**
19085
- * @ngdoc method
19086
- * @name $sceDelegateProvider#resourceUrlBlacklist
19087
- * @kind function
19088
- *
19089
- * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
19090
- * provided. This must be an array or null. A snapshot of this array is used so further
19091
- * changes to the array are ignored.</p><p>
19092
- * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
19093
- * allowed in this array.</p><p>
19094
- * The typical usage for the blacklist is to **block
19095
- * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
19096
- * these would otherwise be trusted but actually return content from the redirected domain.
19097
- * </p><p>
19098
- * Finally, **the blacklist overrides the whitelist** and has the final say.
19099
- *
19100
- * @return {Array} The currently set blacklist array.
19101
- *
19102
- * @description
19103
- * Sets/Gets the blacklist of trusted resource URLs.
19104
- *
19105
- * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
19106
- * is no blacklist.)
19107
- */
19108
-
19109
- this.resourceUrlBlacklist = function(value) {
19110
- if (arguments.length) {
19111
- resourceUrlBlacklist = adjustMatchers(value);
19112
- }
19113
- return resourceUrlBlacklist;
19114
- };
19115
-
19116
- this.$get = ['$injector', function($injector) {
19117
-
19118
- var htmlSanitizer = function htmlSanitizer(html) {
19119
- throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
19120
- };
19121
-
19122
- if ($injector.has('$sanitize')) {
19123
- htmlSanitizer = $injector.get('$sanitize');
19124
- }
19125
-
19126
-
19127
- function matchUrl(matcher, parsedUrl) {
19128
- if (matcher === 'self') {
19129
- return urlIsSameOrigin(parsedUrl);
19130
- } else {
19131
- // definitely a regex. See adjustMatchers()
19132
- return !!matcher.exec(parsedUrl.href);
19133
- }
19134
- }
19135
-
19136
- function isResourceUrlAllowedByPolicy(url) {
19137
- var parsedUrl = urlResolve(url.toString());
19138
- var i, n, allowed = false;
19139
- // Ensure that at least one item from the whitelist allows this url.
19140
- for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
19141
- if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
19142
- allowed = true;
19143
- break;
19144
- }
19145
- }
19146
- if (allowed) {
19147
- // Ensure that no item from the blacklist blocked this url.
19148
- for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
19149
- if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
19150
- allowed = false;
19151
- break;
19152
- }
19153
- }
19154
- }
19155
- return allowed;
19156
- }
19157
-
19158
- function generateHolderType(Base) {
19159
- var holderType = function TrustedValueHolderType(trustedValue) {
19160
- this.$$unwrapTrustedValue = function() {
19161
- return trustedValue;
19162
- };
19163
- };
19164
- if (Base) {
19165
- holderType.prototype = new Base();
19166
- }
19167
- holderType.prototype.valueOf = function sceValueOf() {
19168
- return this.$$unwrapTrustedValue();
19169
- };
19170
- holderType.prototype.toString = function sceToString() {
19171
- return this.$$unwrapTrustedValue().toString();
19172
- };
19173
- return holderType;
19174
- }
19175
-
19176
- var trustedValueHolderBase = generateHolderType(),
19177
- byType = {};
19178
-
19179
- byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
19180
- byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
19181
- byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
19182
- byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
19183
- byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
19184
-
19185
- /**
19186
- * @ngdoc method
19187
- * @name $sceDelegate#trustAs
19188
- *
19189
- * @description
19190
- * Returns a trusted representation of the parameter for the specified context. This trusted
19191
- * object will later on be used as-is, without any security check, by bindings or directives
19192
- * that require this security context.
19193
- * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass
19194
- * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as
19195
- * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the
19196
- * sanitizer loaded, passing the value itself will render all the HTML that does not pose a
19197
- * security risk.
19198
- *
19199
- * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those
19200
- * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual
19201
- * escaping.
19202
- *
19203
- * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
19204
- * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
19205
- *
19206
- * @param {*} value The value that should be considered trusted.
19207
- * @return {*} A trusted representation of value, that can be used in the given context.
19208
- */
19209
- function trustAs(type, trustedValue) {
19210
- var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
19211
- if (!Constructor) {
19212
- throw $sceMinErr('icontext',
19213
- 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
19214
- type, trustedValue);
19215
- }
19216
- if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
19217
- return trustedValue;
19218
- }
19219
- // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
19220
- // mutable objects, we ensure here that the value passed in is actually a string.
19221
- if (typeof trustedValue !== 'string') {
19222
- throw $sceMinErr('itype',
19223
- 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
19224
- type);
19225
- }
19226
- return new Constructor(trustedValue);
19227
- }
19228
-
19229
- /**
19230
- * @ngdoc method
19231
- * @name $sceDelegate#valueOf
19232
- *
19233
- * @description
19234
- * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
19235
- * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
19236
- * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
19237
- *
19238
- * If the passed parameter is not a value that had been returned by {@link
19239
- * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is.
19240
- *
19241
- * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
19242
- * call or anything else.
19243
- * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
19244
- * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
19245
- * `value` unchanged.
19246
- */
19247
- function valueOf(maybeTrusted) {
19248
- if (maybeTrusted instanceof trustedValueHolderBase) {
19249
- return maybeTrusted.$$unwrapTrustedValue();
19250
- } else {
19251
- return maybeTrusted;
19252
- }
19253
- }
19254
-
19255
- /**
19256
- * @ngdoc method
19257
- * @name $sceDelegate#getTrusted
19258
- *
19259
- * @description
19260
- * Takes any input, and either returns a value that's safe to use in the specified context, or
19261
- * throws an exception.
19262
- *
19263
- * In practice, there are several cases. When given a string, this function runs checks
19264
- * and sanitization to make it safe without prior assumptions. When given the result of a {@link
19265
- * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call, it returns the originally supplied
19266
- * value if that value's context is valid for this call's context. Finally, this function can
19267
- * also throw when there is no way to turn `maybeTrusted` in a safe value (e.g., no sanitization
19268
- * is available or possible.)
19269
- *
19270
- * @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
19271
- * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
19272
- * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.)
19273
- * @return {*} A version of the value that's safe to use in the given context, or throws an
19274
- * exception if this is impossible.
19275
- */
19276
- function getTrusted(type, maybeTrusted) {
19277
- if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
19278
- return maybeTrusted;
19279
- }
19280
- var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
19281
- // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return
19282
- // as-is.
19283
- if (constructor && maybeTrusted instanceof constructor) {
19284
- return maybeTrusted.$$unwrapTrustedValue();
19285
- }
19286
- // Otherwise, if we get here, then we may either make it safe, or throw an exception. This
19287
- // depends on the context: some are sanitizatible (HTML), some use whitelists (RESOURCE_URL),
19288
- // some are impossible to do (JS). This step isn't implemented for CSS and URL, as AngularJS
19289
- // has no corresponding sinks.
19290
- if (type === SCE_CONTEXTS.RESOURCE_URL) {
19291
- // RESOURCE_URL uses a whitelist.
19292
- if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
19293
- return maybeTrusted;
19294
- } else {
19295
- throw $sceMinErr('insecurl',
19296
- 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
19297
- maybeTrusted.toString());
19298
- }
19299
- } else if (type === SCE_CONTEXTS.HTML) {
19300
- // htmlSanitizer throws its own error when no sanitizer is available.
19301
- return htmlSanitizer(maybeTrusted);
19302
- }
19303
- // Default error when the $sce service has no way to make the input safe.
19304
- throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
19305
- }
19306
-
19307
- return { trustAs: trustAs,
19308
- getTrusted: getTrusted,
19309
- valueOf: valueOf };
19310
- }];
19311
- }
19312
-
19313
-
19314
- /**
19315
- * @ngdoc provider
19316
- * @name $sceProvider
19317
- * @this
19318
- *
19319
- * @description
19320
- *
19321
- * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
19322
- * - enable/disable Strict Contextual Escaping (SCE) in a module
19323
- * - override the default implementation with a custom delegate
19324
- *
19325
- * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
19326
- */
19327
-
19328
- /**
19329
- * @ngdoc service
19330
- * @name $sce
19331
- * @kind function
19332
- *
19333
- * @description
19334
- *
19335
- * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
19336
- *
19337
- * # Strict Contextual Escaping
19338
- *
19339
- * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render
19340
- * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and
19341
- * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
19342
- *
19343
- * ## Overview
19344
- *
19345
- * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in
19346
- * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically
19347
- * run security checks on them (sanitizations, whitelists, depending on context), or throw when it
19348
- * cannot guarantee the security of the result. That behavior depends strongly on contexts: HTML
19349
- * can be sanitized, but template URLs cannot, for instance.
19350
- *
19351
- * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML:
19352
- * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it
19353
- * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and
19354
- * render the input as-is, you will need to mark it as trusted for that context before attempting
19355
- * to bind it.
19356
- *
19357
- * As of version 1.2, AngularJS ships with SCE enabled by default.
19358
- *
19359
- * ## In practice
19360
- *
19361
- * Here's an example of a binding in a privileged context:
19362
- *
19363
- * ```
19364
- * <input ng-model="userHtml" aria-label="User input">
19365
- * <div ng-bind-html="userHtml"></div>
19366
- * ```
19367
- *
19368
- * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
19369
- * disabled, this application allows the user to render arbitrary HTML into the DIV, which would
19370
- * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog
19371
- * articles, etc. via bindings. (HTML is just one example of a context where rendering user
19372
- * controlled input creates security vulnerabilities.)
19373
- *
19374
- * For the case of HTML, you might use a library, either on the client side, or on the server side,
19375
- * to sanitize unsafe HTML before binding to the value and rendering it in the document.
19376
- *
19377
- * How would you ensure that every place that used these types of bindings was bound to a value that
19378
- * was sanitized by your library (or returned as safe for rendering by your server?) How can you
19379
- * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
19380
- * properties/fields and forgot to update the binding to the sanitized value?
19381
- *
19382
- * To be secure by default, AngularJS makes sure bindings go through that sanitization, or
19383
- * any similar validation process, unless there's a good reason to trust the given value in this
19384
- * context. That trust is formalized with a function call. This means that as a developer, you
19385
- * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues,
19386
- * you just need to ensure the values you mark as trusted indeed are safe - because they were
19387
- * received from your server, sanitized by your library, etc. You can organize your codebase to
19388
- * help with this - perhaps allowing only the files in a specific directory to do this.
19389
- * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then
19390
- * becomes a more manageable task.
19391
- *
19392
- * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
19393
- * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
19394
- * build the trusted versions of your values.
19395
- *
19396
- * ## How does it work?
19397
- *
19398
- * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
19399
- * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as
19400
- * a way to enforce the required security context in your data sink. Directives use {@link
19401
- * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs
19402
- * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also,
19403
- * when binding without directives, AngularJS will understand the context of your bindings
19404
- * automatically.
19405
- *
19406
- * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
19407
- * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
19408
- * simplified):
19409
- *
19410
- * ```
19411
- * var ngBindHtmlDirective = ['$sce', function($sce) {
19412
- * return function(scope, element, attr) {
19413
- * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
19414
- * element.html(value || '');
19415
- * });
19416
- * };
19417
- * }];
19418
- * ```
19419
- *
19420
- * ## Impact on loading templates
19421
- *
19422
- * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
19423
- * `templateUrl`'s specified by {@link guide/directive directives}.
19424
- *
19425
- * By default, Angular only loads templates from the same domain and protocol as the application
19426
- * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
19427
- * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
19428
- * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
19429
- * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
19430
- *
19431
- * *Please note*:
19432
- * The browser's
19433
- * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
19434
- * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
19435
- * policy apply in addition to this and may further restrict whether the template is successfully
19436
- * loaded. This means that without the right CORS policy, loading templates from a different domain
19437
- * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
19438
- * browsers.
19439
- *
19440
- * ## This feels like too much overhead
19441
- *
19442
- * It's important to remember that SCE only applies to interpolation expressions.
19443
- *
19444
- * If your expressions are constant literals, they're automatically trusted and you don't need to
19445
- * call `$sce.trustAs` on them (e.g.
19446
- * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. The `$sceDelegate` will
19447
- * also use the `$sanitize` service if it is available when binding untrusted values to
19448
- * `$sce.HTML` context. AngularJS provides an implementation in `angular-sanitize.js`, and if you
19449
- * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
19450
- * your application.
19451
- *
19452
- * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
19453
- * templates in `ng-include` from your application's domain without having to even know about SCE.
19454
- * It blocks loading templates from other domains or loading templates over http from an https
19455
- * served document. You can change these by setting your own custom {@link
19456
- * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
19457
- * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
19458
- *
19459
- * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
19460
- * application that's secure and can be audited to verify that with much more ease than bolting
19461
- * security onto an application later.
19462
- *
19463
- * <a name="contexts"></a>
19464
- * ## What trusted context types are supported?
19465
- *
19466
- * | Context | Notes |
19467
- * |---------------------|----------------|
19468
- * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered, and the {@link ngSanitize.$sanitize $sanitize} service is available (implemented by the {@link ngSanitize ngSanitize} module) this will sanitize the value instead of throwing an error. |
19469
- * | `$sce.CSS` | For CSS that's safe to source into the application. Currently, no bindings require this context. Feel free to use it in your own directives. |
19470
- * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=`, `<img src=`, and some others sanitize their urls and don't constitute an SCE context.) |
19471
- * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG`, `VIDEO`, `AUDIO`, `SOURCE`, and `TRACK` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does (it's not just the URL that matters, but also what is at the end of it), and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
19472
- * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently, no bindings require this context. Feel free to use it in your own directives. |
19473
- *
19474
- *
19475
- * Be aware that `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
19476
- * through {@link ng.$sce#getTrusted $sce.getTrusted}. There's no CSS-, URL-, or JS-context bindings
19477
- * in AngularJS currently, so their corresponding `$sce.trustAs` functions aren't useful yet. This
19478
- * might evolve.
19479
- *
19480
- * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
19481
- *
19482
- * Each element in these arrays must be one of the following:
19483
- *
19484
- * - **'self'**
19485
- * - The special **string**, `'self'`, can be used to match against all URLs of the **same
19486
- * domain** as the application document using the **same protocol**.
19487
- * - **String** (except the special value `'self'`)
19488
- * - The string is matched against the full *normalized / absolute URL* of the resource
19489
- * being tested (substring matches are not good enough.)
19490
- * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
19491
- * match themselves.
19492
- * - `*`: matches zero or more occurrences of any character other than one of the following 6
19493
- * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
19494
- * in a whitelist.
19495
- * - `**`: matches zero or more occurrences of *any* character. As such, it's not
19496
- * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
19497
- * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
19498
- * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
19499
- * http://foo.example.com/templates/**).
19500
- * - **RegExp** (*see caveat below*)
19501
- * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
19502
- * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
19503
- * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
19504
- * have good test coverage). For instance, the use of `.` in the regex is correct only in a
19505
- * small number of cases. A `.` character in the regex used when matching the scheme or a
19506
- * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
19507
- * is highly recommended to use the string patterns and only fall back to regular expressions
19508
- * as a last resort.
19509
- * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
19510
- * matched against the **entire** *normalized / absolute URL* of the resource being tested
19511
- * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
19512
- * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
19513
- * - If you are generating your JavaScript from some other templating engine (not
19514
- * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
19515
- * remember to escape your regular expression (and be aware that you might need more than
19516
- * one level of escaping depending on your templating engine and the way you interpolated
19517
- * the value.) Do make use of your platform's escaping mechanism as it might be good
19518
- * enough before coding your own. E.g. Ruby has
19519
- * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
19520
- * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
19521
- * Javascript lacks a similar built in function for escaping. Take a look at Google
19522
- * Closure library's [goog.string.regExpEscape(s)](
19523
- * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
19524
- *
19525
- * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
19526
- *
19527
- * ## Show me an example using SCE.
19528
- *
19529
- * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service">
19530
- * <file name="index.html">
19531
- * <div ng-controller="AppController as myCtrl">
19532
- * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
19533
- * <b>User comments</b><br>
19534
- * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
19535
- * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
19536
- * exploit.
19537
- * <div class="well">
19538
- * <div ng-repeat="userComment in myCtrl.userComments">
19539
- * <b>{{userComment.name}}</b>:
19540
- * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
19541
- * <br>
19542
- * </div>
19543
- * </div>
19544
- * </div>
19545
- * </file>
19546
- *
19547
- * <file name="script.js">
19548
- * angular.module('mySceApp', ['ngSanitize'])
19549
- * .controller('AppController', ['$http', '$templateCache', '$sce',
19550
- * function AppController($http, $templateCache, $sce) {
19551
- * var self = this;
19552
- * $http.get('test_data.json', {cache: $templateCache}).then(function(response) {
19553
- * self.userComments = response.data;
19554
- * });
19555
- * self.explicitlyTrustedHtml = $sce.trustAsHtml(
19556
- * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
19557
- * 'sanitization.&quot;">Hover over this text.</span>');
19558
- * }]);
19559
- * </file>
19560
- *
19561
- * <file name="test_data.json">
19562
- * [
19563
- * { "name": "Alice",
19564
- * "htmlComment":
19565
- * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
19566
- * },
19567
- * { "name": "Bob",
19568
- * "htmlComment": "<i>Yes!</i> Am I the only other one?"
19569
- * }
19570
- * ]
19571
- * </file>
19572
- *
19573
- * <file name="protractor.js" type="protractor">
19574
- * describe('SCE doc demo', function() {
19575
- * it('should sanitize untrusted values', function() {
19576
- * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML'))
19577
- * .toBe('<span>Is <i>anyone</i> reading this?</span>');
19578
- * });
19579
- *
19580
- * it('should NOT sanitize explicitly trusted values', function() {
19581
- * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe(
19582
- * '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
19583
- * 'sanitization.&quot;">Hover over this text.</span>');
19584
- * });
19585
- * });
19586
- * </file>
19587
- * </example>
19588
- *
19589
- *
19590
- *
19591
- * ## Can I disable SCE completely?
19592
- *
19593
- * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
19594
- * for little coding overhead. It will be much harder to take an SCE disabled application and
19595
- * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
19596
- * for cases where you have a lot of existing code that was written before SCE was introduced and
19597
- * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if
19598
- * you are writing a library, you will cause security bugs applications using it.
19599
- *
19600
- * That said, here's how you can completely disable SCE:
19601
- *
19602
- * ```
19603
- * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
19604
- * // Completely disable SCE. For demonstration purposes only!
19605
- * // Do not use in new projects or libraries.
19606
- * $sceProvider.enabled(false);
19607
- * });
19608
- * ```
19609
- *
19610
- */
19611
-
19612
- function $SceProvider() {
19613
- var enabled = true;
19614
-
19615
- /**
19616
- * @ngdoc method
19617
- * @name $sceProvider#enabled
19618
- * @kind function
19619
- *
19620
- * @param {boolean=} value If provided, then enables/disables SCE application-wide.
19621
- * @return {boolean} True if SCE is enabled, false otherwise.
19622
- *
19623
- * @description
19624
- * Enables/disables SCE and returns the current value.
19625
- */
19626
- this.enabled = function(value) {
19627
- if (arguments.length) {
19628
- enabled = !!value;
19629
- }
19630
- return enabled;
19631
- };
19632
-
19633
-
19634
- /* Design notes on the default implementation for SCE.
19635
- *
19636
- * The API contract for the SCE delegate
19637
- * -------------------------------------
19638
- * The SCE delegate object must provide the following 3 methods:
19639
- *
19640
- * - trustAs(contextEnum, value)
19641
- * This method is used to tell the SCE service that the provided value is OK to use in the
19642
- * contexts specified by contextEnum. It must return an object that will be accepted by
19643
- * getTrusted() for a compatible contextEnum and return this value.
19644
- *
19645
- * - valueOf(value)
19646
- * For values that were not produced by trustAs(), return them as is. For values that were
19647
- * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
19648
- * trustAs is wrapping the given values into some type, this operation unwraps it when given
19649
- * such a value.
19650
- *
19651
- * - getTrusted(contextEnum, value)
19652
- * This function should return the a value that is safe to use in the context specified by
19653
- * contextEnum or throw and exception otherwise.
19654
- *
19655
- * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
19656
- * opaque or wrapped in some holder object. That happens to be an implementation detail. For
19657
- * instance, an implementation could maintain a registry of all trusted objects by context. In
19658
- * such a case, trustAs() would return the same object that was passed in. getTrusted() would
19659
- * return the same object passed in if it was found in the registry under a compatible context or
19660
- * throw an exception otherwise. An implementation might only wrap values some of the time based
19661
- * on some criteria. getTrusted() might return a value and not throw an exception for special
19662
- * constants or objects even if not wrapped. All such implementations fulfill this contract.
19663
- *
19664
- *
19665
- * A note on the inheritance model for SCE contexts
19666
- * ------------------------------------------------
19667
- * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
19668
- * is purely an implementation details.
19669
- *
19670
- * The contract is simply this:
19671
- *
19672
- * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
19673
- * will also succeed.
19674
- *
19675
- * Inheritance happens to capture this in a natural way. In some future, we may not use
19676
- * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to
19677
- * be aware of this detail.
19678
- */
19679
-
19680
- this.$get = ['$parse', '$sceDelegate', function(
19681
- $parse, $sceDelegate) {
19682
- // Support: IE 9-11 only
19683
- // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
19684
- // the "expression(javascript expression)" syntax which is insecure.
19685
- if (enabled && msie < 8) {
19686
- throw $sceMinErr('iequirks',
19687
- 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
19688
- 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
19689
- 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
19690
- }
19691
-
19692
- var sce = shallowCopy(SCE_CONTEXTS);
19693
-
19694
- /**
19695
- * @ngdoc method
19696
- * @name $sce#isEnabled
19697
- * @kind function
19698
- *
19699
- * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you
19700
- * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
19701
- *
19702
- * @description
19703
- * Returns a boolean indicating if SCE is enabled.
19704
- */
19705
- sce.isEnabled = function() {
19706
- return enabled;
19707
- };
19708
- sce.trustAs = $sceDelegate.trustAs;
19709
- sce.getTrusted = $sceDelegate.getTrusted;
19710
- sce.valueOf = $sceDelegate.valueOf;
19711
-
19712
- if (!enabled) {
19713
- sce.trustAs = sce.getTrusted = function(type, value) { return value; };
19714
- sce.valueOf = identity;
19715
- }
19716
-
19717
- /**
19718
- * @ngdoc method
19719
- * @name $sce#parseAs
19720
- *
19721
- * @description
19722
- * Converts Angular {@link guide/expression expression} into a function. This is like {@link
19723
- * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
19724
- * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
19725
- * *result*)}
19726
- *
19727
- * @param {string} type The SCE context in which this result will be used.
19728
- * @param {string} expression String expression to compile.
19729
- * @return {function(context, locals)} A function which represents the compiled expression:
19730
- *
19731
- * * `context` – `{object}` – an object against which any expressions embedded in the
19732
- * strings are evaluated against (typically a scope object).
19733
- * * `locals` – `{object=}` – local variables context object, useful for overriding values
19734
- * in `context`.
19735
- */
19736
- sce.parseAs = function sceParseAs(type, expr) {
19737
- var parsed = $parse(expr);
19738
- if (parsed.literal && parsed.constant) {
19739
- return parsed;
19740
- } else {
19741
- return $parse(expr, function(value) {
19742
- return sce.getTrusted(type, value);
19743
- });
19744
- }
19745
- };
19746
-
19747
- /**
19748
- * @ngdoc method
19749
- * @name $sce#trustAs
19750
- *
19751
- * @description
19752
- * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a
19753
- * wrapped object that represents your value, and the trust you have in its safety for the given
19754
- * context. AngularJS can then use that value as-is in bindings of the specified secure context.
19755
- * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute
19756
- * interpolations. See {@link ng.$sce $sce} for strict contextual escaping.
19757
- *
19758
- * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
19759
- * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
19760
- *
19761
- * @param {*} value The value that that should be considered trusted.
19762
- * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19763
- * in the context you specified.
19764
- */
19765
-
19766
- /**
19767
- * @ngdoc method
19768
- * @name $sce#trustAsHtml
19769
- *
19770
- * @description
19771
- * Shorthand method. `$sce.trustAsHtml(value)` →
19772
- * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
19773
- *
19774
- * @param {*} value The value to mark as trusted for `$sce.HTML` context.
19775
- * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19776
- * in `$sce.HTML` context (like `ng-bind-html`).
19777
- */
19778
-
19779
- /**
19780
- * @ngdoc method
19781
- * @name $sce#trustAsCss
19782
- *
19783
- * @description
19784
- * Shorthand method. `$sce.trustAsCss(value)` →
19785
- * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`}
19786
- *
19787
- * @param {*} value The value to mark as trusted for `$sce.CSS` context.
19788
- * @return {*} A wrapped version of value that can be used as a trusted variant
19789
- * of your `value` in `$sce.CSS` context. This context is currently unused, so there are
19790
- * almost no reasons to use this function so far.
19791
- */
19792
-
19793
- /**
19794
- * @ngdoc method
19795
- * @name $sce#trustAsUrl
19796
- *
19797
- * @description
19798
- * Shorthand method. `$sce.trustAsUrl(value)` →
19799
- * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
19800
- *
19801
- * @param {*} value The value to mark as trusted for `$sce.URL` context.
19802
- * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19803
- * in `$sce.URL` context. That context is currently unused, so there are almost no reasons
19804
- * to use this function so far.
19805
- */
19806
-
19807
- /**
19808
- * @ngdoc method
19809
- * @name $sce#trustAsResourceUrl
19810
- *
19811
- * @description
19812
- * Shorthand method. `$sce.trustAsResourceUrl(value)` →
19813
- * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
19814
- *
19815
- * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context.
19816
- * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19817
- * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute
19818
- * bindings, ...)
19819
- */
19820
-
19821
- /**
19822
- * @ngdoc method
19823
- * @name $sce#trustAsJs
19824
- *
19825
- * @description
19826
- * Shorthand method. `$sce.trustAsJs(value)` →
19827
- * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
19828
- *
19829
- * @param {*} value The value to mark as trusted for `$sce.JS` context.
19830
- * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
19831
- * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to
19832
- * use this function so far.
19833
- */
19834
-
19835
- /**
19836
- * @ngdoc method
19837
- * @name $sce#getTrusted
19838
- *
19839
- * @description
19840
- * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
19841
- * takes any input, and either returns a value that's safe to use in the specified context,
19842
- * or throws an exception. This function is aware of trusted values created by the `trustAs`
19843
- * function and its shorthands, and when contexts are appropriate, returns the unwrapped value
19844
- * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a
19845
- * safe value (e.g., no sanitization is available or possible.)
19846
- *
19847
- * @param {string} type The context in which this value is to be used.
19848
- * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs
19849
- * `$sce.trustAs`} call, or anything else (which will not be considered trusted.)
19850
- * @return {*} A version of the value that's safe to use in the given context, or throws an
19851
- * exception if this is impossible.
19852
- */
19853
-
19854
- /**
19855
- * @ngdoc method
19856
- * @name $sce#getTrustedHtml
19857
- *
19858
- * @description
19859
- * Shorthand method. `$sce.getTrustedHtml(value)` →
19860
- * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
19861
- *
19862
- * @param {*} value The value to pass to `$sce.getTrusted`.
19863
- * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)`
19864
- */
19865
-
19866
- /**
19867
- * @ngdoc method
19868
- * @name $sce#getTrustedCss
19869
- *
19870
- * @description
19871
- * Shorthand method. `$sce.getTrustedCss(value)` →
19872
- * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
19873
- *
19874
- * @param {*} value The value to pass to `$sce.getTrusted`.
19875
- * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)`
19876
- */
19877
-
19878
- /**
19879
- * @ngdoc method
19880
- * @name $sce#getTrustedUrl
19881
- *
19882
- * @description
19883
- * Shorthand method. `$sce.getTrustedUrl(value)` →
19884
- * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
19885
- *
19886
- * @param {*} value The value to pass to `$sce.getTrusted`.
19887
- * @return {*} The return value of `$sce.getTrusted($sce.URL, value)`
19888
- */
19889
-
19890
- /**
19891
- * @ngdoc method
19892
- * @name $sce#getTrustedResourceUrl
19893
- *
19894
- * @description
19895
- * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
19896
- * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
19897
- *
19898
- * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
19899
- * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
19900
- */
19901
-
19902
- /**
19903
- * @ngdoc method
19904
- * @name $sce#getTrustedJs
19905
- *
19906
- * @description
19907
- * Shorthand method. `$sce.getTrustedJs(value)` →
19908
- * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
19909
- *
19910
- * @param {*} value The value to pass to `$sce.getTrusted`.
19911
- * @return {*} The return value of `$sce.getTrusted($sce.JS, value)`
19912
- */
19913
-
19914
- /**
19915
- * @ngdoc method
19916
- * @name $sce#parseAsHtml
19917
- *
19918
- * @description
19919
- * Shorthand method. `$sce.parseAsHtml(expression string)` →
19920
- * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
19921
- *
19922
- * @param {string} expression String expression to compile.
19923
- * @return {function(context, locals)} A function which represents the compiled expression:
19924
- *
19925
- * * `context` – `{object}` – an object against which any expressions embedded in the
19926
- * strings are evaluated against (typically a scope object).
19927
- * * `locals` – `{object=}` – local variables context object, useful for overriding values
19928
- * in `context`.
19929
- */
19930
-
19931
- /**
19932
- * @ngdoc method
19933
- * @name $sce#parseAsCss
19934
- *
19935
- * @description
19936
- * Shorthand method. `$sce.parseAsCss(value)` →
19937
- * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
19938
- *
19939
- * @param {string} expression String expression to compile.
19940
- * @return {function(context, locals)} A function which represents the compiled expression:
19941
- *
19942
- * * `context` – `{object}` – an object against which any expressions embedded in the
19943
- * strings are evaluated against (typically a scope object).
19944
- * * `locals` – `{object=}` – local variables context object, useful for overriding values
19945
- * in `context`.
19946
- */
19947
-
19948
- /**
19949
- * @ngdoc method
19950
- * @name $sce#parseAsUrl
19951
- *
19952
- * @description
19953
- * Shorthand method. `$sce.parseAsUrl(value)` →
19954
- * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
19955
- *
19956
- * @param {string} expression String expression to compile.
19957
- * @return {function(context, locals)} A function which represents the compiled expression:
19958
- *
19959
- * * `context` – `{object}` – an object against which any expressions embedded in the
19960
- * strings are evaluated against (typically a scope object).
19961
- * * `locals` – `{object=}` – local variables context object, useful for overriding values
19962
- * in `context`.
19963
- */
19964
-
19965
- /**
19966
- * @ngdoc method
19967
- * @name $sce#parseAsResourceUrl
19968
- *
19969
- * @description
19970
- * Shorthand method. `$sce.parseAsResourceUrl(value)` →
19971
- * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
19972
- *
19973
- * @param {string} expression String expression to compile.
19974
- * @return {function(context, locals)} A function which represents the compiled expression:
19975
- *
19976
- * * `context` – `{object}` – an object against which any expressions embedded in the
19977
- * strings are evaluated against (typically a scope object).
19978
- * * `locals` – `{object=}` – local variables context object, useful for overriding values
19979
- * in `context`.
19980
- */
19981
-
19982
- /**
19983
- * @ngdoc method
19984
- * @name $sce#parseAsJs
19985
- *
19986
- * @description
19987
- * Shorthand method. `$sce.parseAsJs(value)` →
19988
- * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
19989
- *
19990
- * @param {string} expression String expression to compile.
19991
- * @return {function(context, locals)} A function which represents the compiled expression:
19992
- *
19993
- * * `context` – `{object}` – an object against which any expressions embedded in the
19994
- * strings are evaluated against (typically a scope object).
19995
- * * `locals` – `{object=}` – local variables context object, useful for overriding values
19996
- * in `context`.
19997
- */
19998
-
19999
- // Shorthand delegations.
20000
- var parse = sce.parseAs,
20001
- getTrusted = sce.getTrusted,
20002
- trustAs = sce.trustAs;
20003
-
20004
- forEach(SCE_CONTEXTS, function(enumValue, name) {
20005
- var lName = lowercase(name);
20006
- sce[snakeToCamel('parse_as_' + lName)] = function(expr) {
20007
- return parse(enumValue, expr);
20008
- };
20009
- sce[snakeToCamel('get_trusted_' + lName)] = function(value) {
20010
- return getTrusted(enumValue, value);
20011
- };
20012
- sce[snakeToCamel('trust_as_' + lName)] = function(value) {
20013
- return trustAs(enumValue, value);
20014
- };
20015
- });
20016
-
20017
- return sce;
20018
- }];
20019
- }
20020
-
20021
- /* exported $SnifferProvider */
20022
-
20023
- /**
20024
- * !!! This is an undocumented "private" service !!!
20025
- *
20026
- * @name $sniffer
20027
- * @requires $window
20028
- * @requires $document
20029
- * @this
20030
- *
20031
- * @property {boolean} history Does the browser support html5 history api ?
20032
- * @property {boolean} transitions Does the browser support CSS transition events ?
20033
- * @property {boolean} animations Does the browser support CSS animation events ?
20034
- *
20035
- * @description
20036
- * This is very simple implementation of testing browser's features.
20037
- */
20038
- function $SnifferProvider() {
20039
- this.$get = ['$window', '$document', function($window, $document) {
20040
- var eventSupport = {},
20041
- // Chrome Packaged Apps are not allowed to access `history.pushState`.
20042
- // If not sandboxed, they can be detected by the presence of `chrome.app.runtime`
20043
- // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by
20044
- // the presence of an extension runtime ID and the absence of other Chrome runtime APIs
20045
- // (see https://developer.chrome.com/apps/manifest/sandbox).
20046
- // (NW.js apps have access to Chrome APIs, but do support `history`.)
20047
- isNw = $window.nw && $window.nw.process,
20048
- isChromePackagedApp =
20049
- !isNw &&
20050
- $window.chrome &&
20051
- ($window.chrome.app && $window.chrome.app.runtime ||
20052
- !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id),
20053
- hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState,
20054
- android =
20055
- toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
20056
- boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
20057
- document = $document[0] || {},
20058
- bodyStyle = document.body && document.body.style,
20059
- transitions = false,
20060
- animations = false;
20061
-
20062
- if (bodyStyle) {
20063
- // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x
20064
- // Mentioned browsers need a -webkit- prefix for transitions & animations.
20065
- transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle);
20066
- animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle);
20067
- }
20068
-
20069
-
20070
- return {
20071
- // Android has history.pushState, but it does not update location correctly
20072
- // so let's not use the history API at all.
20073
- // http://code.google.com/p/android/issues/detail?id=17471
20074
- // https://github.com/angular/angular.js/issues/904
20075
-
20076
- // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
20077
- // so let's not use the history API also
20078
- // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
20079
- history: !!(hasHistoryPushState && !(android < 4) && !boxee),
20080
- hasEvent: function(event) {
20081
- // Support: IE 9-11 only
20082
- // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
20083
- // it. In particular the event is not fired when backspace or delete key are pressed or
20084
- // when cut operation is performed.
20085
- // IE10+ implements 'input' event but it erroneously fires under various situations,
20086
- // e.g. when placeholder changes, or a form is focused.
20087
- if (event === 'input' && msie) return false;
20088
-
20089
- if (isUndefined(eventSupport[event])) {
20090
- var divElm = document.createElement('div');
20091
- eventSupport[event] = 'on' + event in divElm;
20092
- }
20093
-
20094
- return eventSupport[event];
20095
- },
20096
- csp: csp(),
20097
- transitions: transitions,
20098
- animations: animations,
20099
- android: android
20100
- };
20101
- }];
20102
- }
20103
-
20104
- var $templateRequestMinErr = minErr('$compile');
20105
-
20106
- /**
20107
- * @ngdoc provider
20108
- * @name $templateRequestProvider
20109
- * @this
20110
- *
20111
- * @description
20112
- * Used to configure the options passed to the {@link $http} service when making a template request.
20113
- *
20114
- * For example, it can be used for specifying the "Accept" header that is sent to the server, when
20115
- * requesting a template.
20116
- */
20117
- function $TemplateRequestProvider() {
20118
-
20119
- var httpOptions;
20120
-
20121
- /**
20122
- * @ngdoc method
20123
- * @name $templateRequestProvider#httpOptions
20124
- * @description
20125
- * The options to be passed to the {@link $http} service when making the request.
20126
- * You can use this to override options such as the "Accept" header for template requests.
20127
- *
20128
- * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the
20129
- * options if not overridden here.
20130
- *
20131
- * @param {string=} value new value for the {@link $http} options.
20132
- * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter.
20133
- */
20134
- this.httpOptions = function(val) {
20135
- if (val) {
20136
- httpOptions = val;
20137
- return this;
20138
- }
20139
- return httpOptions;
20140
- };
20141
-
20142
- /**
20143
- * @ngdoc service
20144
- * @name $templateRequest
20145
- *
20146
- * @description
20147
- * The `$templateRequest` service runs security checks then downloads the provided template using
20148
- * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
20149
- * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
20150
- * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
20151
- * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
20152
- * when `tpl` is of type string and `$templateCache` has the matching entry.
20153
- *
20154
- * If you want to pass custom options to the `$http` service, such as setting the Accept header you
20155
- * can configure this via {@link $templateRequestProvider#httpOptions}.
20156
- *
20157
- * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
20158
- * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
20159
- *
20160
- * @return {Promise} a promise for the HTTP response data of the given URL.
20161
- *
20162
- * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
20163
- */
20164
- this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce',
20165
- function($exceptionHandler, $templateCache, $http, $q, $sce) {
20166
-
20167
- function handleRequestFn(tpl, ignoreRequestError) {
20168
- handleRequestFn.totalPendingRequests++;
20169
-
20170
- // We consider the template cache holds only trusted templates, so
20171
- // there's no need to go through whitelisting again for keys that already
20172
- // are included in there. This also makes Angular accept any script
20173
- // directive, no matter its name. However, we still need to unwrap trusted
20174
- // types.
20175
- if (!isString(tpl) || isUndefined($templateCache.get(tpl))) {
20176
- tpl = $sce.getTrustedResourceUrl(tpl);
20177
- }
20178
-
20179
- var transformResponse = $http.defaults && $http.defaults.transformResponse;
20180
-
20181
- if (isArray(transformResponse)) {
20182
- transformResponse = transformResponse.filter(function(transformer) {
20183
- return transformer !== defaultHttpResponseTransform;
20184
- });
20185
- } else if (transformResponse === defaultHttpResponseTransform) {
20186
- transformResponse = null;
20187
- }
20188
-
20189
- return $http.get(tpl, extend({
20190
- cache: $templateCache,
20191
- transformResponse: transformResponse
20192
- }, httpOptions))
20193
- .finally(function() {
20194
- handleRequestFn.totalPendingRequests--;
20195
- })
20196
- .then(function(response) {
20197
- $templateCache.put(tpl, response.data);
20198
- return response.data;
20199
- }, handleError);
20200
-
20201
- function handleError(resp) {
20202
- if (!ignoreRequestError) {
20203
- resp = $templateRequestMinErr('tpload',
20204
- 'Failed to load template: {0} (HTTP status: {1} {2})',
20205
- tpl, resp.status, resp.statusText);
20206
-
20207
- $exceptionHandler(resp);
20208
- }
20209
-
20210
- return $q.reject(resp);
20211
- }
20212
- }
20213
-
20214
- handleRequestFn.totalPendingRequests = 0;
20215
-
20216
- return handleRequestFn;
20217
- }
20218
- ];
20219
- }
20220
-
20221
- /** @this */
20222
- function $$TestabilityProvider() {
20223
- this.$get = ['$rootScope', '$browser', '$location',
20224
- function($rootScope, $browser, $location) {
20225
-
20226
- /**
20227
- * @name $testability
20228
- *
20229
- * @description
20230
- * The private $$testability service provides a collection of methods for use when debugging
20231
- * or by automated test and debugging tools.
20232
- */
20233
- var testability = {};
20234
-
20235
- /**
20236
- * @name $$testability#findBindings
20237
- *
20238
- * @description
20239
- * Returns an array of elements that are bound (via ng-bind or {{}})
20240
- * to expressions matching the input.
20241
- *
20242
- * @param {Element} element The element root to search from.
20243
- * @param {string} expression The binding expression to match.
20244
- * @param {boolean} opt_exactMatch If true, only returns exact matches
20245
- * for the expression. Filters and whitespace are ignored.
20246
- */
20247
- testability.findBindings = function(element, expression, opt_exactMatch) {
20248
- var bindings = element.getElementsByClassName('ng-binding');
20249
- var matches = [];
20250
- forEach(bindings, function(binding) {
20251
- var dataBinding = angular.element(binding).data('$binding');
20252
- if (dataBinding) {
20253
- forEach(dataBinding, function(bindingName) {
20254
- if (opt_exactMatch) {
20255
- var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
20256
- if (matcher.test(bindingName)) {
20257
- matches.push(binding);
20258
- }
20259
- } else {
20260
- if (bindingName.indexOf(expression) !== -1) {
20261
- matches.push(binding);
20262
- }
20263
- }
20264
- });
20265
- }
20266
- });
20267
- return matches;
20268
- };
20269
-
20270
- /**
20271
- * @name $$testability#findModels
20272
- *
20273
- * @description
20274
- * Returns an array of elements that are two-way found via ng-model to
20275
- * expressions matching the input.
20276
- *
20277
- * @param {Element} element The element root to search from.
20278
- * @param {string} expression The model expression to match.
20279
- * @param {boolean} opt_exactMatch If true, only returns exact matches
20280
- * for the expression.
20281
- */
20282
- testability.findModels = function(element, expression, opt_exactMatch) {
20283
- var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
20284
- for (var p = 0; p < prefixes.length; ++p) {
20285
- var attributeEquals = opt_exactMatch ? '=' : '*=';
20286
- var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
20287
- var elements = element.querySelectorAll(selector);
20288
- if (elements.length) {
20289
- return elements;
20290
- }
20291
- }
20292
- };
20293
-
20294
- /**
20295
- * @name $$testability#getLocation
20296
- *
20297
- * @description
20298
- * Shortcut for getting the location in a browser agnostic way. Returns
20299
- * the path, search, and hash. (e.g. /path?a=b#hash)
20300
- */
20301
- testability.getLocation = function() {
20302
- return $location.url();
20303
- };
20304
-
20305
- /**
20306
- * @name $$testability#setLocation
20307
- *
20308
- * @description
20309
- * Shortcut for navigating to a location without doing a full page reload.
20310
- *
20311
- * @param {string} url The location url (path, search and hash,
20312
- * e.g. /path?a=b#hash) to go to.
20313
- */
20314
- testability.setLocation = function(url) {
20315
- if (url !== $location.url()) {
20316
- $location.url(url);
20317
- $rootScope.$digest();
20318
- }
20319
- };
20320
-
20321
- /**
20322
- * @name $$testability#whenStable
20323
- *
20324
- * @description
20325
- * Calls the callback when $timeout and $http requests are completed.
20326
- *
20327
- * @param {function} callback
20328
- */
20329
- testability.whenStable = function(callback) {
20330
- $browser.notifyWhenNoOutstandingRequests(callback);
20331
- };
20332
-
20333
- return testability;
20334
- }];
20335
- }
20336
-
20337
- /** @this */
20338
- function $TimeoutProvider() {
20339
- this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
20340
- function($rootScope, $browser, $q, $$q, $exceptionHandler) {
20341
-
20342
- var deferreds = {};
20343
-
20344
-
20345
- /**
20346
- * @ngdoc service
20347
- * @name $timeout
20348
- *
20349
- * @description
20350
- * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
20351
- * block and delegates any exceptions to
20352
- * {@link ng.$exceptionHandler $exceptionHandler} service.
20353
- *
20354
- * The return value of calling `$timeout` is a promise, which will be resolved when
20355
- * the delay has passed and the timeout function, if provided, is executed.
20356
- *
20357
- * To cancel a timeout request, call `$timeout.cancel(promise)`.
20358
- *
20359
- * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
20360
- * synchronously flush the queue of deferred functions.
20361
- *
20362
- * If you only want a promise that will be resolved after some specified delay
20363
- * then you can call `$timeout` without the `fn` function.
20364
- *
20365
- * @param {function()=} fn A function, whose execution should be delayed.
20366
- * @param {number=} [delay=0] Delay in milliseconds.
20367
- * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
20368
- * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
20369
- * @param {...*=} Pass additional parameters to the executed function.
20370
- * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise
20371
- * will be resolved with the return value of the `fn` function.
20372
- *
20373
- */
20374
- function timeout(fn, delay, invokeApply) {
20375
- if (!isFunction(fn)) {
20376
- invokeApply = delay;
20377
- delay = fn;
20378
- fn = noop;
20379
- }
20380
-
20381
- var args = sliceArgs(arguments, 3),
20382
- skipApply = (isDefined(invokeApply) && !invokeApply),
20383
- deferred = (skipApply ? $$q : $q).defer(),
20384
- promise = deferred.promise,
20385
- timeoutId;
20386
-
20387
- timeoutId = $browser.defer(function() {
20388
- try {
20389
- deferred.resolve(fn.apply(null, args));
20390
- } catch (e) {
20391
- deferred.reject(e);
20392
- $exceptionHandler(e);
20393
- } finally {
20394
- delete deferreds[promise.$$timeoutId];
20395
- }
20396
-
20397
- if (!skipApply) $rootScope.$apply();
20398
- }, delay);
20399
-
20400
- promise.$$timeoutId = timeoutId;
20401
- deferreds[timeoutId] = deferred;
20402
-
20403
- return promise;
20404
- }
20405
-
20406
-
20407
- /**
20408
- * @ngdoc method
20409
- * @name $timeout#cancel
20410
- *
20411
- * @description
20412
- * Cancels a task associated with the `promise`. As a result of this, the promise will be
20413
- * resolved with a rejection.
20414
- *
20415
- * @param {Promise=} promise Promise returned by the `$timeout` function.
20416
- * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
20417
- * canceled.
20418
- */
20419
- timeout.cancel = function(promise) {
20420
- if (promise && promise.$$timeoutId in deferreds) {
20421
- // Timeout cancels should not report an unhandled promise.
20422
- markQExceptionHandled(deferreds[promise.$$timeoutId].promise);
20423
- deferreds[promise.$$timeoutId].reject('canceled');
20424
- delete deferreds[promise.$$timeoutId];
20425
- return $browser.defer.cancel(promise.$$timeoutId);
20426
- }
20427
- return false;
20428
- };
20429
-
20430
- return timeout;
20431
- }];
20432
- }
20433
-
20434
- // NOTE: The usage of window and document instead of $window and $document here is
20435
- // deliberate. This service depends on the specific behavior of anchor nodes created by the
20436
- // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
20437
- // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
20438
- // doesn't know about mocked locations and resolves URLs to the real document - which is
20439
- // exactly the behavior needed here. There is little value is mocking these out for this
20440
- // service.
20441
- var urlParsingNode = window.document.createElement('a');
20442
- var originUrl = urlResolve(window.location.href);
20443
-
20444
-
20445
- /**
20446
- *
20447
- * Implementation Notes for non-IE browsers
20448
- * ----------------------------------------
20449
- * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
20450
- * results both in the normalizing and parsing of the URL. Normalizing means that a relative
20451
- * URL will be resolved into an absolute URL in the context of the application document.
20452
- * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
20453
- * properties are all populated to reflect the normalized URL. This approach has wide
20454
- * compatibility - Safari 1+, Mozilla 1+ etc. See
20455
- * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
20456
- *
20457
- * Implementation Notes for IE
20458
- * ---------------------------
20459
- * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
20460
- * browsers. However, the parsed components will not be set if the URL assigned did not specify
20461
- * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
20462
- * work around that by performing the parsing in a 2nd step by taking a previously normalized
20463
- * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
20464
- * properties such as protocol, hostname, port, etc.
20465
- *
20466
- * References:
20467
- * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
20468
- * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
20469
- * http://url.spec.whatwg.org/#urlutils
20470
- * https://github.com/angular/angular.js/pull/2902
20471
- * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
20472
- *
20473
- * @kind function
20474
- * @param {string} url The URL to be parsed.
20475
- * @description Normalizes and parses a URL.
20476
- * @returns {object} Returns the normalized URL as a dictionary.
20477
- *
20478
- * | member name | Description |
20479
- * |---------------|----------------|
20480
- * | href | A normalized version of the provided URL if it was not an absolute URL |
20481
- * | protocol | The protocol including the trailing colon |
20482
- * | host | The host and port (if the port is non-default) of the normalizedUrl |
20483
- * | search | The search params, minus the question mark |
20484
- * | hash | The hash string, minus the hash symbol
20485
- * | hostname | The hostname
20486
- * | port | The port, without ":"
20487
- * | pathname | The pathname, beginning with "/"
20488
- *
20489
- */
20490
- function urlResolve(url) {
20491
- var href = url;
20492
-
20493
- // Support: IE 9-11 only
20494
- if (msie) {
20495
- // Normalize before parse. Refer Implementation Notes on why this is
20496
- // done in two steps on IE.
20497
- urlParsingNode.setAttribute('href', href);
20498
- href = urlParsingNode.href;
20499
- }
20500
-
20501
- urlParsingNode.setAttribute('href', href);
20502
-
20503
- // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
20504
- return {
20505
- href: urlParsingNode.href,
20506
- protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
20507
- host: urlParsingNode.host,
20508
- search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
20509
- hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
20510
- hostname: urlParsingNode.hostname,
20511
- port: urlParsingNode.port,
20512
- pathname: (urlParsingNode.pathname.charAt(0) === '/')
20513
- ? urlParsingNode.pathname
20514
- : '/' + urlParsingNode.pathname
20515
- };
20516
- }
20517
-
20518
- /**
20519
- * Parse a request URL and determine whether this is a same-origin request as the application document.
20520
- *
20521
- * @param {string|object} requestUrl The url of the request as a string that will be resolved
20522
- * or a parsed URL object.
20523
- * @returns {boolean} Whether the request is for the same origin as the application document.
20524
- */
20525
- function urlIsSameOrigin(requestUrl) {
20526
- var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
20527
- return (parsed.protocol === originUrl.protocol &&
20528
- parsed.host === originUrl.host);
20529
- }
20530
-
20531
- /**
20532
- * @ngdoc service
20533
- * @name $window
20534
- * @this
20535
- *
20536
- * @description
20537
- * A reference to the browser's `window` object. While `window`
20538
- * is globally available in JavaScript, it causes testability problems, because
20539
- * it is a global variable. In angular we always refer to it through the
20540
- * `$window` service, so it may be overridden, removed or mocked for testing.
20541
- *
20542
- * Expressions, like the one defined for the `ngClick` directive in the example
20543
- * below, are evaluated with respect to the current scope. Therefore, there is
20544
- * no risk of inadvertently coding in a dependency on a global value in such an
20545
- * expression.
20546
- *
20547
- * @example
20548
- <example module="windowExample" name="window-service">
20549
- <file name="index.html">
20550
- <script>
20551
- angular.module('windowExample', [])
20552
- .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
20553
- $scope.greeting = 'Hello, World!';
20554
- $scope.doGreeting = function(greeting) {
20555
- $window.alert(greeting);
20556
- };
20557
- }]);
20558
- </script>
20559
- <div ng-controller="ExampleController">
20560
- <input type="text" ng-model="greeting" aria-label="greeting" />
20561
- <button ng-click="doGreeting(greeting)">ALERT</button>
20562
- </div>
20563
- </file>
20564
- <file name="protractor.js" type="protractor">
20565
- it('should display the greeting in the input box', function() {
20566
- element(by.model('greeting')).sendKeys('Hello, E2E Tests');
20567
- // If we click the button it will block the test runner
20568
- // element(':button').click();
20569
- });
20570
- </file>
20571
- </example>
20572
- */
20573
- function $WindowProvider() {
20574
- this.$get = valueFn(window);
20575
- }
20576
-
20577
- /**
20578
- * @name $$cookieReader
20579
- * @requires $document
20580
- *
20581
- * @description
20582
- * This is a private service for reading cookies used by $http and ngCookies
20583
- *
20584
- * @return {Object} a key/value map of the current cookies
20585
- */
20586
- function $$CookieReader($document) {
20587
- var rawDocument = $document[0] || {};
20588
- var lastCookies = {};
20589
- var lastCookieString = '';
20590
-
20591
- function safeGetCookie(rawDocument) {
20592
- try {
20593
- return rawDocument.cookie || '';
20594
- } catch (e) {
20595
- return '';
20596
- }
20597
- }
20598
-
20599
- function safeDecodeURIComponent(str) {
20600
- try {
20601
- return decodeURIComponent(str);
20602
- } catch (e) {
20603
- return str;
20604
- }
20605
- }
20606
-
20607
- return function() {
20608
- var cookieArray, cookie, i, index, name;
20609
- var currentCookieString = safeGetCookie(rawDocument);
20610
-
20611
- if (currentCookieString !== lastCookieString) {
20612
- lastCookieString = currentCookieString;
20613
- cookieArray = lastCookieString.split('; ');
20614
- lastCookies = {};
20615
-
20616
- for (i = 0; i < cookieArray.length; i++) {
20617
- cookie = cookieArray[i];
20618
- index = cookie.indexOf('=');
20619
- if (index > 0) { //ignore nameless cookies
20620
- name = safeDecodeURIComponent(cookie.substring(0, index));
20621
- // the first value that is seen for a cookie is the most
20622
- // specific one. values for the same cookie name that
20623
- // follow are for less specific paths.
20624
- if (isUndefined(lastCookies[name])) {
20625
- lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
20626
- }
20627
- }
20628
- }
20629
- }
20630
- return lastCookies;
20631
- };
20632
- }
20633
-
20634
- $$CookieReader.$inject = ['$document'];
20635
-
20636
- /** @this */
20637
- function $$CookieReaderProvider() {
20638
- this.$get = $$CookieReader;
20639
- }
20640
-
20641
- /* global currencyFilter: true,
20642
- dateFilter: true,
20643
- filterFilter: true,
20644
- jsonFilter: true,
20645
- limitToFilter: true,
20646
- lowercaseFilter: true,
20647
- numberFilter: true,
20648
- orderByFilter: true,
20649
- uppercaseFilter: true,
20650
- */
20651
-
20652
- /**
20653
- * @ngdoc provider
20654
- * @name $filterProvider
20655
- * @description
20656
- *
20657
- * Filters are just functions which transform input to an output. However filters need to be
20658
- * Dependency Injected. To achieve this a filter definition consists of a factory function which is
20659
- * annotated with dependencies and is responsible for creating a filter function.
20660
- *
20661
- * <div class="alert alert-warning">
20662
- * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
20663
- * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
20664
- * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
20665
- * (`myapp_subsection_filterx`).
20666
- * </div>
20667
- *
20668
- * ```js
20669
- * // Filter registration
20670
- * function MyModule($provide, $filterProvider) {
20671
- * // create a service to demonstrate injection (not always needed)
20672
- * $provide.value('greet', function(name){
20673
- * return 'Hello ' + name + '!';
20674
- * });
20675
- *
20676
- * // register a filter factory which uses the
20677
- * // greet service to demonstrate DI.
20678
- * $filterProvider.register('greet', function(greet){
20679
- * // return the filter function which uses the greet service
20680
- * // to generate salutation
20681
- * return function(text) {
20682
- * // filters need to be forgiving so check input validity
20683
- * return text && greet(text) || text;
20684
- * };
20685
- * });
20686
- * }
20687
- * ```
20688
- *
20689
- * The filter function is registered with the `$injector` under the filter name suffix with
20690
- * `Filter`.
20691
- *
20692
- * ```js
20693
- * it('should be the same instance', inject(
20694
- * function($filterProvider) {
20695
- * $filterProvider.register('reverse', function(){
20696
- * return ...;
20697
- * });
20698
- * },
20699
- * function($filter, reverseFilter) {
20700
- * expect($filter('reverse')).toBe(reverseFilter);
20701
- * });
20702
- * ```
20703
- *
20704
- *
20705
- * For more information about how angular filters work, and how to create your own filters, see
20706
- * {@link guide/filter Filters} in the Angular Developer Guide.
20707
- */
20708
-
20709
- /**
20710
- * @ngdoc service
20711
- * @name $filter
20712
- * @kind function
20713
- * @description
20714
- * Filters are used for formatting data displayed to the user.
20715
- *
20716
- * They can be used in view templates, controllers or services.Angular comes
20717
- * with a collection of [built-in filters](api/ng/filter), but it is easy to
20718
- * define your own as well.
20719
- *
20720
- * The general syntax in templates is as follows:
20721
- *
20722
- * ```html
20723
- * {{ expression [| filter_name[:parameter_value] ... ] }}
20724
- * ```
20725
- *
20726
- * @param {String} name Name of the filter function to retrieve
20727
- * @return {Function} the filter function
20728
- * @example
20729
- <example name="$filter" module="filterExample">
20730
- <file name="index.html">
20731
- <div ng-controller="MainCtrl">
20732
- <h3>{{ originalText }}</h3>
20733
- <h3>{{ filteredText }}</h3>
20734
- </div>
20735
- </file>
20736
-
20737
- <file name="script.js">
20738
- angular.module('filterExample', [])
20739
- .controller('MainCtrl', function($scope, $filter) {
20740
- $scope.originalText = 'hello';
20741
- $scope.filteredText = $filter('uppercase')($scope.originalText);
20742
- });
20743
- </file>
20744
- </example>
20745
- */
20746
- $FilterProvider.$inject = ['$provide'];
20747
- /** @this */
20748
- function $FilterProvider($provide) {
20749
- var suffix = 'Filter';
20750
-
20751
- /**
20752
- * @ngdoc method
20753
- * @name $filterProvider#register
20754
- * @param {string|Object} name Name of the filter function, or an object map of filters where
20755
- * the keys are the filter names and the values are the filter factories.
20756
- *
20757
- * <div class="alert alert-warning">
20758
- * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
20759
- * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
20760
- * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
20761
- * (`myapp_subsection_filterx`).
20762
- * </div>
20763
- * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
20764
- * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
20765
- * of the registered filter instances.
20766
- */
20767
- function register(name, factory) {
20768
- if (isObject(name)) {
20769
- var filters = {};
20770
- forEach(name, function(filter, key) {
20771
- filters[key] = register(key, filter);
20772
- });
20773
- return filters;
20774
- } else {
20775
- return $provide.factory(name + suffix, factory);
20776
- }
20777
- }
20778
- this.register = register;
20779
-
20780
- this.$get = ['$injector', function($injector) {
20781
- return function(name) {
20782
- return $injector.get(name + suffix);
20783
- };
20784
- }];
20785
-
20786
- ////////////////////////////////////////
20787
-
20788
- /* global
20789
- currencyFilter: false,
20790
- dateFilter: false,
20791
- filterFilter: false,
20792
- jsonFilter: false,
20793
- limitToFilter: false,
20794
- lowercaseFilter: false,
20795
- numberFilter: false,
20796
- orderByFilter: false,
20797
- uppercaseFilter: false
20798
- */
20799
-
20800
- register('currency', currencyFilter);
20801
- register('date', dateFilter);
20802
- register('filter', filterFilter);
20803
- register('json', jsonFilter);
20804
- register('limitTo', limitToFilter);
20805
- register('lowercase', lowercaseFilter);
20806
- register('number', numberFilter);
20807
- register('orderBy', orderByFilter);
20808
- register('uppercase', uppercaseFilter);
20809
- }
20810
-
20811
- /**
20812
- * @ngdoc filter
20813
- * @name filter
20814
- * @kind function
20815
- *
20816
- * @description
20817
- * Selects a subset of items from `array` and returns it as a new array.
20818
- *
20819
- * @param {Array} array The source array.
20820
- * <div class="alert alert-info">
20821
- * **Note**: If the array contains objects that reference themselves, filtering is not possible.
20822
- * </div>
20823
- * @param {string|Object|function()} expression The predicate to be used for selecting items from
20824
- * `array`.
20825
- *
20826
- * Can be one of:
20827
- *
20828
- * - `string`: The string is used for matching against the contents of the `array`. All strings or
20829
- * objects with string properties in `array` that match this string will be returned. This also
20830
- * applies to nested object properties.
20831
- * The predicate can be negated by prefixing the string with `!`.
20832
- *
20833
- * - `Object`: A pattern object can be used to filter specific properties on objects contained
20834
- * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
20835
- * which have property `name` containing "M" and property `phone` containing "1". A special
20836
- * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
20837
- * against any property of the object or its nested object properties. That's equivalent to the
20838
- * simple substring match with a `string` as described above. The special property name can be
20839
- * overwritten, using the `anyPropertyKey` parameter.
20840
- * The predicate can be negated by prefixing the string with `!`.
20841
- * For example `{name: "!M"}` predicate will return an array of items which have property `name`
20842
- * not containing "M".
20843
- *
20844
- * Note that a named property will match properties on the same level only, while the special
20845
- * `$` property will match properties on the same level or deeper. E.g. an array item like
20846
- * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
20847
- * **will** be matched by `{$: 'John'}`.
20848
- *
20849
- * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
20850
- * The function is called for each element of the array, with the element, its index, and
20851
- * the entire array itself as arguments.
20852
- *
20853
- * The final result is an array of those elements that the predicate returned true for.
20854
- *
20855
- * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in
20856
- * determining if values retrieved using `expression` (when it is not a function) should be
20857
- * considered a match based on the expected value (from the filter expression) and actual
20858
- * value (from the object in the array).
20859
- *
20860
- * Can be one of:
20861
- *
20862
- * - `function(actual, expected)`:
20863
- * The function will be given the object value and the predicate value to compare and
20864
- * should return true if both values should be considered equal.
20865
- *
20866
- * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
20867
- * This is essentially strict comparison of expected and actual.
20868
- *
20869
- * - `false`: A short hand for a function which will look for a substring match in a case
20870
- * insensitive way. Primitive values are converted to strings. Objects are not compared against
20871
- * primitives, unless they have a custom `toString` method (e.g. `Date` objects).
20872
- *
20873
- *
20874
- * Defaults to `false`.
20875
- *
20876
- * @param {string} [anyPropertyKey] The special property name that matches against any property.
20877
- * By default `$`.
20878
- *
20879
- * @example
20880
- <example name="filter-filter">
20881
- <file name="index.html">
20882
- <div ng-init="friends = [{name:'John', phone:'555-1276'},
20883
- {name:'Mary', phone:'800-BIG-MARY'},
20884
- {name:'Mike', phone:'555-4321'},
20885
- {name:'Adam', phone:'555-5678'},
20886
- {name:'Julie', phone:'555-8765'},
20887
- {name:'Juliette', phone:'555-5678'}]"></div>
20888
-
20889
- <label>Search: <input ng-model="searchText"></label>
20890
- <table id="searchTextResults">
20891
- <tr><th>Name</th><th>Phone</th></tr>
20892
- <tr ng-repeat="friend in friends | filter:searchText">
20893
- <td>{{friend.name}}</td>
20894
- <td>{{friend.phone}}</td>
20895
- </tr>
20896
- </table>
20897
- <hr>
20898
- <label>Any: <input ng-model="search.$"></label> <br>
20899
- <label>Name only <input ng-model="search.name"></label><br>
20900
- <label>Phone only <input ng-model="search.phone"></label><br>
20901
- <label>Equality <input type="checkbox" ng-model="strict"></label><br>
20902
- <table id="searchObjResults">
20903
- <tr><th>Name</th><th>Phone</th></tr>
20904
- <tr ng-repeat="friendObj in friends | filter:search:strict">
20905
- <td>{{friendObj.name}}</td>
20906
- <td>{{friendObj.phone}}</td>
20907
- </tr>
20908
- </table>
20909
- </file>
20910
- <file name="protractor.js" type="protractor">
20911
- var expectFriendNames = function(expectedNames, key) {
20912
- element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
20913
- arr.forEach(function(wd, i) {
20914
- expect(wd.getText()).toMatch(expectedNames[i]);
20915
- });
20916
- });
20917
- };
20918
-
20919
- it('should search across all fields when filtering with a string', function() {
20920
- var searchText = element(by.model('searchText'));
20921
- searchText.clear();
20922
- searchText.sendKeys('m');
20923
- expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
20924
-
20925
- searchText.clear();
20926
- searchText.sendKeys('76');
20927
- expectFriendNames(['John', 'Julie'], 'friend');
20928
- });
20929
-
20930
- it('should search in specific fields when filtering with a predicate object', function() {
20931
- var searchAny = element(by.model('search.$'));
20932
- searchAny.clear();
20933
- searchAny.sendKeys('i');
20934
- expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
20935
- });
20936
- it('should use a equal comparison when comparator is true', function() {
20937
- var searchName = element(by.model('search.name'));
20938
- var strict = element(by.model('strict'));
20939
- searchName.clear();
20940
- searchName.sendKeys('Julie');
20941
- strict.click();
20942
- expectFriendNames(['Julie'], 'friendObj');
20943
- });
20944
- </file>
20945
- </example>
20946
- */
20947
-
20948
- function filterFilter() {
20949
- return function(array, expression, comparator, anyPropertyKey) {
20950
- if (!isArrayLike(array)) {
20951
- if (array == null) {
20952
- return array;
20953
- } else {
20954
- throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
20955
- }
20956
- }
20957
-
20958
- anyPropertyKey = anyPropertyKey || '$';
20959
- var expressionType = getTypeForFilter(expression);
20960
- var predicateFn;
20961
- var matchAgainstAnyProp;
20962
-
20963
- switch (expressionType) {
20964
- case 'function':
20965
- predicateFn = expression;
20966
- break;
20967
- case 'boolean':
20968
- case 'null':
20969
- case 'number':
20970
- case 'string':
20971
- matchAgainstAnyProp = true;
20972
- // falls through
20973
- case 'object':
20974
- predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp);
20975
- break;
20976
- default:
20977
- return array;
20978
- }
20979
-
20980
- return Array.prototype.filter.call(array, predicateFn);
20981
- };
20982
- }
20983
-
20984
- // Helper functions for `filterFilter`
20985
- function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
20986
- var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression);
20987
- var predicateFn;
20988
-
20989
- if (comparator === true) {
20990
- comparator = equals;
20991
- } else if (!isFunction(comparator)) {
20992
- comparator = function(actual, expected) {
20993
- if (isUndefined(actual)) {
20994
- // No substring matching against `undefined`
20995
- return false;
20996
- }
20997
- if ((actual === null) || (expected === null)) {
20998
- // No substring matching against `null`; only match against `null`
20999
- return actual === expected;
21000
- }
21001
- if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
21002
- // Should not compare primitives against objects, unless they have custom `toString` method
21003
- return false;
21004
- }
21005
-
21006
- actual = lowercase('' + actual);
21007
- expected = lowercase('' + expected);
21008
- return actual.indexOf(expected) !== -1;
21009
- };
21010
- }
21011
-
21012
- predicateFn = function(item) {
21013
- if (shouldMatchPrimitives && !isObject(item)) {
21014
- return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
21015
- }
21016
- return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
21017
- };
21018
-
21019
- return predicateFn;
21020
- }
21021
-
21022
- function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
21023
- var actualType = getTypeForFilter(actual);
21024
- var expectedType = getTypeForFilter(expected);
21025
-
21026
- if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
21027
- return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
21028
- } else if (isArray(actual)) {
21029
- // In case `actual` is an array, consider it a match
21030
- // if ANY of it's items matches `expected`
21031
- return actual.some(function(item) {
21032
- return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
21033
- });
21034
- }
21035
-
21036
- switch (actualType) {
21037
- case 'object':
21038
- var key;
21039
- if (matchAgainstAnyProp) {
21040
- for (key in actual) {
21041
- // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined
21042
- // See: https://github.com/angular/angular.js/issues/15644
21043
- if (key.charAt && (key.charAt(0) !== '$') &&
21044
- deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
21045
- return true;
21046
- }
21047
- }
21048
- return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false);
21049
- } else if (expectedType === 'object') {
21050
- for (key in expected) {
21051
- var expectedVal = expected[key];
21052
- if (isFunction(expectedVal) || isUndefined(expectedVal)) {
21053
- continue;
21054
- }
21055
-
21056
- var matchAnyProperty = key === anyPropertyKey;
21057
- var actualVal = matchAnyProperty ? actual : actual[key];
21058
- if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
21059
- return false;
21060
- }
21061
- }
21062
- return true;
21063
- } else {
21064
- return comparator(actual, expected);
21065
- }
21066
- case 'function':
21067
- return false;
21068
- default:
21069
- return comparator(actual, expected);
21070
- }
21071
- }
21072
-
21073
- // Used for easily differentiating between `null` and actual `object`
21074
- function getTypeForFilter(val) {
21075
- return (val === null) ? 'null' : typeof val;
21076
- }
21077
-
21078
- var MAX_DIGITS = 22;
21079
- var DECIMAL_SEP = '.';
21080
- var ZERO_CHAR = '0';
21081
-
21082
- /**
21083
- * @ngdoc filter
21084
- * @name currency
21085
- * @kind function
21086
- *
21087
- * @description
21088
- * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
21089
- * symbol for current locale is used.
21090
- *
21091
- * @param {number} amount Input to filter.
21092
- * @param {string=} symbol Currency symbol or identifier to be displayed.
21093
- * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
21094
- * @returns {string} Formatted number.
21095
- *
21096
- *
21097
- * @example
21098
- <example module="currencyExample" name="currency-filter">
21099
- <file name="index.html">
21100
- <script>
21101
- angular.module('currencyExample', [])
21102
- .controller('ExampleController', ['$scope', function($scope) {
21103
- $scope.amount = 1234.56;
21104
- }]);
21105
- </script>
21106
- <div ng-controller="ExampleController">
21107
- <input type="number" ng-model="amount" aria-label="amount"> <br>
21108
- default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
21109
- custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span><br>
21110
- no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
21111
- </div>
21112
- </file>
21113
- <file name="protractor.js" type="protractor">
21114
- it('should init with 1234.56', function() {
21115
- expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
21116
- expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
21117
- expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
21118
- });
21119
- it('should update', function() {
21120
- if (browser.params.browser === 'safari') {
21121
- // Safari does not understand the minus key. See
21122
- // https://github.com/angular/protractor/issues/481
21123
- return;
21124
- }
21125
- element(by.model('amount')).clear();
21126
- element(by.model('amount')).sendKeys('-1234');
21127
- expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
21128
- expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
21129
- expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
21130
- });
21131
- </file>
21132
- </example>
21133
- */
21134
- currencyFilter.$inject = ['$locale'];
21135
- function currencyFilter($locale) {
21136
- var formats = $locale.NUMBER_FORMATS;
21137
- return function(amount, currencySymbol, fractionSize) {
21138
- if (isUndefined(currencySymbol)) {
21139
- currencySymbol = formats.CURRENCY_SYM;
21140
- }
21141
-
21142
- if (isUndefined(fractionSize)) {
21143
- fractionSize = formats.PATTERNS[1].maxFrac;
21144
- }
21145
-
21146
- // if null or undefined pass it through
21147
- return (amount == null)
21148
- ? amount
21149
- : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
21150
- replace(/\u00A4/g, currencySymbol);
21151
- };
21152
- }
21153
-
21154
- /**
21155
- * @ngdoc filter
21156
- * @name number
21157
- * @kind function
21158
- *
21159
- * @description
21160
- * Formats a number as text.
21161
- *
21162
- * If the input is null or undefined, it will just be returned.
21163
- * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively.
21164
- * If the input is not a number an empty string is returned.
21165
- *
21166
- *
21167
- * @param {number|string} number Number to format.
21168
- * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
21169
- * If this is not provided then the fraction size is computed from the current locale's number
21170
- * formatting pattern. In the case of the default locale, it will be 3.
21171
- * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
21172
- * locale (e.g., in the en_US locale it will have "." as the decimal separator and
21173
- * include "," group separators after each third digit).
21174
- *
21175
- * @example
21176
- <example module="numberFilterExample" name="number-filter">
21177
- <file name="index.html">
21178
- <script>
21179
- angular.module('numberFilterExample', [])
21180
- .controller('ExampleController', ['$scope', function($scope) {
21181
- $scope.val = 1234.56789;
21182
- }]);
21183
- </script>
21184
- <div ng-controller="ExampleController">
21185
- <label>Enter number: <input ng-model='val'></label><br>
21186
- Default formatting: <span id='number-default'>{{val | number}}</span><br>
21187
- No fractions: <span>{{val | number:0}}</span><br>
21188
- Negative number: <span>{{-val | number:4}}</span>
21189
- </div>
21190
- </file>
21191
- <file name="protractor.js" type="protractor">
21192
- it('should format numbers', function() {
21193
- expect(element(by.id('number-default')).getText()).toBe('1,234.568');
21194
- expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
21195
- expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
21196
- });
21197
-
21198
- it('should update', function() {
21199
- element(by.model('val')).clear();
21200
- element(by.model('val')).sendKeys('3374.333');
21201
- expect(element(by.id('number-default')).getText()).toBe('3,374.333');
21202
- expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
21203
- expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
21204
- });
21205
- </file>
21206
- </example>
21207
- */
21208
- numberFilter.$inject = ['$locale'];
21209
- function numberFilter($locale) {
21210
- var formats = $locale.NUMBER_FORMATS;
21211
- return function(number, fractionSize) {
21212
-
21213
- // if null or undefined pass it through
21214
- return (number == null)
21215
- ? number
21216
- : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
21217
- fractionSize);
21218
- };
21219
- }
21220
-
21221
- /**
21222
- * Parse a number (as a string) into three components that can be used
21223
- * for formatting the number.
21224
- *
21225
- * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
21226
- *
21227
- * @param {string} numStr The number to parse
21228
- * @return {object} An object describing this number, containing the following keys:
21229
- * - d : an array of digits containing leading zeros as necessary
21230
- * - i : the number of the digits in `d` that are to the left of the decimal point
21231
- * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
21232
- *
21233
- */
21234
- function parse(numStr) {
21235
- var exponent = 0, digits, numberOfIntegerDigits;
21236
- var i, j, zeros;
21237
-
21238
- // Decimal point?
21239
- if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
21240
- numStr = numStr.replace(DECIMAL_SEP, '');
21241
- }
21242
-
21243
- // Exponential form?
21244
- if ((i = numStr.search(/e/i)) > 0) {
21245
- // Work out the exponent.
21246
- if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i;
21247
- numberOfIntegerDigits += +numStr.slice(i + 1);
21248
- numStr = numStr.substring(0, i);
21249
- } else if (numberOfIntegerDigits < 0) {
21250
- // There was no decimal point or exponent so it is an integer.
21251
- numberOfIntegerDigits = numStr.length;
21252
- }
21253
-
21254
- // Count the number of leading zeros.
21255
- for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ }
21256
-
21257
- if (i === (zeros = numStr.length)) {
21258
- // The digits are all zero.
21259
- digits = [0];
21260
- numberOfIntegerDigits = 1;
21261
- } else {
21262
- // Count the number of trailing zeros
21263
- zeros--;
21264
- while (numStr.charAt(zeros) === ZERO_CHAR) zeros--;
21265
-
21266
- // Trailing zeros are insignificant so ignore them
21267
- numberOfIntegerDigits -= i;
21268
- digits = [];
21269
- // Convert string to array of digits without leading/trailing zeros.
21270
- for (j = 0; i <= zeros; i++, j++) {
21271
- digits[j] = +numStr.charAt(i);
21272
- }
21273
- }
21274
-
21275
- // If the number overflows the maximum allowed digits then use an exponent.
21276
- if (numberOfIntegerDigits > MAX_DIGITS) {
21277
- digits = digits.splice(0, MAX_DIGITS - 1);
21278
- exponent = numberOfIntegerDigits - 1;
21279
- numberOfIntegerDigits = 1;
21280
- }
21281
-
21282
- return { d: digits, e: exponent, i: numberOfIntegerDigits };
21283
- }
21284
-
21285
- /**
21286
- * Round the parsed number to the specified number of decimal places
21287
- * This function changed the parsedNumber in-place
21288
- */
21289
- function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) {
21290
- var digits = parsedNumber.d;
21291
- var fractionLen = digits.length - parsedNumber.i;
21292
-
21293
- // determine fractionSize if it is not specified; `+fractionSize` converts it to a number
21294
- fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize;
21295
-
21296
- // The index of the digit to where rounding is to occur
21297
- var roundAt = fractionSize + parsedNumber.i;
21298
- var digit = digits[roundAt];
21299
-
21300
- if (roundAt > 0) {
21301
- // Drop fractional digits beyond `roundAt`
21302
- digits.splice(Math.max(parsedNumber.i, roundAt));
21303
-
21304
- // Set non-fractional digits beyond `roundAt` to 0
21305
- for (var j = roundAt; j < digits.length; j++) {
21306
- digits[j] = 0;
21307
- }
21308
- } else {
21309
- // We rounded to zero so reset the parsedNumber
21310
- fractionLen = Math.max(0, fractionLen);
21311
- parsedNumber.i = 1;
21312
- digits.length = Math.max(1, roundAt = fractionSize + 1);
21313
- digits[0] = 0;
21314
- for (var i = 1; i < roundAt; i++) digits[i] = 0;
21315
- }
21316
-
21317
- if (digit >= 5) {
21318
- if (roundAt - 1 < 0) {
21319
- for (var k = 0; k > roundAt; k--) {
21320
- digits.unshift(0);
21321
- parsedNumber.i++;
21322
- }
21323
- digits.unshift(1);
21324
- parsedNumber.i++;
21325
- } else {
21326
- digits[roundAt - 1]++;
21327
- }
21328
- }
21329
-
21330
- // Pad out with zeros to get the required fraction length
21331
- for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);
21332
-
21333
-
21334
- // Do any carrying, e.g. a digit was rounded up to 10
21335
- var carry = digits.reduceRight(function(carry, d, i, digits) {
21336
- d = d + carry;
21337
- digits[i] = d % 10;
21338
- return Math.floor(d / 10);
21339
- }, 0);
21340
- if (carry) {
21341
- digits.unshift(carry);
21342
- parsedNumber.i++;
21343
- }
21344
- }
21345
-
21346
- /**
21347
- * Format a number into a string
21348
- * @param {number} number The number to format
21349
- * @param {{
21350
- * minFrac, // the minimum number of digits required in the fraction part of the number
21351
- * maxFrac, // the maximum number of digits required in the fraction part of the number
21352
- * gSize, // number of digits in each group of separated digits
21353
- * lgSize, // number of digits in the last group of digits before the decimal separator
21354
- * negPre, // the string to go in front of a negative number (e.g. `-` or `(`))
21355
- * posPre, // the string to go in front of a positive number
21356
- * negSuf, // the string to go after a negative number (e.g. `)`)
21357
- * posSuf // the string to go after a positive number
21358
- * }} pattern
21359
- * @param {string} groupSep The string to separate groups of number (e.g. `,`)
21360
- * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`)
21361
- * @param {[type]} fractionSize The size of the fractional part of the number
21362
- * @return {string} The number formatted as a string
21363
- */
21364
- function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
21365
-
21366
- if (!(isString(number) || isNumber(number)) || isNaN(number)) return '';
21367
-
21368
- var isInfinity = !isFinite(number);
21369
- var isZero = false;
21370
- var numStr = Math.abs(number) + '',
21371
- formattedText = '',
21372
- parsedNumber;
21373
-
21374
- if (isInfinity) {
21375
- formattedText = '\u221e';
21376
- } else {
21377
- parsedNumber = parse(numStr);
21378
-
21379
- roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac);
21380
-
21381
- var digits = parsedNumber.d;
21382
- var integerLen = parsedNumber.i;
21383
- var exponent = parsedNumber.e;
21384
- var decimals = [];
21385
- isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true);
21386
-
21387
- // pad zeros for small numbers
21388
- while (integerLen < 0) {
21389
- digits.unshift(0);
21390
- integerLen++;
21391
- }
21392
-
21393
- // extract decimals digits
21394
- if (integerLen > 0) {
21395
- decimals = digits.splice(integerLen, digits.length);
21396
- } else {
21397
- decimals = digits;
21398
- digits = [0];
21399
- }
21400
-
21401
- // format the integer digits with grouping separators
21402
- var groups = [];
21403
- if (digits.length >= pattern.lgSize) {
21404
- groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
21405
- }
21406
- while (digits.length > pattern.gSize) {
21407
- groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
21408
- }
21409
- if (digits.length) {
21410
- groups.unshift(digits.join(''));
21411
- }
21412
- formattedText = groups.join(groupSep);
21413
-
21414
- // append the decimal digits
21415
- if (decimals.length) {
21416
- formattedText += decimalSep + decimals.join('');
21417
- }
21418
-
21419
- if (exponent) {
21420
- formattedText += 'e+' + exponent;
21421
- }
21422
- }
21423
- if (number < 0 && !isZero) {
21424
- return pattern.negPre + formattedText + pattern.negSuf;
21425
- } else {
21426
- return pattern.posPre + formattedText + pattern.posSuf;
21427
- }
21428
- }
21429
-
21430
- function padNumber(num, digits, trim, negWrap) {
21431
- var neg = '';
21432
- if (num < 0 || (negWrap && num <= 0)) {
21433
- if (negWrap) {
21434
- num = -num + 1;
21435
- } else {
21436
- num = -num;
21437
- neg = '-';
21438
- }
21439
- }
21440
- num = '' + num;
21441
- while (num.length < digits) num = ZERO_CHAR + num;
21442
- if (trim) {
21443
- num = num.substr(num.length - digits);
21444
- }
21445
- return neg + num;
21446
- }
21447
-
21448
-
21449
- function dateGetter(name, size, offset, trim, negWrap) {
21450
- offset = offset || 0;
21451
- return function(date) {
21452
- var value = date['get' + name]();
21453
- if (offset > 0 || value > -offset) {
21454
- value += offset;
21455
- }
21456
- if (value === 0 && offset === -12) value = 12;
21457
- return padNumber(value, size, trim, negWrap);
21458
- };
21459
- }
21460
-
21461
- function dateStrGetter(name, shortForm, standAlone) {
21462
- return function(date, formats) {
21463
- var value = date['get' + name]();
21464
- var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : '');
21465
- var get = uppercase(propPrefix + name);
21466
-
21467
- return formats[get][value];
21468
- };
21469
- }
21470
-
21471
- function timeZoneGetter(date, formats, offset) {
21472
- var zone = -1 * offset;
21473
- var paddedZone = (zone >= 0) ? '+' : '';
21474
-
21475
- paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
21476
- padNumber(Math.abs(zone % 60), 2);
21477
-
21478
- return paddedZone;
21479
- }
21480
-
21481
- function getFirstThursdayOfYear(year) {
21482
- // 0 = index of January
21483
- var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
21484
- // 4 = index of Thursday (+1 to account for 1st = 5)
21485
- // 11 = index of *next* Thursday (+1 account for 1st = 12)
21486
- return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
21487
- }
21488
-
21489
- function getThursdayThisWeek(datetime) {
21490
- return new Date(datetime.getFullYear(), datetime.getMonth(),
21491
- // 4 = index of Thursday
21492
- datetime.getDate() + (4 - datetime.getDay()));
21493
- }
21494
-
21495
- function weekGetter(size) {
21496
- return function(date) {
21497
- var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
21498
- thisThurs = getThursdayThisWeek(date);
21499
-
21500
- var diff = +thisThurs - +firstThurs,
21501
- result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
21502
-
21503
- return padNumber(result, size);
21504
- };
21505
- }
21506
-
21507
- function ampmGetter(date, formats) {
21508
- return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
21509
- }
21510
-
21511
- function eraGetter(date, formats) {
21512
- return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
21513
- }
21514
-
21515
- function longEraGetter(date, formats) {
21516
- return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
21517
- }
21518
-
21519
- var DATE_FORMATS = {
21520
- yyyy: dateGetter('FullYear', 4, 0, false, true),
21521
- yy: dateGetter('FullYear', 2, 0, true, true),
21522
- y: dateGetter('FullYear', 1, 0, false, true),
21523
- MMMM: dateStrGetter('Month'),
21524
- MMM: dateStrGetter('Month', true),
21525
- MM: dateGetter('Month', 2, 1),
21526
- M: dateGetter('Month', 1, 1),
21527
- LLLL: dateStrGetter('Month', false, true),
21528
- dd: dateGetter('Date', 2),
21529
- d: dateGetter('Date', 1),
21530
- HH: dateGetter('Hours', 2),
21531
- H: dateGetter('Hours', 1),
21532
- hh: dateGetter('Hours', 2, -12),
21533
- h: dateGetter('Hours', 1, -12),
21534
- mm: dateGetter('Minutes', 2),
21535
- m: dateGetter('Minutes', 1),
21536
- ss: dateGetter('Seconds', 2),
21537
- s: dateGetter('Seconds', 1),
21538
- // while ISO 8601 requires fractions to be prefixed with `.` or `,`
21539
- // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
21540
- sss: dateGetter('Milliseconds', 3),
21541
- EEEE: dateStrGetter('Day'),
21542
- EEE: dateStrGetter('Day', true),
21543
- a: ampmGetter,
21544
- Z: timeZoneGetter,
21545
- ww: weekGetter(2),
21546
- w: weekGetter(1),
21547
- G: eraGetter,
21548
- GG: eraGetter,
21549
- GGG: eraGetter,
21550
- GGGG: longEraGetter
21551
- };
21552
-
21553
- var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,
21554
- NUMBER_STRING = /^-?\d+$/;
21555
-
21556
- /**
21557
- * @ngdoc filter
21558
- * @name date
21559
- * @kind function
21560
- *
21561
- * @description
21562
- * Formats `date` to a string based on the requested `format`.
21563
- *
21564
- * `format` string can be composed of the following elements:
21565
- *
21566
- * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
21567
- * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
21568
- * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
21569
- * * `'MMMM'`: Month in year (January-December)
21570
- * * `'MMM'`: Month in year (Jan-Dec)
21571
- * * `'MM'`: Month in year, padded (01-12)
21572
- * * `'M'`: Month in year (1-12)
21573
- * * `'LLLL'`: Stand-alone month in year (January-December)
21574
- * * `'dd'`: Day in month, padded (01-31)
21575
- * * `'d'`: Day in month (1-31)
21576
- * * `'EEEE'`: Day in Week,(Sunday-Saturday)
21577
- * * `'EEE'`: Day in Week, (Sun-Sat)
21578
- * * `'HH'`: Hour in day, padded (00-23)
21579
- * * `'H'`: Hour in day (0-23)
21580
- * * `'hh'`: Hour in AM/PM, padded (01-12)
21581
- * * `'h'`: Hour in AM/PM, (1-12)
21582
- * * `'mm'`: Minute in hour, padded (00-59)
21583
- * * `'m'`: Minute in hour (0-59)
21584
- * * `'ss'`: Second in minute, padded (00-59)
21585
- * * `'s'`: Second in minute (0-59)
21586
- * * `'sss'`: Millisecond in second, padded (000-999)
21587
- * * `'a'`: AM/PM marker
21588
- * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
21589
- * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
21590
- * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
21591
- * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
21592
- * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
21593
- *
21594
- * `format` string can also be one of the following predefined
21595
- * {@link guide/i18n localizable formats}:
21596
- *
21597
- * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
21598
- * (e.g. Sep 3, 2010 12:05:08 PM)
21599
- * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
21600
- * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
21601
- * (e.g. Friday, September 3, 2010)
21602
- * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
21603
- * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
21604
- * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
21605
- * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
21606
- * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
21607
- *
21608
- * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
21609
- * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
21610
- * (e.g. `"h 'o''clock'"`).
21611
- *
21612
- * Any other characters in the `format` string will be output as-is.
21613
- *
21614
- * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
21615
- * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
21616
- * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
21617
- * specified in the string input, the time is considered to be in the local timezone.
21618
- * @param {string=} format Formatting rules (see Description). If not specified,
21619
- * `mediumDate` is used.
21620
- * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
21621
- * continental US time zone abbreviations, but for general use, use a time zone offset, for
21622
- * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
21623
- * If not specified, the timezone of the browser will be used.
21624
- * @returns {string} Formatted string or the input if input is not recognized as date/millis.
21625
- *
21626
- * @example
21627
- <example name="filter-date">
21628
- <file name="index.html">
21629
- <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
21630
- <span>{{1288323623006 | date:'medium'}}</span><br>
21631
- <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
21632
- <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
21633
- <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
21634
- <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
21635
- <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
21636
- <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
21637
- </file>
21638
- <file name="protractor.js" type="protractor">
21639
- it('should format date', function() {
21640
- expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
21641
- toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
21642
- expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
21643
- toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/);
21644
- expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
21645
- toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
21646
- expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
21647
- toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
21648
- });
21649
- </file>
21650
- </example>
21651
- */
21652
- dateFilter.$inject = ['$locale'];
21653
- function dateFilter($locale) {
21654
-
21655
-
21656
- var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
21657
- // 1 2 3 4 5 6 7 8 9 10 11
21658
- function jsonStringToDate(string) {
21659
- var match;
21660
- if ((match = string.match(R_ISO8601_STR))) {
21661
- var date = new Date(0),
21662
- tzHour = 0,
21663
- tzMin = 0,
21664
- dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
21665
- timeSetter = match[8] ? date.setUTCHours : date.setHours;
21666
-
21667
- if (match[9]) {
21668
- tzHour = toInt(match[9] + match[10]);
21669
- tzMin = toInt(match[9] + match[11]);
21670
- }
21671
- dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
21672
- var h = toInt(match[4] || 0) - tzHour;
21673
- var m = toInt(match[5] || 0) - tzMin;
21674
- var s = toInt(match[6] || 0);
21675
- var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
21676
- timeSetter.call(date, h, m, s, ms);
21677
- return date;
21678
- }
21679
- return string;
21680
- }
21681
-
21682
-
21683
- return function(date, format, timezone) {
21684
- var text = '',
21685
- parts = [],
21686
- fn, match;
21687
-
21688
- format = format || 'mediumDate';
21689
- format = $locale.DATETIME_FORMATS[format] || format;
21690
- if (isString(date)) {
21691
- date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
21692
- }
21693
-
21694
- if (isNumber(date)) {
21695
- date = new Date(date);
21696
- }
21697
-
21698
- if (!isDate(date) || !isFinite(date.getTime())) {
21699
- return date;
21700
- }
21701
-
21702
- while (format) {
21703
- match = DATE_FORMATS_SPLIT.exec(format);
21704
- if (match) {
21705
- parts = concat(parts, match, 1);
21706
- format = parts.pop();
21707
- } else {
21708
- parts.push(format);
21709
- format = null;
21710
- }
21711
- }
21712
-
21713
- var dateTimezoneOffset = date.getTimezoneOffset();
21714
- if (timezone) {
21715
- dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
21716
- date = convertTimezoneToLocal(date, timezone, true);
21717
- }
21718
- forEach(parts, function(value) {
21719
- fn = DATE_FORMATS[value];
21720
- text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
21721
- : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
21722
- });
21723
-
21724
- return text;
21725
- };
21726
- }
21727
-
21728
-
21729
- /**
21730
- * @ngdoc filter
21731
- * @name json
21732
- * @kind function
21733
- *
21734
- * @description
21735
- * Allows you to convert a JavaScript object into JSON string.
21736
- *
21737
- * This filter is mostly useful for debugging. When using the double curly {{value}} notation
21738
- * the binding is automatically converted to JSON.
21739
- *
21740
- * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
21741
- * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
21742
- * @returns {string} JSON string.
21743
- *
21744
- *
21745
- * @example
21746
- <example name="filter-json">
21747
- <file name="index.html">
21748
- <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
21749
- <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
21750
- </file>
21751
- <file name="protractor.js" type="protractor">
21752
- it('should jsonify filtered objects', function() {
21753
- expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/);
21754
- expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/);
21755
- });
21756
- </file>
21757
- </example>
21758
- *
21759
- */
21760
- function jsonFilter() {
21761
- return function(object, spacing) {
21762
- if (isUndefined(spacing)) {
21763
- spacing = 2;
21764
- }
21765
- return toJson(object, spacing);
21766
- };
21767
- }
21768
-
21769
-
21770
- /**
21771
- * @ngdoc filter
21772
- * @name lowercase
21773
- * @kind function
21774
- * @description
21775
- * Converts string to lowercase.
21776
- *
21777
- * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example.
21778
- *
21779
- * @see angular.lowercase
21780
- */
21781
- var lowercaseFilter = valueFn(lowercase);
21782
-
21783
-
21784
- /**
21785
- * @ngdoc filter
21786
- * @name uppercase
21787
- * @kind function
21788
- * @description
21789
- * Converts string to uppercase.
21790
- * @example
21791
- <example module="uppercaseFilterExample" name="filter-uppercase">
21792
- <file name="index.html">
21793
- <script>
21794
- angular.module('uppercaseFilterExample', [])
21795
- .controller('ExampleController', ['$scope', function($scope) {
21796
- $scope.title = 'This is a title';
21797
- }]);
21798
- </script>
21799
- <div ng-controller="ExampleController">
21800
- <!-- This title should be formatted normally -->
21801
- <h1>{{title}}</h1>
21802
- <!-- This title should be capitalized -->
21803
- <h1>{{title | uppercase}}</h1>
21804
- </div>
21805
- </file>
21806
- </example>
21807
- */
21808
- var uppercaseFilter = valueFn(uppercase);
21809
-
21810
- /**
21811
- * @ngdoc filter
21812
- * @name limitTo
21813
- * @kind function
21814
- *
21815
- * @description
21816
- * Creates a new array or string containing only a specified number of elements. The elements are
21817
- * taken from either the beginning or the end of the source array, string or number, as specified by
21818
- * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported
21819
- * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input,
21820
- * it is converted to a string.
21821
- *
21822
- * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited.
21823
- * @param {string|number} limit - The length of the returned array or string. If the `limit` number
21824
- * is positive, `limit` number of items from the beginning of the source array/string are copied.
21825
- * If the number is negative, `limit` number of items from the end of the source array/string
21826
- * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
21827
- * the input will be returned unchanged.
21828
- * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index,
21829
- * `begin` indicates an offset from the end of `input`. Defaults to `0`.
21830
- * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had
21831
- * less than `limit` elements.
21832
- *
21833
- * @example
21834
- <example module="limitToExample" name="limit-to-filter">
21835
- <file name="index.html">
21836
- <script>
21837
- angular.module('limitToExample', [])
21838
- .controller('ExampleController', ['$scope', function($scope) {
21839
- $scope.numbers = [1,2,3,4,5,6,7,8,9];
21840
- $scope.letters = "abcdefghi";
21841
- $scope.longNumber = 2345432342;
21842
- $scope.numLimit = 3;
21843
- $scope.letterLimit = 3;
21844
- $scope.longNumberLimit = 3;
21845
- }]);
21846
- </script>
21847
- <div ng-controller="ExampleController">
21848
- <label>
21849
- Limit {{numbers}} to:
21850
- <input type="number" step="1" ng-model="numLimit">
21851
- </label>
21852
- <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
21853
- <label>
21854
- Limit {{letters}} to:
21855
- <input type="number" step="1" ng-model="letterLimit">
21856
- </label>
21857
- <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
21858
- <label>
21859
- Limit {{longNumber}} to:
21860
- <input type="number" step="1" ng-model="longNumberLimit">
21861
- </label>
21862
- <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
21863
- </div>
21864
- </file>
21865
- <file name="protractor.js" type="protractor">
21866
- var numLimitInput = element(by.model('numLimit'));
21867
- var letterLimitInput = element(by.model('letterLimit'));
21868
- var longNumberLimitInput = element(by.model('longNumberLimit'));
21869
- var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
21870
- var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
21871
- var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
21872
-
21873
- it('should limit the number array to first three items', function() {
21874
- expect(numLimitInput.getAttribute('value')).toBe('3');
21875
- expect(letterLimitInput.getAttribute('value')).toBe('3');
21876
- expect(longNumberLimitInput.getAttribute('value')).toBe('3');
21877
- expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
21878
- expect(limitedLetters.getText()).toEqual('Output letters: abc');
21879
- expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
21880
- });
21881
-
21882
- // There is a bug in safari and protractor that doesn't like the minus key
21883
- // it('should update the output when -3 is entered', function() {
21884
- // numLimitInput.clear();
21885
- // numLimitInput.sendKeys('-3');
21886
- // letterLimitInput.clear();
21887
- // letterLimitInput.sendKeys('-3');
21888
- // longNumberLimitInput.clear();
21889
- // longNumberLimitInput.sendKeys('-3');
21890
- // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
21891
- // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
21892
- // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
21893
- // });
21894
-
21895
- it('should not exceed the maximum size of input array', function() {
21896
- numLimitInput.clear();
21897
- numLimitInput.sendKeys('100');
21898
- letterLimitInput.clear();
21899
- letterLimitInput.sendKeys('100');
21900
- longNumberLimitInput.clear();
21901
- longNumberLimitInput.sendKeys('100');
21902
- expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
21903
- expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
21904
- expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
21905
- });
21906
- </file>
21907
- </example>
21908
- */
21909
- function limitToFilter() {
21910
- return function(input, limit, begin) {
21911
- if (Math.abs(Number(limit)) === Infinity) {
21912
- limit = Number(limit);
21913
- } else {
21914
- limit = toInt(limit);
21915
- }
21916
- if (isNumberNaN(limit)) return input;
21917
-
21918
- if (isNumber(input)) input = input.toString();
21919
- if (!isArrayLike(input)) return input;
21920
-
21921
- begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
21922
- begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
21923
-
21924
- if (limit >= 0) {
21925
- return sliceFn(input, begin, begin + limit);
21926
- } else {
21927
- if (begin === 0) {
21928
- return sliceFn(input, limit, input.length);
21929
- } else {
21930
- return sliceFn(input, Math.max(0, begin + limit), begin);
21931
- }
21932
- }
21933
- };
21934
- }
21935
-
21936
- function sliceFn(input, begin, end) {
21937
- if (isString(input)) return input.slice(begin, end);
21938
-
21939
- return slice.call(input, begin, end);
21940
- }
21941
-
21942
- /**
21943
- * @ngdoc filter
21944
- * @name orderBy
21945
- * @kind function
21946
- *
21947
- * @description
21948
- * Returns an array containing the items from the specified `collection`, ordered by a `comparator`
21949
- * function based on the values computed using the `expression` predicate.
21950
- *
21951
- * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in
21952
- * `[{id: 'bar'}, {id: 'foo'}]`.
21953
- *
21954
- * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray,
21955
- * String, etc).
21956
- *
21957
- * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker
21958
- * for the preceding one. The `expression` is evaluated against each item and the output is used
21959
- * for comparing with other items.
21960
- *
21961
- * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in
21962
- * ascending order.
21963
- *
21964
- * The comparison is done using the `comparator` function. If none is specified, a default, built-in
21965
- * comparator is used (see below for details - in a nutshell, it compares numbers numerically and
21966
- * strings alphabetically).
21967
- *
21968
- * ### Under the hood
21969
- *
21970
- * Ordering the specified `collection` happens in two phases:
21971
- *
21972
- * 1. All items are passed through the predicate (or predicates), and the returned values are saved
21973
- * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed
21974
- * through a predicate that extracts the value of the `label` property, would be transformed to:
21975
- * ```
21976
- * {
21977
- * value: 'foo',
21978
- * type: 'string',
21979
- * index: ...
21980
- * }
21981
- * ```
21982
- * 2. The comparator function is used to sort the items, based on the derived values, types and
21983
- * indices.
21984
- *
21985
- * If you use a custom comparator, it will be called with pairs of objects of the form
21986
- * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal
21987
- * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the
21988
- * second, or `1` otherwise.
21989
- *
21990
- * In order to ensure that the sorting will be deterministic across platforms, if none of the
21991
- * specified predicates can distinguish between two items, `orderBy` will automatically introduce a
21992
- * dummy predicate that returns the item's index as `value`.
21993
- * (If you are using a custom comparator, make sure it can handle this predicate as well.)
21994
- *
21995
- * If a custom comparator still can't distinguish between two items, then they will be sorted based
21996
- * on their index using the built-in comparator.
21997
- *
21998
- * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted
21999
- * value for an item, `orderBy` will try to convert that object to a primitive value, before passing
22000
- * it to the comparator. The following rules govern the conversion:
22001
- *
22002
- * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be
22003
- * used instead.<br />
22004
- * (If the object has a `valueOf()` method that returns another object, then the returned object
22005
- * will be used in subsequent steps.)
22006
- * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that
22007
- * returns a primitive, its return value will be used instead.<br />
22008
- * (If the object has a `toString()` method that returns another object, then the returned object
22009
- * will be used in subsequent steps.)
22010
- * 3. No conversion; the object itself is used.
22011
- *
22012
- * ### The default comparator
22013
- *
22014
- * The default, built-in comparator should be sufficient for most usecases. In short, it compares
22015
- * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
22016
- * using their index in the original collection, and sorts values of different types by type.
22017
- *
22018
- * More specifically, it follows these steps to determine the relative order of items:
22019
- *
22020
- * 1. If the compared values are of different types, compare the types themselves alphabetically.
22021
- * 2. If both values are of type `string`, compare them alphabetically in a case- and
22022
- * locale-insensitive way.
22023
- * 3. If both values are objects, compare their indices instead.
22024
- * 4. Otherwise, return:
22025
- * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`).
22026
- * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator).
22027
- * - `1`, otherwise.
22028
- *
22029
- * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
22030
- * saved as numbers and not strings.
22031
- * **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e.
22032
- * `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to
22033
- * other values.
22034
- *
22035
- * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
22036
- * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
22037
- * predicates) to be used by the comparator to determine the order of elements.
22038
- *
22039
- * Can be one of:
22040
- *
22041
- * - `Function`: A getter function. This function will be called with each item as argument and
22042
- * the return value will be used for sorting.
22043
- * - `string`: An Angular expression. This expression will be evaluated against each item and the
22044
- * result will be used for sorting. For example, use `'label'` to sort by a property called
22045
- * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label`
22046
- * property.<br />
22047
- * (The result of a constant expression is interpreted as a property name to be used for
22048
- * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a
22049
- * property called `special name`.)<br />
22050
- * An expression can be optionally prefixed with `+` or `-` to control the sorting direction,
22051
- * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided,
22052
- * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons.
22053
- * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the
22054
- * relative order of two items, the next predicate is used as a tie-breaker.
22055
- *
22056
- * **Note:** If the predicate is missing or empty then it defaults to `'+'`.
22057
- *
22058
- * @param {boolean=} reverse - If `true`, reverse the sorting order.
22059
- * @param {(Function)=} comparator - The comparator function used to determine the relative order of
22060
- * value pairs. If omitted, the built-in comparator will be used.
22061
- *
22062
- * @returns {Array} - The sorted array.
22063
- *
22064
- *
22065
- * @example
22066
- * ### Ordering a table with `ngRepeat`
22067
- *
22068
- * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by
22069
- * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means
22070
- * it defaults to the built-in comparator.
22071
- *
22072
- <example name="orderBy-static" module="orderByExample1">
22073
- <file name="index.html">
22074
- <div ng-controller="ExampleController">
22075
- <table class="friends">
22076
- <tr>
22077
- <th>Name</th>
22078
- <th>Phone Number</th>
22079
- <th>Age</th>
22080
- </tr>
22081
- <tr ng-repeat="friend in friends | orderBy:'-age'">
22082
- <td>{{friend.name}}</td>
22083
- <td>{{friend.phone}}</td>
22084
- <td>{{friend.age}}</td>
22085
- </tr>
22086
- </table>
22087
- </div>
22088
- </file>
22089
- <file name="script.js">
22090
- angular.module('orderByExample1', [])
22091
- .controller('ExampleController', ['$scope', function($scope) {
22092
- $scope.friends = [
22093
- {name: 'John', phone: '555-1212', age: 10},
22094
- {name: 'Mary', phone: '555-9876', age: 19},
22095
- {name: 'Mike', phone: '555-4321', age: 21},
22096
- {name: 'Adam', phone: '555-5678', age: 35},
22097
- {name: 'Julie', phone: '555-8765', age: 29}
22098
- ];
22099
- }]);
22100
- </file>
22101
- <file name="style.css">
22102
- .friends {
22103
- border-collapse: collapse;
22104
- }
22105
-
22106
- .friends th {
22107
- border-bottom: 1px solid;
22108
- }
22109
- .friends td, .friends th {
22110
- border-left: 1px solid;
22111
- padding: 5px 10px;
22112
- }
22113
- .friends td:first-child, .friends th:first-child {
22114
- border-left: none;
22115
- }
22116
- </file>
22117
- <file name="protractor.js" type="protractor">
22118
- // Element locators
22119
- var names = element.all(by.repeater('friends').column('friend.name'));
22120
-
22121
- it('should sort friends by age in reverse order', function() {
22122
- expect(names.get(0).getText()).toBe('Adam');
22123
- expect(names.get(1).getText()).toBe('Julie');
22124
- expect(names.get(2).getText()).toBe('Mike');
22125
- expect(names.get(3).getText()).toBe('Mary');
22126
- expect(names.get(4).getText()).toBe('John');
22127
- });
22128
- </file>
22129
- </example>
22130
- * <hr />
22131
- *
22132
- * @example
22133
- * ### Changing parameters dynamically
22134
- *
22135
- * All parameters can be changed dynamically. The next example shows how you can make the columns of
22136
- * a table sortable, by binding the `expression` and `reverse` parameters to scope properties.
22137
- *
22138
- <example name="orderBy-dynamic" module="orderByExample2">
22139
- <file name="index.html">
22140
- <div ng-controller="ExampleController">
22141
- <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
22142
- <hr/>
22143
- <button ng-click="propertyName = null; reverse = false">Set to unsorted</button>
22144
- <hr/>
22145
- <table class="friends">
22146
- <tr>
22147
- <th>
22148
- <button ng-click="sortBy('name')">Name</button>
22149
- <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
22150
- </th>
22151
- <th>
22152
- <button ng-click="sortBy('phone')">Phone Number</button>
22153
- <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
22154
- </th>
22155
- <th>
22156
- <button ng-click="sortBy('age')">Age</button>
22157
- <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
22158
- </th>
22159
- </tr>
22160
- <tr ng-repeat="friend in friends | orderBy:propertyName:reverse">
22161
- <td>{{friend.name}}</td>
22162
- <td>{{friend.phone}}</td>
22163
- <td>{{friend.age}}</td>
22164
- </tr>
22165
- </table>
22166
- </div>
22167
- </file>
22168
- <file name="script.js">
22169
- angular.module('orderByExample2', [])
22170
- .controller('ExampleController', ['$scope', function($scope) {
22171
- var friends = [
22172
- {name: 'John', phone: '555-1212', age: 10},
22173
- {name: 'Mary', phone: '555-9876', age: 19},
22174
- {name: 'Mike', phone: '555-4321', age: 21},
22175
- {name: 'Adam', phone: '555-5678', age: 35},
22176
- {name: 'Julie', phone: '555-8765', age: 29}
22177
- ];
22178
-
22179
- $scope.propertyName = 'age';
22180
- $scope.reverse = true;
22181
- $scope.friends = friends;
22182
-
22183
- $scope.sortBy = function(propertyName) {
22184
- $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
22185
- $scope.propertyName = propertyName;
22186
- };
22187
- }]);
22188
- </file>
22189
- <file name="style.css">
22190
- .friends {
22191
- border-collapse: collapse;
22192
- }
22193
-
22194
- .friends th {
22195
- border-bottom: 1px solid;
22196
- }
22197
- .friends td, .friends th {
22198
- border-left: 1px solid;
22199
- padding: 5px 10px;
22200
- }
22201
- .friends td:first-child, .friends th:first-child {
22202
- border-left: none;
22203
- }
22204
-
22205
- .sortorder:after {
22206
- content: '\25b2'; // BLACK UP-POINTING TRIANGLE
22207
- }
22208
- .sortorder.reverse:after {
22209
- content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
22210
- }
22211
- </file>
22212
- <file name="protractor.js" type="protractor">
22213
- // Element locators
22214
- var unsortButton = element(by.partialButtonText('unsorted'));
22215
- var nameHeader = element(by.partialButtonText('Name'));
22216
- var phoneHeader = element(by.partialButtonText('Phone'));
22217
- var ageHeader = element(by.partialButtonText('Age'));
22218
- var firstName = element(by.repeater('friends').column('friend.name').row(0));
22219
- var lastName = element(by.repeater('friends').column('friend.name').row(4));
22220
-
22221
- it('should sort friends by some property, when clicking on the column header', function() {
22222
- expect(firstName.getText()).toBe('Adam');
22223
- expect(lastName.getText()).toBe('John');
22224
-
22225
- phoneHeader.click();
22226
- expect(firstName.getText()).toBe('John');
22227
- expect(lastName.getText()).toBe('Mary');
22228
-
22229
- nameHeader.click();
22230
- expect(firstName.getText()).toBe('Adam');
22231
- expect(lastName.getText()).toBe('Mike');
22232
-
22233
- ageHeader.click();
22234
- expect(firstName.getText()).toBe('John');
22235
- expect(lastName.getText()).toBe('Adam');
22236
- });
22237
-
22238
- it('should sort friends in reverse order, when clicking on the same column', function() {
22239
- expect(firstName.getText()).toBe('Adam');
22240
- expect(lastName.getText()).toBe('John');
22241
-
22242
- ageHeader.click();
22243
- expect(firstName.getText()).toBe('John');
22244
- expect(lastName.getText()).toBe('Adam');
22245
-
22246
- ageHeader.click();
22247
- expect(firstName.getText()).toBe('Adam');
22248
- expect(lastName.getText()).toBe('John');
22249
- });
22250
-
22251
- it('should restore the original order, when clicking "Set to unsorted"', function() {
22252
- expect(firstName.getText()).toBe('Adam');
22253
- expect(lastName.getText()).toBe('John');
22254
-
22255
- unsortButton.click();
22256
- expect(firstName.getText()).toBe('John');
22257
- expect(lastName.getText()).toBe('Julie');
22258
- });
22259
- </file>
22260
- </example>
22261
- * <hr />
22262
- *
22263
- * @example
22264
- * ### Using `orderBy` inside a controller
22265
- *
22266
- * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and
22267
- * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory
22268
- * and retrieve the `orderBy` filter with `$filter('orderBy')`.)
22269
- *
22270
- <example name="orderBy-call-manually" module="orderByExample3">
22271
- <file name="index.html">
22272
- <div ng-controller="ExampleController">
22273
- <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
22274
- <hr/>
22275
- <button ng-click="sortBy(null)">Set to unsorted</button>
22276
- <hr/>
22277
- <table class="friends">
22278
- <tr>
22279
- <th>
22280
- <button ng-click="sortBy('name')">Name</button>
22281
- <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
22282
- </th>
22283
- <th>
22284
- <button ng-click="sortBy('phone')">Phone Number</button>
22285
- <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
22286
- </th>
22287
- <th>
22288
- <button ng-click="sortBy('age')">Age</button>
22289
- <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
22290
- </th>
22291
- </tr>
22292
- <tr ng-repeat="friend in friends">
22293
- <td>{{friend.name}}</td>
22294
- <td>{{friend.phone}}</td>
22295
- <td>{{friend.age}}</td>
22296
- </tr>
22297
- </table>
22298
- </div>
22299
- </file>
22300
- <file name="script.js">
22301
- angular.module('orderByExample3', [])
22302
- .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) {
22303
- var friends = [
22304
- {name: 'John', phone: '555-1212', age: 10},
22305
- {name: 'Mary', phone: '555-9876', age: 19},
22306
- {name: 'Mike', phone: '555-4321', age: 21},
22307
- {name: 'Adam', phone: '555-5678', age: 35},
22308
- {name: 'Julie', phone: '555-8765', age: 29}
22309
- ];
22310
-
22311
- $scope.propertyName = 'age';
22312
- $scope.reverse = true;
22313
- $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
22314
-
22315
- $scope.sortBy = function(propertyName) {
22316
- $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName)
22317
- ? !$scope.reverse : false;
22318
- $scope.propertyName = propertyName;
22319
- $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
22320
- };
22321
- }]);
22322
- </file>
22323
- <file name="style.css">
22324
- .friends {
22325
- border-collapse: collapse;
22326
- }
22327
-
22328
- .friends th {
22329
- border-bottom: 1px solid;
22330
- }
22331
- .friends td, .friends th {
22332
- border-left: 1px solid;
22333
- padding: 5px 10px;
22334
- }
22335
- .friends td:first-child, .friends th:first-child {
22336
- border-left: none;
22337
- }
22338
-
22339
- .sortorder:after {
22340
- content: '\25b2'; // BLACK UP-POINTING TRIANGLE
22341
- }
22342
- .sortorder.reverse:after {
22343
- content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE
22344
- }
22345
- </file>
22346
- <file name="protractor.js" type="protractor">
22347
- // Element locators
22348
- var unsortButton = element(by.partialButtonText('unsorted'));
22349
- var nameHeader = element(by.partialButtonText('Name'));
22350
- var phoneHeader = element(by.partialButtonText('Phone'));
22351
- var ageHeader = element(by.partialButtonText('Age'));
22352
- var firstName = element(by.repeater('friends').column('friend.name').row(0));
22353
- var lastName = element(by.repeater('friends').column('friend.name').row(4));
22354
-
22355
- it('should sort friends by some property, when clicking on the column header', function() {
22356
- expect(firstName.getText()).toBe('Adam');
22357
- expect(lastName.getText()).toBe('John');
22358
-
22359
- phoneHeader.click();
22360
- expect(firstName.getText()).toBe('John');
22361
- expect(lastName.getText()).toBe('Mary');
22362
-
22363
- nameHeader.click();
22364
- expect(firstName.getText()).toBe('Adam');
22365
- expect(lastName.getText()).toBe('Mike');
22366
-
22367
- ageHeader.click();
22368
- expect(firstName.getText()).toBe('John');
22369
- expect(lastName.getText()).toBe('Adam');
22370
- });
22371
-
22372
- it('should sort friends in reverse order, when clicking on the same column', function() {
22373
- expect(firstName.getText()).toBe('Adam');
22374
- expect(lastName.getText()).toBe('John');
22375
-
22376
- ageHeader.click();
22377
- expect(firstName.getText()).toBe('John');
22378
- expect(lastName.getText()).toBe('Adam');
22379
-
22380
- ageHeader.click();
22381
- expect(firstName.getText()).toBe('Adam');
22382
- expect(lastName.getText()).toBe('John');
22383
- });
22384
-
22385
- it('should restore the original order, when clicking "Set to unsorted"', function() {
22386
- expect(firstName.getText()).toBe('Adam');
22387
- expect(lastName.getText()).toBe('John');
22388
-
22389
- unsortButton.click();
22390
- expect(firstName.getText()).toBe('John');
22391
- expect(lastName.getText()).toBe('Julie');
22392
- });
22393
- </file>
22394
- </example>
22395
- * <hr />
22396
- *
22397
- * @example
22398
- * ### Using a custom comparator
22399
- *
22400
- * If you have very specific requirements about the way items are sorted, you can pass your own
22401
- * comparator function. For example, you might need to compare some strings in a locale-sensitive
22402
- * way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
22403
- * argument - passing `false` retains the default sorting order, i.e. ascending.)
22404
- *
22405
- <example name="orderBy-custom-comparator" module="orderByExample4">
22406
- <file name="index.html">
22407
- <div ng-controller="ExampleController">
22408
- <div class="friends-container custom-comparator">
22409
- <h3>Locale-sensitive Comparator</h3>
22410
- <table class="friends">
22411
- <tr>
22412
- <th>Name</th>
22413
- <th>Favorite Letter</th>
22414
- </tr>
22415
- <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator">
22416
- <td>{{friend.name}}</td>
22417
- <td>{{friend.favoriteLetter}}</td>
22418
- </tr>
22419
- </table>
22420
- </div>
22421
- <div class="friends-container default-comparator">
22422
- <h3>Default Comparator</h3>
22423
- <table class="friends">
22424
- <tr>
22425
- <th>Name</th>
22426
- <th>Favorite Letter</th>
22427
- </tr>
22428
- <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'">
22429
- <td>{{friend.name}}</td>
22430
- <td>{{friend.favoriteLetter}}</td>
22431
- </tr>
22432
- </table>
22433
- </div>
22434
- </div>
22435
- </file>
22436
- <file name="script.js">
22437
- angular.module('orderByExample4', [])
22438
- .controller('ExampleController', ['$scope', function($scope) {
22439
- $scope.friends = [
22440
- {name: 'John', favoriteLetter: 'Ä'},
22441
- {name: 'Mary', favoriteLetter: 'Ü'},
22442
- {name: 'Mike', favoriteLetter: 'Ö'},
22443
- {name: 'Adam', favoriteLetter: 'H'},
22444
- {name: 'Julie', favoriteLetter: 'Z'}
22445
- ];
22446
-
22447
- $scope.localeSensitiveComparator = function(v1, v2) {
22448
- // If we don't get strings, just compare by index
22449
- if (v1.type !== 'string' || v2.type !== 'string') {
22450
- return (v1.index < v2.index) ? -1 : 1;
22451
- }
22452
-
22453
- // Compare strings alphabetically, taking locale into account
22454
- return v1.value.localeCompare(v2.value);
22455
- };
22456
- }]);
22457
- </file>
22458
- <file name="style.css">
22459
- .friends-container {
22460
- display: inline-block;
22461
- margin: 0 30px;
22462
- }
22463
-
22464
- .friends {
22465
- border-collapse: collapse;
22466
- }
22467
-
22468
- .friends th {
22469
- border-bottom: 1px solid;
22470
- }
22471
- .friends td, .friends th {
22472
- border-left: 1px solid;
22473
- padding: 5px 10px;
22474
- }
22475
- .friends td:first-child, .friends th:first-child {
22476
- border-left: none;
22477
- }
22478
- </file>
22479
- <file name="protractor.js" type="protractor">
22480
- // Element locators
22481
- var container = element(by.css('.custom-comparator'));
22482
- var names = container.all(by.repeater('friends').column('friend.name'));
22483
-
22484
- it('should sort friends by favorite letter (in correct alphabetical order)', function() {
22485
- expect(names.get(0).getText()).toBe('John');
22486
- expect(names.get(1).getText()).toBe('Adam');
22487
- expect(names.get(2).getText()).toBe('Mike');
22488
- expect(names.get(3).getText()).toBe('Mary');
22489
- expect(names.get(4).getText()).toBe('Julie');
22490
- });
22491
- </file>
22492
- </example>
22493
- *
22494
- */
22495
- orderByFilter.$inject = ['$parse'];
22496
- function orderByFilter($parse) {
22497
- return function(array, sortPredicate, reverseOrder, compareFn) {
22498
-
22499
- if (array == null) return array;
22500
- if (!isArrayLike(array)) {
22501
- throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array);
22502
- }
22503
-
22504
- if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
22505
- if (sortPredicate.length === 0) { sortPredicate = ['+']; }
22506
-
22507
- var predicates = processPredicates(sortPredicate);
22508
-
22509
- var descending = reverseOrder ? -1 : 1;
22510
-
22511
- // Define the `compare()` function. Use a default comparator if none is specified.
22512
- var compare = isFunction(compareFn) ? compareFn : defaultCompare;
22513
-
22514
- // The next three lines are a version of a Swartzian Transform idiom from Perl
22515
- // (sometimes called the Decorate-Sort-Undecorate idiom)
22516
- // See https://en.wikipedia.org/wiki/Schwartzian_transform
22517
- var compareValues = Array.prototype.map.call(array, getComparisonObject);
22518
- compareValues.sort(doComparison);
22519
- array = compareValues.map(function(item) { return item.value; });
22520
-
22521
- return array;
22522
-
22523
- function getComparisonObject(value, index) {
22524
- // NOTE: We are adding an extra `tieBreaker` value based on the element's index.
22525
- // This will be used to keep the sort stable when none of the input predicates can
22526
- // distinguish between two elements.
22527
- return {
22528
- value: value,
22529
- tieBreaker: {value: index, type: 'number', index: index},
22530
- predicateValues: predicates.map(function(predicate) {
22531
- return getPredicateValue(predicate.get(value), index);
22532
- })
22533
- };
22534
- }
22535
-
22536
- function doComparison(v1, v2) {
22537
- for (var i = 0, ii = predicates.length; i < ii; i++) {
22538
- var result = compare(v1.predicateValues[i], v2.predicateValues[i]);
22539
- if (result) {
22540
- return result * predicates[i].descending * descending;
22541
- }
22542
- }
22543
-
22544
- return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending;
22545
- }
22546
- };
22547
-
22548
- function processPredicates(sortPredicates) {
22549
- return sortPredicates.map(function(predicate) {
22550
- var descending = 1, get = identity;
22551
-
22552
- if (isFunction(predicate)) {
22553
- get = predicate;
22554
- } else if (isString(predicate)) {
22555
- if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) {
22556
- descending = predicate.charAt(0) === '-' ? -1 : 1;
22557
- predicate = predicate.substring(1);
22558
- }
22559
- if (predicate !== '') {
22560
- get = $parse(predicate);
22561
- if (get.constant) {
22562
- var key = get();
22563
- get = function(value) { return value[key]; };
22564
- }
22565
- }
22566
- }
22567
- return {get: get, descending: descending};
22568
- });
22569
- }
22570
-
22571
- function isPrimitive(value) {
22572
- switch (typeof value) {
22573
- case 'number': /* falls through */
22574
- case 'boolean': /* falls through */
22575
- case 'string':
22576
- return true;
22577
- default:
22578
- return false;
22579
- }
22580
- }
22581
-
22582
- function objectValue(value) {
22583
- // If `valueOf` is a valid function use that
22584
- if (isFunction(value.valueOf)) {
22585
- value = value.valueOf();
22586
- if (isPrimitive(value)) return value;
22587
- }
22588
- // If `toString` is a valid function and not the one from `Object.prototype` use that
22589
- if (hasCustomToString(value)) {
22590
- value = value.toString();
22591
- if (isPrimitive(value)) return value;
22592
- }
22593
-
22594
- return value;
22595
- }
22596
-
22597
- function getPredicateValue(value, index) {
22598
- var type = typeof value;
22599
- if (value === null) {
22600
- type = 'string';
22601
- value = 'null';
22602
- } else if (type === 'object') {
22603
- value = objectValue(value);
22604
- }
22605
- return {value: value, type: type, index: index};
22606
- }
22607
-
22608
- function defaultCompare(v1, v2) {
22609
- var result = 0;
22610
- var type1 = v1.type;
22611
- var type2 = v2.type;
22612
-
22613
- if (type1 === type2) {
22614
- var value1 = v1.value;
22615
- var value2 = v2.value;
22616
-
22617
- if (type1 === 'string') {
22618
- // Compare strings case-insensitively
22619
- value1 = value1.toLowerCase();
22620
- value2 = value2.toLowerCase();
22621
- } else if (type1 === 'object') {
22622
- // For basic objects, use the position of the object
22623
- // in the collection instead of the value
22624
- if (isObject(value1)) value1 = v1.index;
22625
- if (isObject(value2)) value2 = v2.index;
22626
- }
22627
-
22628
- if (value1 !== value2) {
22629
- result = value1 < value2 ? -1 : 1;
22630
- }
22631
- } else {
22632
- result = type1 < type2 ? -1 : 1;
22633
- }
22634
-
22635
- return result;
22636
- }
22637
- }
22638
-
22639
- function ngDirective(directive) {
22640
- if (isFunction(directive)) {
22641
- directive = {
22642
- link: directive
22643
- };
22644
- }
22645
- directive.restrict = directive.restrict || 'AC';
22646
- return valueFn(directive);
22647
- }
22648
-
22649
- /**
22650
- * @ngdoc directive
22651
- * @name a
22652
- * @restrict E
22653
- *
22654
- * @description
22655
- * Modifies the default behavior of the html a tag so that the default action is prevented when
22656
- * the href attribute is empty.
22657
- *
22658
- * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive.
22659
- */
22660
- var htmlAnchorDirective = valueFn({
22661
- restrict: 'E',
22662
- compile: function(element, attr) {
22663
- if (!attr.href && !attr.xlinkHref) {
22664
- return function(scope, element) {
22665
- // If the linked element is not an anchor tag anymore, do nothing
22666
- if (element[0].nodeName.toLowerCase() !== 'a') return;
22667
-
22668
- // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
22669
- var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
22670
- 'xlink:href' : 'href';
22671
- element.on('click', function(event) {
22672
- // if we have no href url, then don't navigate anywhere.
22673
- if (!element.attr(href)) {
22674
- event.preventDefault();
22675
- }
22676
- });
22677
- };
22678
- }
22679
- }
22680
- });
22681
-
22682
- /**
22683
- * @ngdoc directive
22684
- * @name ngHref
22685
- * @restrict A
22686
- * @priority 99
22687
- *
22688
- * @description
22689
- * Using Angular markup like `{{hash}}` in an href attribute will
22690
- * make the link go to the wrong URL if the user clicks it before
22691
- * Angular has a chance to replace the `{{hash}}` markup with its
22692
- * value. Until Angular replaces the markup the link will be broken
22693
- * and will most likely return a 404 error. The `ngHref` directive
22694
- * solves this problem.
22695
- *
22696
- * The wrong way to write it:
22697
- * ```html
22698
- * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
22699
- * ```
22700
- *
22701
- * The correct way to write it:
22702
- * ```html
22703
- * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
22704
- * ```
22705
- *
22706
- * @element A
22707
- * @param {template} ngHref any string which can contain `{{}}` markup.
22708
- *
22709
- * @example
22710
- * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
22711
- * in links and their different behaviors:
22712
- <example name="ng-href">
22713
- <file name="index.html">
22714
- <input ng-model="value" /><br />
22715
- <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
22716
- <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
22717
- <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
22718
- <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
22719
- <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
22720
- <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
22721
- </file>
22722
- <file name="protractor.js" type="protractor">
22723
- it('should execute ng-click but not reload when href without value', function() {
22724
- element(by.id('link-1')).click();
22725
- expect(element(by.model('value')).getAttribute('value')).toEqual('1');
22726
- expect(element(by.id('link-1')).getAttribute('href')).toBe('');
22727
- });
22728
-
22729
- it('should execute ng-click but not reload when href empty string', function() {
22730
- element(by.id('link-2')).click();
22731
- expect(element(by.model('value')).getAttribute('value')).toEqual('2');
22732
- expect(element(by.id('link-2')).getAttribute('href')).toBe('');
22733
- });
22734
-
22735
- it('should execute ng-click and change url when ng-href specified', function() {
22736
- expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
22737
-
22738
- element(by.id('link-3')).click();
22739
-
22740
- // At this point, we navigate away from an Angular page, so we need
22741
- // to use browser.driver to get the base webdriver.
22742
-
22743
- browser.wait(function() {
22744
- return browser.driver.getCurrentUrl().then(function(url) {
22745
- return url.match(/\/123$/);
22746
- });
22747
- }, 5000, 'page should navigate to /123');
22748
- });
22749
-
22750
- it('should execute ng-click but not reload when href empty string and name specified', function() {
22751
- element(by.id('link-4')).click();
22752
- expect(element(by.model('value')).getAttribute('value')).toEqual('4');
22753
- expect(element(by.id('link-4')).getAttribute('href')).toBe('');
22754
- });
22755
-
22756
- it('should execute ng-click but not reload when no href but name specified', function() {
22757
- element(by.id('link-5')).click();
22758
- expect(element(by.model('value')).getAttribute('value')).toEqual('5');
22759
- expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
22760
- });
22761
-
22762
- it('should only change url when only ng-href', function() {
22763
- element(by.model('value')).clear();
22764
- element(by.model('value')).sendKeys('6');
22765
- expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
22766
-
22767
- element(by.id('link-6')).click();
22768
-
22769
- // At this point, we navigate away from an Angular page, so we need
22770
- // to use browser.driver to get the base webdriver.
22771
- browser.wait(function() {
22772
- return browser.driver.getCurrentUrl().then(function(url) {
22773
- return url.match(/\/6$/);
22774
- });
22775
- }, 5000, 'page should navigate to /6');
22776
- });
22777
- </file>
22778
- </example>
22779
- */
22780
-
22781
- /**
22782
- * @ngdoc directive
22783
- * @name ngSrc
22784
- * @restrict A
22785
- * @priority 99
22786
- *
22787
- * @description
22788
- * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
22789
- * work right: The browser will fetch from the URL with the literal
22790
- * text `{{hash}}` until Angular replaces the expression inside
22791
- * `{{hash}}`. The `ngSrc` directive solves this problem.
22792
- *
22793
- * The buggy way to write it:
22794
- * ```html
22795
- * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
22796
- * ```
22797
- *
22798
- * The correct way to write it:
22799
- * ```html
22800
- * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
22801
- * ```
22802
- *
22803
- * @element IMG
22804
- * @param {template} ngSrc any string which can contain `{{}}` markup.
22805
- */
22806
-
22807
- /**
22808
- * @ngdoc directive
22809
- * @name ngSrcset
22810
- * @restrict A
22811
- * @priority 99
22812
- *
22813
- * @description
22814
- * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
22815
- * work right: The browser will fetch from the URL with the literal
22816
- * text `{{hash}}` until Angular replaces the expression inside
22817
- * `{{hash}}`. The `ngSrcset` directive solves this problem.
22818
- *
22819
- * The buggy way to write it:
22820
- * ```html
22821
- * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
22822
- * ```
22823
- *
22824
- * The correct way to write it:
22825
- * ```html
22826
- * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
22827
- * ```
22828
- *
22829
- * @element IMG
22830
- * @param {template} ngSrcset any string which can contain `{{}}` markup.
22831
- */
22832
-
22833
- /**
22834
- * @ngdoc directive
22835
- * @name ngDisabled
22836
- * @restrict A
22837
- * @priority 100
22838
- *
22839
- * @description
22840
- *
22841
- * This directive sets the `disabled` attribute on the element (typically a form control,
22842
- * e.g. `input`, `button`, `select` etc.) if the
22843
- * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
22844
- *
22845
- * A special directive is necessary because we cannot use interpolation inside the `disabled`
22846
- * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22847
- *
22848
- * @example
22849
- <example name="ng-disabled">
22850
- <file name="index.html">
22851
- <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
22852
- <button ng-model="button" ng-disabled="checked">Button</button>
22853
- </file>
22854
- <file name="protractor.js" type="protractor">
22855
- it('should toggle button', function() {
22856
- expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
22857
- element(by.model('checked')).click();
22858
- expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
22859
- });
22860
- </file>
22861
- </example>
22862
- *
22863
- * @element INPUT
22864
- * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
22865
- * then the `disabled` attribute will be set on the element
22866
- */
22867
-
22868
-
22869
- /**
22870
- * @ngdoc directive
22871
- * @name ngChecked
22872
- * @restrict A
22873
- * @priority 100
22874
- *
22875
- * @description
22876
- * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
22877
- *
22878
- * Note that this directive should not be used together with {@link ngModel `ngModel`},
22879
- * as this can lead to unexpected behavior.
22880
- *
22881
- * A special directive is necessary because we cannot use interpolation inside the `checked`
22882
- * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22883
- *
22884
- * @example
22885
- <example name="ng-checked">
22886
- <file name="index.html">
22887
- <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
22888
- <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
22889
- </file>
22890
- <file name="protractor.js" type="protractor">
22891
- it('should check both checkBoxes', function() {
22892
- expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
22893
- element(by.model('master')).click();
22894
- expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
22895
- });
22896
- </file>
22897
- </example>
22898
- *
22899
- * @element INPUT
22900
- * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
22901
- * then the `checked` attribute will be set on the element
22902
- */
22903
-
22904
-
22905
- /**
22906
- * @ngdoc directive
22907
- * @name ngReadonly
22908
- * @restrict A
22909
- * @priority 100
22910
- *
22911
- * @description
22912
- *
22913
- * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy.
22914
- * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on
22915
- * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information.
22916
- *
22917
- * A special directive is necessary because we cannot use interpolation inside the `readonly`
22918
- * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22919
- *
22920
- * @example
22921
- <example name="ng-readonly">
22922
- <file name="index.html">
22923
- <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
22924
- <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
22925
- </file>
22926
- <file name="protractor.js" type="protractor">
22927
- it('should toggle readonly attr', function() {
22928
- expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
22929
- element(by.model('checked')).click();
22930
- expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
22931
- });
22932
- </file>
22933
- </example>
22934
- *
22935
- * @element INPUT
22936
- * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
22937
- * then special attribute "readonly" will be set on the element
22938
- */
22939
-
22940
-
22941
- /**
22942
- * @ngdoc directive
22943
- * @name ngSelected
22944
- * @restrict A
22945
- * @priority 100
22946
- *
22947
- * @description
22948
- *
22949
- * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
22950
- *
22951
- * A special directive is necessary because we cannot use interpolation inside the `selected`
22952
- * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22953
- *
22954
- * <div class="alert alert-warning">
22955
- * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only
22956
- * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you
22957
- * should not use `ngSelected` on the options, as `ngModel` will set the select value and
22958
- * selected options.
22959
- * </div>
22960
- *
22961
- * @example
22962
- <example name="ng-selected">
22963
- <file name="index.html">
22964
- <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
22965
- <select aria-label="ngSelected demo">
22966
- <option>Hello!</option>
22967
- <option id="greet" ng-selected="selected">Greetings!</option>
22968
- </select>
22969
- </file>
22970
- <file name="protractor.js" type="protractor">
22971
- it('should select Greetings!', function() {
22972
- expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
22973
- element(by.model('selected')).click();
22974
- expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
22975
- });
22976
- </file>
22977
- </example>
22978
- *
22979
- * @element OPTION
22980
- * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
22981
- * then special attribute "selected" will be set on the element
22982
- */
22983
-
22984
- /**
22985
- * @ngdoc directive
22986
- * @name ngOpen
22987
- * @restrict A
22988
- * @priority 100
22989
- *
22990
- * @description
22991
- *
22992
- * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
22993
- *
22994
- * A special directive is necessary because we cannot use interpolation inside the `open`
22995
- * attribute. See the {@link guide/interpolation interpolation guide} for more info.
22996
- *
22997
- * ## A note about browser compatibility
22998
- *
22999
- * Edge, Firefox, and Internet Explorer do not support the `details` element, it is
23000
- * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead.
23001
- *
23002
- * @example
23003
- <example name="ng-open">
23004
- <file name="index.html">
23005
- <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
23006
- <details id="details" ng-open="open">
23007
- <summary>Show/Hide me</summary>
23008
- </details>
23009
- </file>
23010
- <file name="protractor.js" type="protractor">
23011
- it('should toggle open', function() {
23012
- expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
23013
- element(by.model('open')).click();
23014
- expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
23015
- });
23016
- </file>
23017
- </example>
23018
- *
23019
- * @element DETAILS
23020
- * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
23021
- * then special attribute "open" will be set on the element
23022
- */
23023
-
23024
- var ngAttributeAliasDirectives = {};
23025
-
23026
- // boolean attrs are evaluated
23027
- forEach(BOOLEAN_ATTR, function(propName, attrName) {
23028
- // binding to multiple is not supported
23029
- if (propName === 'multiple') return;
23030
-
23031
- function defaultLinkFn(scope, element, attr) {
23032
- scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
23033
- attr.$set(attrName, !!value);
23034
- });
23035
- }
23036
-
23037
- var normalized = directiveNormalize('ng-' + attrName);
23038
- var linkFn = defaultLinkFn;
23039
-
23040
- if (propName === 'checked') {
23041
- linkFn = function(scope, element, attr) {
23042
- // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
23043
- if (attr.ngModel !== attr[normalized]) {
23044
- defaultLinkFn(scope, element, attr);
23045
- }
23046
- };
23047
- }
23048
-
23049
- ngAttributeAliasDirectives[normalized] = function() {
23050
- return {
23051
- restrict: 'A',
23052
- priority: 100,
23053
- link: linkFn
23054
- };
23055
- };
23056
- });
23057
-
23058
- // aliased input attrs are evaluated
23059
- forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
23060
- ngAttributeAliasDirectives[ngAttr] = function() {
23061
- return {
23062
- priority: 100,
23063
- link: function(scope, element, attr) {
23064
- //special case ngPattern when a literal regular expression value
23065
- //is used as the expression (this way we don't have to watch anything).
23066
- if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') {
23067
- var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
23068
- if (match) {
23069
- attr.$set('ngPattern', new RegExp(match[1], match[2]));
23070
- return;
23071
- }
23072
- }
23073
-
23074
- scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
23075
- attr.$set(ngAttr, value);
23076
- });
23077
- }
23078
- };
23079
- };
23080
- });
23081
-
23082
- // ng-src, ng-srcset, ng-href are interpolated
23083
- forEach(['src', 'srcset', 'href'], function(attrName) {
23084
- var normalized = directiveNormalize('ng-' + attrName);
23085
- ngAttributeAliasDirectives[normalized] = function() {
23086
- return {
23087
- priority: 99, // it needs to run after the attributes are interpolated
23088
- link: function(scope, element, attr) {
23089
- var propName = attrName,
23090
- name = attrName;
23091
-
23092
- if (attrName === 'href' &&
23093
- toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
23094
- name = 'xlinkHref';
23095
- attr.$attr[name] = 'xlink:href';
23096
- propName = null;
23097
- }
23098
-
23099
- attr.$observe(normalized, function(value) {
23100
- if (!value) {
23101
- if (attrName === 'href') {
23102
- attr.$set(name, null);
23103
- }
23104
- return;
23105
- }
23106
-
23107
- attr.$set(name, value);
23108
-
23109
- // Support: IE 9-11 only
23110
- // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
23111
- // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
23112
- // to set the property as well to achieve the desired effect.
23113
- // We use attr[attrName] value since $set can sanitize the url.
23114
- if (msie && propName) element.prop(propName, attr[name]);
23115
- });
23116
- }
23117
- };
23118
- };
23119
- });
23120
-
23121
- /* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS
23122
- */
23123
- var nullFormCtrl = {
23124
- $addControl: noop,
23125
- $$renameControl: nullFormRenameControl,
23126
- $removeControl: noop,
23127
- $setValidity: noop,
23128
- $setDirty: noop,
23129
- $setPristine: noop,
23130
- $setSubmitted: noop
23131
- },
23132
- PENDING_CLASS = 'ng-pending',
23133
- SUBMITTED_CLASS = 'ng-submitted';
23134
-
23135
- function nullFormRenameControl(control, name) {
23136
- control.$name = name;
23137
- }
23138
-
23139
- /**
23140
- * @ngdoc type
23141
- * @name form.FormController
23142
- *
23143
- * @property {boolean} $pristine True if user has not interacted with the form yet.
23144
- * @property {boolean} $dirty True if user has already interacted with the form.
23145
- * @property {boolean} $valid True if all of the containing forms and controls are valid.
23146
- * @property {boolean} $invalid True if at least one containing control or form is invalid.
23147
- * @property {boolean} $submitted True if user has submitted the form even if its invalid.
23148
- *
23149
- * @property {Object} $pending An object hash, containing references to controls or forms with
23150
- * pending validators, where:
23151
- *
23152
- * - keys are validations tokens (error names).
23153
- * - values are arrays of controls or forms that have a pending validator for the given error name.
23154
- *
23155
- * See {@link form.FormController#$error $error} for a list of built-in validation tokens.
23156
- *
23157
- * @property {Object} $error An object hash, containing references to controls or forms with failing
23158
- * validators, where:
23159
- *
23160
- * - keys are validation tokens (error names),
23161
- * - values are arrays of controls or forms that have a failing validator for the given error name.
23162
- *
23163
- * Built-in validation tokens:
23164
- * - `email`
23165
- * - `max`
23166
- * - `maxlength`
23167
- * - `min`
23168
- * - `minlength`
23169
- * - `number`
23170
- * - `pattern`
23171
- * - `required`
23172
- * - `url`
23173
- * - `date`
23174
- * - `datetimelocal`
23175
- * - `time`
23176
- * - `week`
23177
- * - `month`
23178
- *
23179
- * @description
23180
- * `FormController` keeps track of all its controls and nested forms as well as the state of them,
23181
- * such as being valid/invalid or dirty/pristine.
23182
- *
23183
- * Each {@link ng.directive:form form} directive creates an instance
23184
- * of `FormController`.
23185
- *
23186
- */
23187
- //asks for $scope to fool the BC controller module
23188
- FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
23189
- function FormController($element, $attrs, $scope, $animate, $interpolate) {
23190
- this.$$controls = [];
23191
-
23192
- // init state
23193
- this.$error = {};
23194
- this.$$success = {};
23195
- this.$pending = undefined;
23196
- this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope);
23197
- this.$dirty = false;
23198
- this.$pristine = true;
23199
- this.$valid = true;
23200
- this.$invalid = false;
23201
- this.$submitted = false;
23202
- this.$$parentForm = nullFormCtrl;
23203
-
23204
- this.$$element = $element;
23205
- this.$$animate = $animate;
23206
-
23207
- setupValidity(this);
23208
- }
23209
-
23210
- FormController.prototype = {
23211
- /**
23212
- * @ngdoc method
23213
- * @name form.FormController#$rollbackViewValue
23214
- *
23215
- * @description
23216
- * Rollback all form controls pending updates to the `$modelValue`.
23217
- *
23218
- * Updates may be pending by a debounced event or because the input is waiting for a some future
23219
- * event defined in `ng-model-options`. This method is typically needed by the reset button of
23220
- * a form that uses `ng-model-options` to pend updates.
23221
- */
23222
- $rollbackViewValue: function() {
23223
- forEach(this.$$controls, function(control) {
23224
- control.$rollbackViewValue();
23225
- });
23226
- },
23227
-
23228
- /**
23229
- * @ngdoc method
23230
- * @name form.FormController#$commitViewValue
23231
- *
23232
- * @description
23233
- * Commit all form controls pending updates to the `$modelValue`.
23234
- *
23235
- * Updates may be pending by a debounced event or because the input is waiting for a some future
23236
- * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
23237
- * usually handles calling this in response to input events.
23238
- */
23239
- $commitViewValue: function() {
23240
- forEach(this.$$controls, function(control) {
23241
- control.$commitViewValue();
23242
- });
23243
- },
23244
-
23245
- /**
23246
- * @ngdoc method
23247
- * @name form.FormController#$addControl
23248
- * @param {object} control control object, either a {@link form.FormController} or an
23249
- * {@link ngModel.NgModelController}
23250
- *
23251
- * @description
23252
- * Register a control with the form. Input elements using ngModelController do this automatically
23253
- * when they are linked.
23254
- *
23255
- * Note that the current state of the control will not be reflected on the new parent form. This
23256
- * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
23257
- * state.
23258
- *
23259
- * However, if the method is used programmatically, for example by adding dynamically created controls,
23260
- * or controls that have been previously removed without destroying their corresponding DOM element,
23261
- * it's the developers responsibility to make sure the current state propagates to the parent form.
23262
- *
23263
- * For example, if an input control is added that is already `$dirty` and has `$error` properties,
23264
- * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
23265
- */
23266
- $addControl: function(control) {
23267
- // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
23268
- // and not added to the scope. Now we throw an error.
23269
- assertNotHasOwnProperty(control.$name, 'input');
23270
- this.$$controls.push(control);
23271
-
23272
- if (control.$name) {
23273
- this[control.$name] = control;
23274
- }
23275
-
23276
- control.$$parentForm = this;
23277
- },
23278
-
23279
- // Private API: rename a form control
23280
- $$renameControl: function(control, newName) {
23281
- var oldName = control.$name;
23282
-
23283
- if (this[oldName] === control) {
23284
- delete this[oldName];
23285
- }
23286
- this[newName] = control;
23287
- control.$name = newName;
23288
- },
23289
-
23290
- /**
23291
- * @ngdoc method
23292
- * @name form.FormController#$removeControl
23293
- * @param {object} control control object, either a {@link form.FormController} or an
23294
- * {@link ngModel.NgModelController}
23295
- *
23296
- * @description
23297
- * Deregister a control from the form.
23298
- *
23299
- * Input elements using ngModelController do this automatically when they are destroyed.
23300
- *
23301
- * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
23302
- * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
23303
- * different from case to case. For example, removing the only `$dirty` control from a form may or
23304
- * may not mean that the form is still `$dirty`.
23305
- */
23306
- $removeControl: function(control) {
23307
- if (control.$name && this[control.$name] === control) {
23308
- delete this[control.$name];
23309
- }
23310
- forEach(this.$pending, function(value, name) {
23311
- // eslint-disable-next-line no-invalid-this
23312
- this.$setValidity(name, null, control);
23313
- }, this);
23314
- forEach(this.$error, function(value, name) {
23315
- // eslint-disable-next-line no-invalid-this
23316
- this.$setValidity(name, null, control);
23317
- }, this);
23318
- forEach(this.$$success, function(value, name) {
23319
- // eslint-disable-next-line no-invalid-this
23320
- this.$setValidity(name, null, control);
23321
- }, this);
23322
-
23323
- arrayRemove(this.$$controls, control);
23324
- control.$$parentForm = nullFormCtrl;
23325
- },
23326
-
23327
- /**
23328
- * @ngdoc method
23329
- * @name form.FormController#$setDirty
23330
- *
23331
- * @description
23332
- * Sets the form to a dirty state.
23333
- *
23334
- * This method can be called to add the 'ng-dirty' class and set the form to a dirty
23335
- * state (ng-dirty class). This method will also propagate to parent forms.
23336
- */
23337
- $setDirty: function() {
23338
- this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
23339
- this.$$animate.addClass(this.$$element, DIRTY_CLASS);
23340
- this.$dirty = true;
23341
- this.$pristine = false;
23342
- this.$$parentForm.$setDirty();
23343
- },
23344
-
23345
- /**
23346
- * @ngdoc method
23347
- * @name form.FormController#$setPristine
23348
- *
23349
- * @description
23350
- * Sets the form to its pristine state.
23351
- *
23352
- * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes
23353
- * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted`
23354
- * state to false.
23355
- *
23356
- * This method will also propagate to all the controls contained in this form.
23357
- *
23358
- * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
23359
- * saving or resetting it.
23360
- */
23361
- $setPristine: function() {
23362
- this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
23363
- this.$dirty = false;
23364
- this.$pristine = true;
23365
- this.$submitted = false;
23366
- forEach(this.$$controls, function(control) {
23367
- control.$setPristine();
23368
- });
23369
- },
23370
-
23371
- /**
23372
- * @ngdoc method
23373
- * @name form.FormController#$setUntouched
23374
- *
23375
- * @description
23376
- * Sets the form to its untouched state.
23377
- *
23378
- * This method can be called to remove the 'ng-touched' class and set the form controls to their
23379
- * untouched state (ng-untouched class).
23380
- *
23381
- * Setting a form controls back to their untouched state is often useful when setting the form
23382
- * back to its pristine state.
23383
- */
23384
- $setUntouched: function() {
23385
- forEach(this.$$controls, function(control) {
23386
- control.$setUntouched();
23387
- });
23388
- },
23389
-
23390
- /**
23391
- * @ngdoc method
23392
- * @name form.FormController#$setSubmitted
23393
- *
23394
- * @description
23395
- * Sets the form to its submitted state.
23396
- */
23397
- $setSubmitted: function() {
23398
- this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
23399
- this.$submitted = true;
23400
- this.$$parentForm.$setSubmitted();
23401
- }
23402
- };
23403
-
23404
- /**
23405
- * @ngdoc method
23406
- * @name form.FormController#$setValidity
23407
- *
23408
- * @description
23409
- * Change the validity state of the form, and notify the parent form (if any).
23410
- *
23411
- * Application developers will rarely need to call this method directly. It is used internally, by
23412
- * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a
23413
- * control's validity state to the parent `FormController`.
23414
- *
23415
- * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be
23416
- * assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for
23417
- * unfulfilled `$asyncValidators`), so that it is available for data-binding. The
23418
- * `validationErrorKey` should be in camelCase and will get converted into dash-case for
23419
- * class name. Example: `myError` will result in `ng-valid-my-error` and
23420
- * `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`.
23421
- * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending
23422
- * (undefined), or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
23423
- * Skipped is used by AngularJS when validators do not run because of parse errors and when
23424
- * `$asyncValidators` do not run because any of the `$validators` failed.
23425
- * @param {NgModelController | FormController} controller - The controller whose validity state is
23426
- * triggering the change.
23427
- */
23428
- addSetValidityMethod({
23429
- clazz: FormController,
23430
- set: function(object, property, controller) {
23431
- var list = object[property];
23432
- if (!list) {
23433
- object[property] = [controller];
23434
- } else {
23435
- var index = list.indexOf(controller);
23436
- if (index === -1) {
23437
- list.push(controller);
23438
- }
23439
- }
23440
- },
23441
- unset: function(object, property, controller) {
23442
- var list = object[property];
23443
- if (!list) {
23444
- return;
23445
- }
23446
- arrayRemove(list, controller);
23447
- if (list.length === 0) {
23448
- delete object[property];
23449
- }
23450
- }
23451
- });
23452
-
23453
- /**
23454
- * @ngdoc directive
23455
- * @name ngForm
23456
- * @restrict EAC
23457
- *
23458
- * @description
23459
- * Nestable alias of {@link ng.directive:form `form`} directive. HTML
23460
- * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
23461
- * sub-group of controls needs to be determined.
23462
- *
23463
- * Note: the purpose of `ngForm` is to group controls,
23464
- * but not to be a replacement for the `<form>` tag with all of its capabilities
23465
- * (e.g. posting to the server, ...).
23466
- *
23467
- * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
23468
- * related scope, under this name.
23469
- *
23470
- */
23471
-
23472
- /**
23473
- * @ngdoc directive
23474
- * @name form
23475
- * @restrict E
23476
- *
23477
- * @description
23478
- * Directive that instantiates
23479
- * {@link form.FormController FormController}.
23480
- *
23481
- * If the `name` attribute is specified, the form controller is published onto the current scope under
23482
- * this name.
23483
- *
23484
- * # Alias: {@link ng.directive:ngForm `ngForm`}
23485
- *
23486
- * In Angular, forms can be nested. This means that the outer form is valid when all of the child
23487
- * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
23488
- * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to
23489
- * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group
23490
- * of controls needs to be determined.
23491
- *
23492
- * # CSS classes
23493
- * - `ng-valid` is set if the form is valid.
23494
- * - `ng-invalid` is set if the form is invalid.
23495
- * - `ng-pending` is set if the form is pending.
23496
- * - `ng-pristine` is set if the form is pristine.
23497
- * - `ng-dirty` is set if the form is dirty.
23498
- * - `ng-submitted` is set if the form was submitted.
23499
- *
23500
- * Keep in mind that ngAnimate can detect each of these classes when added and removed.
23501
- *
23502
- *
23503
- * # Submitting a form and preventing the default action
23504
- *
23505
- * Since the role of forms in client-side Angular applications is different than in classical
23506
- * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
23507
- * page reload that sends the data to the server. Instead some javascript logic should be triggered
23508
- * to handle the form submission in an application-specific way.
23509
- *
23510
- * For this reason, Angular prevents the default action (form submission to the server) unless the
23511
- * `<form>` element has an `action` attribute specified.
23512
- *
23513
- * You can use one of the following two ways to specify what javascript method should be called when
23514
- * a form is submitted:
23515
- *
23516
- * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
23517
- * - {@link ng.directive:ngClick ngClick} directive on the first
23518
- * button or input field of type submit (input[type=submit])
23519
- *
23520
- * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
23521
- * or {@link ng.directive:ngClick ngClick} directives.
23522
- * This is because of the following form submission rules in the HTML specification:
23523
- *
23524
- * - If a form has only one input field then hitting enter in this field triggers form submit
23525
- * (`ngSubmit`)
23526
- * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
23527
- * doesn't trigger submit
23528
- * - if a form has one or more input fields and one or more buttons or input[type=submit] then
23529
- * hitting enter in any of the input fields will trigger the click handler on the *first* button or
23530
- * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
23531
- *
23532
- * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
23533
- * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
23534
- * to have access to the updated model.
23535
- *
23536
- * ## Animation Hooks
23537
- *
23538
- * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
23539
- * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
23540
- * other validations that are performed within the form. Animations in ngForm are similar to how
23541
- * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
23542
- * as JS animations.
23543
- *
23544
- * The following example shows a simple way to utilize CSS transitions to style a form element
23545
- * that has been rendered as invalid after it has been validated:
23546
- *
23547
- * <pre>
23548
- * //be sure to include ngAnimate as a module to hook into more
23549
- * //advanced animations
23550
- * .my-form {
23551
- * transition:0.5s linear all;
23552
- * background: white;
23553
- * }
23554
- * .my-form.ng-invalid {
23555
- * background: red;
23556
- * color:white;
23557
- * }
23558
- * </pre>
23559
- *
23560
- * @example
23561
- <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
23562
- <file name="index.html">
23563
- <script>
23564
- angular.module('formExample', [])
23565
- .controller('FormController', ['$scope', function($scope) {
23566
- $scope.userType = 'guest';
23567
- }]);
23568
- </script>
23569
- <style>
23570
- .my-form {
23571
- transition:all linear 0.5s;
23572
- background: transparent;
23573
- }
23574
- .my-form.ng-invalid {
23575
- background: red;
23576
- }
23577
- </style>
23578
- <form name="myForm" ng-controller="FormController" class="my-form">
23579
- userType: <input name="input" ng-model="userType" required>
23580
- <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
23581
- <code>userType = {{userType}}</code><br>
23582
- <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
23583
- <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
23584
- <code>myForm.$valid = {{myForm.$valid}}</code><br>
23585
- <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
23586
- </form>
23587
- </file>
23588
- <file name="protractor.js" type="protractor">
23589
- it('should initialize to model', function() {
23590
- var userType = element(by.binding('userType'));
23591
- var valid = element(by.binding('myForm.input.$valid'));
23592
-
23593
- expect(userType.getText()).toContain('guest');
23594
- expect(valid.getText()).toContain('true');
23595
- });
23596
-
23597
- it('should be invalid if empty', function() {
23598
- var userType = element(by.binding('userType'));
23599
- var valid = element(by.binding('myForm.input.$valid'));
23600
- var userInput = element(by.model('userType'));
23601
-
23602
- userInput.clear();
23603
- userInput.sendKeys('');
23604
-
23605
- expect(userType.getText()).toEqual('userType =');
23606
- expect(valid.getText()).toContain('false');
23607
- });
23608
- </file>
23609
- </example>
23610
- *
23611
- * @param {string=} name Name of the form. If specified, the form controller will be published into
23612
- * related scope, under this name.
23613
- */
23614
- var formDirectiveFactory = function(isNgForm) {
23615
- return ['$timeout', '$parse', function($timeout, $parse) {
23616
- var formDirective = {
23617
- name: 'form',
23618
- restrict: isNgForm ? 'EAC' : 'E',
23619
- require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
23620
- controller: FormController,
23621
- compile: function ngFormCompile(formElement, attr) {
23622
- // Setup initial state of the control
23623
- formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
23624
-
23625
- var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
23626
-
23627
- return {
23628
- pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
23629
- var controller = ctrls[0];
23630
-
23631
- // if `action` attr is not present on the form, prevent the default action (submission)
23632
- if (!('action' in attr)) {
23633
- // we can't use jq events because if a form is destroyed during submission the default
23634
- // action is not prevented. see #1238
23635
- //
23636
- // IE 9 is not affected because it doesn't fire a submit event and try to do a full
23637
- // page reload if the form was destroyed by submission of the form via a click handler
23638
- // on a button in the form. Looks like an IE9 specific bug.
23639
- var handleFormSubmission = function(event) {
23640
- scope.$apply(function() {
23641
- controller.$commitViewValue();
23642
- controller.$setSubmitted();
23643
- });
23644
-
23645
- event.preventDefault();
23646
- };
23647
-
23648
- formElement[0].addEventListener('submit', handleFormSubmission);
23649
-
23650
- // unregister the preventDefault listener so that we don't not leak memory but in a
23651
- // way that will achieve the prevention of the default action.
23652
- formElement.on('$destroy', function() {
23653
- $timeout(function() {
23654
- formElement[0].removeEventListener('submit', handleFormSubmission);
23655
- }, 0, false);
23656
- });
23657
- }
23658
-
23659
- var parentFormCtrl = ctrls[1] || controller.$$parentForm;
23660
- parentFormCtrl.$addControl(controller);
23661
-
23662
- var setter = nameAttr ? getSetter(controller.$name) : noop;
23663
-
23664
- if (nameAttr) {
23665
- setter(scope, controller);
23666
- attr.$observe(nameAttr, function(newValue) {
23667
- if (controller.$name === newValue) return;
23668
- setter(scope, undefined);
23669
- controller.$$parentForm.$$renameControl(controller, newValue);
23670
- setter = getSetter(controller.$name);
23671
- setter(scope, controller);
23672
- });
23673
- }
23674
- formElement.on('$destroy', function() {
23675
- controller.$$parentForm.$removeControl(controller);
23676
- setter(scope, undefined);
23677
- extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
23678
- });
23679
- }
23680
- };
23681
- }
23682
- };
23683
-
23684
- return formDirective;
23685
-
23686
- function getSetter(expression) {
23687
- if (expression === '') {
23688
- //create an assignable expression, so forms with an empty name can be renamed later
23689
- return $parse('this[""]').assign;
23690
- }
23691
- return $parse(expression).assign || noop;
23692
- }
23693
- }];
23694
- };
23695
-
23696
- var formDirective = formDirectiveFactory();
23697
- var ngFormDirective = formDirectiveFactory(true);
23698
-
23699
-
23700
-
23701
- // helper methods
23702
- function setupValidity(instance) {
23703
- instance.$$classCache = {};
23704
- instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS));
23705
- }
23706
- function addSetValidityMethod(context) {
23707
- var clazz = context.clazz,
23708
- set = context.set,
23709
- unset = context.unset;
23710
-
23711
- clazz.prototype.$setValidity = function(validationErrorKey, state, controller) {
23712
- if (isUndefined(state)) {
23713
- createAndSet(this, '$pending', validationErrorKey, controller);
23714
- } else {
23715
- unsetAndCleanup(this, '$pending', validationErrorKey, controller);
23716
- }
23717
- if (!isBoolean(state)) {
23718
- unset(this.$error, validationErrorKey, controller);
23719
- unset(this.$$success, validationErrorKey, controller);
23720
- } else {
23721
- if (state) {
23722
- unset(this.$error, validationErrorKey, controller);
23723
- set(this.$$success, validationErrorKey, controller);
23724
- } else {
23725
- set(this.$error, validationErrorKey, controller);
23726
- unset(this.$$success, validationErrorKey, controller);
23727
- }
23728
- }
23729
- if (this.$pending) {
23730
- cachedToggleClass(this, PENDING_CLASS, true);
23731
- this.$valid = this.$invalid = undefined;
23732
- toggleValidationCss(this, '', null);
23733
- } else {
23734
- cachedToggleClass(this, PENDING_CLASS, false);
23735
- this.$valid = isObjectEmpty(this.$error);
23736
- this.$invalid = !this.$valid;
23737
- toggleValidationCss(this, '', this.$valid);
23738
- }
23739
-
23740
- // re-read the state as the set/unset methods could have
23741
- // combined state in this.$error[validationError] (used for forms),
23742
- // where setting/unsetting only increments/decrements the value,
23743
- // and does not replace it.
23744
- var combinedState;
23745
- if (this.$pending && this.$pending[validationErrorKey]) {
23746
- combinedState = undefined;
23747
- } else if (this.$error[validationErrorKey]) {
23748
- combinedState = false;
23749
- } else if (this.$$success[validationErrorKey]) {
23750
- combinedState = true;
23751
- } else {
23752
- combinedState = null;
23753
- }
23754
-
23755
- toggleValidationCss(this, validationErrorKey, combinedState);
23756
- this.$$parentForm.$setValidity(validationErrorKey, combinedState, this);
23757
- };
23758
-
23759
- function createAndSet(ctrl, name, value, controller) {
23760
- if (!ctrl[name]) {
23761
- ctrl[name] = {};
23762
- }
23763
- set(ctrl[name], value, controller);
23764
- }
23765
-
23766
- function unsetAndCleanup(ctrl, name, value, controller) {
23767
- if (ctrl[name]) {
23768
- unset(ctrl[name], value, controller);
23769
- }
23770
- if (isObjectEmpty(ctrl[name])) {
23771
- ctrl[name] = undefined;
23772
- }
23773
- }
23774
-
23775
- function cachedToggleClass(ctrl, className, switchValue) {
23776
- if (switchValue && !ctrl.$$classCache[className]) {
23777
- ctrl.$$animate.addClass(ctrl.$$element, className);
23778
- ctrl.$$classCache[className] = true;
23779
- } else if (!switchValue && ctrl.$$classCache[className]) {
23780
- ctrl.$$animate.removeClass(ctrl.$$element, className);
23781
- ctrl.$$classCache[className] = false;
23782
- }
23783
- }
23784
-
23785
- function toggleValidationCss(ctrl, validationErrorKey, isValid) {
23786
- validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
23787
-
23788
- cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true);
23789
- cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false);
23790
- }
23791
- }
23792
-
23793
- function isObjectEmpty(obj) {
23794
- if (obj) {
23795
- for (var prop in obj) {
23796
- if (obj.hasOwnProperty(prop)) {
23797
- return false;
23798
- }
23799
- }
23800
- }
23801
- return true;
23802
- }
23803
-
23804
- /* global
23805
- VALID_CLASS: false,
23806
- INVALID_CLASS: false,
23807
- PRISTINE_CLASS: false,
23808
- DIRTY_CLASS: false,
23809
- ngModelMinErr: false
23810
- */
23811
-
23812
- // Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
23813
- var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
23814
- // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
23815
- // Note: We are being more lenient, because browsers are too.
23816
- // 1. Scheme
23817
- // 2. Slashes
23818
- // 3. Username
23819
- // 4. Password
23820
- // 5. Hostname
23821
- // 6. Port
23822
- // 7. Path
23823
- // 8. Query
23824
- // 9. Fragment
23825
- // 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999
23826
- var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
23827
- // eslint-disable-next-line max-len
23828
- var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
23829
- var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
23830
- var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
23831
- var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
23832
- var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
23833
- var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
23834
- var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
23835
-
23836
- var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
23837
- var PARTIAL_VALIDATION_TYPES = createMap();
23838
- forEach('date,datetime-local,month,time,week'.split(','), function(type) {
23839
- PARTIAL_VALIDATION_TYPES[type] = true;
23840
- });
23841
-
23842
- var inputType = {
23843
-
23844
- /**
23845
- * @ngdoc input
23846
- * @name input[text]
23847
- *
23848
- * @description
23849
- * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
23850
- *
23851
- *
23852
- * @param {string} ngModel Assignable angular expression to data-bind to.
23853
- * @param {string=} name Property name of the form under which the control is published.
23854
- * @param {string=} required Adds `required` validation error key if the value is not entered.
23855
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
23856
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
23857
- * `required` when you want to data-bind to the `required` attribute.
23858
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
23859
- * minlength.
23860
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
23861
- * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
23862
- * any length.
23863
- * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
23864
- * that contains the regular expression body that will be converted to a regular expression
23865
- * as in the ngPattern directive.
23866
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
23867
- * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
23868
- * If the expression evaluates to a RegExp object, then this is used directly.
23869
- * If the expression evaluates to a string, then it will be converted to a RegExp
23870
- * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
23871
- * `new RegExp('^abc$')`.<br />
23872
- * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
23873
- * start at the index of the last search's match, thus not taking the whole input value into
23874
- * account.
23875
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
23876
- * interaction with the input element.
23877
- * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
23878
- * This parameter is ignored for input[type=password] controls, which will never trim the
23879
- * input.
23880
- *
23881
- * @example
23882
- <example name="text-input-directive" module="textInputExample">
23883
- <file name="index.html">
23884
- <script>
23885
- angular.module('textInputExample', [])
23886
- .controller('ExampleController', ['$scope', function($scope) {
23887
- $scope.example = {
23888
- text: 'guest',
23889
- word: /^\s*\w*\s*$/
23890
- };
23891
- }]);
23892
- </script>
23893
- <form name="myForm" ng-controller="ExampleController">
23894
- <label>Single word:
23895
- <input type="text" name="input" ng-model="example.text"
23896
- ng-pattern="example.word" required ng-trim="false">
23897
- </label>
23898
- <div role="alert">
23899
- <span class="error" ng-show="myForm.input.$error.required">
23900
- Required!</span>
23901
- <span class="error" ng-show="myForm.input.$error.pattern">
23902
- Single word only!</span>
23903
- </div>
23904
- <code>text = {{example.text}}</code><br/>
23905
- <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/>
23906
- <code>myForm.input.$error = {{myForm.input.$error}}</code><br/>
23907
- <code>myForm.$valid = {{myForm.$valid}}</code><br/>
23908
- <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/>
23909
- </form>
23910
- </file>
23911
- <file name="protractor.js" type="protractor">
23912
- var text = element(by.binding('example.text'));
23913
- var valid = element(by.binding('myForm.input.$valid'));
23914
- var input = element(by.model('example.text'));
23915
-
23916
- it('should initialize to model', function() {
23917
- expect(text.getText()).toContain('guest');
23918
- expect(valid.getText()).toContain('true');
23919
- });
23920
-
23921
- it('should be invalid if empty', function() {
23922
- input.clear();
23923
- input.sendKeys('');
23924
-
23925
- expect(text.getText()).toEqual('text =');
23926
- expect(valid.getText()).toContain('false');
23927
- });
23928
-
23929
- it('should be invalid if multi word', function() {
23930
- input.clear();
23931
- input.sendKeys('hello world');
23932
-
23933
- expect(valid.getText()).toContain('false');
23934
- });
23935
- </file>
23936
- </example>
23937
- */
23938
- 'text': textInputType,
23939
-
23940
- /**
23941
- * @ngdoc input
23942
- * @name input[date]
23943
- *
23944
- * @description
23945
- * Input with date validation and transformation. In browsers that do not yet support
23946
- * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
23947
- * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
23948
- * modern browsers do not yet support this input type, it is important to provide cues to users on the
23949
- * expected input format via a placeholder or label.
23950
- *
23951
- * The model must always be a Date object, otherwise Angular will throw an error.
23952
- * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
23953
- *
23954
- * The timezone to be used to read/write the `Date` instance in the model can be defined using
23955
- * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
23956
- *
23957
- * @param {string} ngModel Assignable angular expression to data-bind to.
23958
- * @param {string=} name Property name of the form under which the control is published.
23959
- * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
23960
- * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
23961
- * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
23962
- * constraint validation.
23963
- * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
23964
- * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
23965
- * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
23966
- * constraint validation.
23967
- * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
23968
- * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
23969
- * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
23970
- * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
23971
- * @param {string=} required Sets `required` validation error key if the value is not entered.
23972
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
23973
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
23974
- * `required` when you want to data-bind to the `required` attribute.
23975
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
23976
- * interaction with the input element.
23977
- *
23978
- * @example
23979
- <example name="date-input-directive" module="dateInputExample">
23980
- <file name="index.html">
23981
- <script>
23982
- angular.module('dateInputExample', [])
23983
- .controller('DateController', ['$scope', function($scope) {
23984
- $scope.example = {
23985
- value: new Date(2013, 9, 22)
23986
- };
23987
- }]);
23988
- </script>
23989
- <form name="myForm" ng-controller="DateController as dateCtrl">
23990
- <label for="exampleInput">Pick a date in 2013:</label>
23991
- <input type="date" id="exampleInput" name="input" ng-model="example.value"
23992
- placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
23993
- <div role="alert">
23994
- <span class="error" ng-show="myForm.input.$error.required">
23995
- Required!</span>
23996
- <span class="error" ng-show="myForm.input.$error.date">
23997
- Not a valid date!</span>
23998
- </div>
23999
- <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
24000
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24001
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24002
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24003
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24004
- </form>
24005
- </file>
24006
- <file name="protractor.js" type="protractor">
24007
- var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
24008
- var valid = element(by.binding('myForm.input.$valid'));
24009
-
24010
- // currently protractor/webdriver does not support
24011
- // sending keys to all known HTML5 input controls
24012
- // for various browsers (see https://github.com/angular/protractor/issues/562).
24013
- function setInput(val) {
24014
- // set the value of the element and force validation.
24015
- var scr = "var ipt = document.getElementById('exampleInput'); " +
24016
- "ipt.value = '" + val + "';" +
24017
- "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24018
- browser.executeScript(scr);
24019
- }
24020
-
24021
- it('should initialize to model', function() {
24022
- expect(value.getText()).toContain('2013-10-22');
24023
- expect(valid.getText()).toContain('myForm.input.$valid = true');
24024
- });
24025
-
24026
- it('should be invalid if empty', function() {
24027
- setInput('');
24028
- expect(value.getText()).toEqual('value =');
24029
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24030
- });
24031
-
24032
- it('should be invalid if over max', function() {
24033
- setInput('2015-01-01');
24034
- expect(value.getText()).toContain('');
24035
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24036
- });
24037
- </file>
24038
- </example>
24039
- */
24040
- 'date': createDateInputType('date', DATE_REGEXP,
24041
- createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
24042
- 'yyyy-MM-dd'),
24043
-
24044
- /**
24045
- * @ngdoc input
24046
- * @name input[datetime-local]
24047
- *
24048
- * @description
24049
- * Input with datetime validation and transformation. In browsers that do not yet support
24050
- * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
24051
- * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
24052
- *
24053
- * The model must always be a Date object, otherwise Angular will throw an error.
24054
- * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
24055
- *
24056
- * The timezone to be used to read/write the `Date` instance in the model can be defined using
24057
- * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
24058
- *
24059
- * @param {string} ngModel Assignable angular expression to data-bind to.
24060
- * @param {string=} name Property name of the form under which the control is published.
24061
- * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24062
- * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
24063
- * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
24064
- * Note that `min` will also add native HTML5 constraint validation.
24065
- * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24066
- * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
24067
- * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
24068
- * Note that `max` will also add native HTML5 constraint validation.
24069
- * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
24070
- * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
24071
- * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
24072
- * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
24073
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24074
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24075
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24076
- * `required` when you want to data-bind to the `required` attribute.
24077
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24078
- * interaction with the input element.
24079
- *
24080
- * @example
24081
- <example name="datetimelocal-input-directive" module="dateExample">
24082
- <file name="index.html">
24083
- <script>
24084
- angular.module('dateExample', [])
24085
- .controller('DateController', ['$scope', function($scope) {
24086
- $scope.example = {
24087
- value: new Date(2010, 11, 28, 14, 57)
24088
- };
24089
- }]);
24090
- </script>
24091
- <form name="myForm" ng-controller="DateController as dateCtrl">
24092
- <label for="exampleInput">Pick a date between in 2013:</label>
24093
- <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
24094
- placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
24095
- <div role="alert">
24096
- <span class="error" ng-show="myForm.input.$error.required">
24097
- Required!</span>
24098
- <span class="error" ng-show="myForm.input.$error.datetimelocal">
24099
- Not a valid date!</span>
24100
- </div>
24101
- <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
24102
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24103
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24104
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24105
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24106
- </form>
24107
- </file>
24108
- <file name="protractor.js" type="protractor">
24109
- var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
24110
- var valid = element(by.binding('myForm.input.$valid'));
24111
-
24112
- // currently protractor/webdriver does not support
24113
- // sending keys to all known HTML5 input controls
24114
- // for various browsers (https://github.com/angular/protractor/issues/562).
24115
- function setInput(val) {
24116
- // set the value of the element and force validation.
24117
- var scr = "var ipt = document.getElementById('exampleInput'); " +
24118
- "ipt.value = '" + val + "';" +
24119
- "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24120
- browser.executeScript(scr);
24121
- }
24122
-
24123
- it('should initialize to model', function() {
24124
- expect(value.getText()).toContain('2010-12-28T14:57:00');
24125
- expect(valid.getText()).toContain('myForm.input.$valid = true');
24126
- });
24127
-
24128
- it('should be invalid if empty', function() {
24129
- setInput('');
24130
- expect(value.getText()).toEqual('value =');
24131
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24132
- });
24133
-
24134
- it('should be invalid if over max', function() {
24135
- setInput('2015-01-01T23:59:00');
24136
- expect(value.getText()).toContain('');
24137
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24138
- });
24139
- </file>
24140
- </example>
24141
- */
24142
- 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
24143
- createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
24144
- 'yyyy-MM-ddTHH:mm:ss.sss'),
24145
-
24146
- /**
24147
- * @ngdoc input
24148
- * @name input[time]
24149
- *
24150
- * @description
24151
- * Input with time validation and transformation. In browsers that do not yet support
24152
- * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
24153
- * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
24154
- * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
24155
- *
24156
- * The model must always be a Date object, otherwise Angular will throw an error.
24157
- * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
24158
- *
24159
- * The timezone to be used to read/write the `Date` instance in the model can be defined using
24160
- * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
24161
- *
24162
- * @param {string} ngModel Assignable angular expression to data-bind to.
24163
- * @param {string=} name Property name of the form under which the control is published.
24164
- * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24165
- * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
24166
- * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
24167
- * native HTML5 constraint validation.
24168
- * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24169
- * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
24170
- * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
24171
- * native HTML5 constraint validation.
24172
- * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
24173
- * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
24174
- * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
24175
- * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
24176
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24177
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24178
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24179
- * `required` when you want to data-bind to the `required` attribute.
24180
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24181
- * interaction with the input element.
24182
- *
24183
- * @example
24184
- <example name="time-input-directive" module="timeExample">
24185
- <file name="index.html">
24186
- <script>
24187
- angular.module('timeExample', [])
24188
- .controller('DateController', ['$scope', function($scope) {
24189
- $scope.example = {
24190
- value: new Date(1970, 0, 1, 14, 57, 0)
24191
- };
24192
- }]);
24193
- </script>
24194
- <form name="myForm" ng-controller="DateController as dateCtrl">
24195
- <label for="exampleInput">Pick a time between 8am and 5pm:</label>
24196
- <input type="time" id="exampleInput" name="input" ng-model="example.value"
24197
- placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
24198
- <div role="alert">
24199
- <span class="error" ng-show="myForm.input.$error.required">
24200
- Required!</span>
24201
- <span class="error" ng-show="myForm.input.$error.time">
24202
- Not a valid date!</span>
24203
- </div>
24204
- <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
24205
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24206
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24207
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24208
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24209
- </form>
24210
- </file>
24211
- <file name="protractor.js" type="protractor">
24212
- var value = element(by.binding('example.value | date: "HH:mm:ss"'));
24213
- var valid = element(by.binding('myForm.input.$valid'));
24214
-
24215
- // currently protractor/webdriver does not support
24216
- // sending keys to all known HTML5 input controls
24217
- // for various browsers (https://github.com/angular/protractor/issues/562).
24218
- function setInput(val) {
24219
- // set the value of the element and force validation.
24220
- var scr = "var ipt = document.getElementById('exampleInput'); " +
24221
- "ipt.value = '" + val + "';" +
24222
- "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24223
- browser.executeScript(scr);
24224
- }
24225
-
24226
- it('should initialize to model', function() {
24227
- expect(value.getText()).toContain('14:57:00');
24228
- expect(valid.getText()).toContain('myForm.input.$valid = true');
24229
- });
24230
-
24231
- it('should be invalid if empty', function() {
24232
- setInput('');
24233
- expect(value.getText()).toEqual('value =');
24234
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24235
- });
24236
-
24237
- it('should be invalid if over max', function() {
24238
- setInput('23:59:00');
24239
- expect(value.getText()).toContain('');
24240
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24241
- });
24242
- </file>
24243
- </example>
24244
- */
24245
- 'time': createDateInputType('time', TIME_REGEXP,
24246
- createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
24247
- 'HH:mm:ss.sss'),
24248
-
24249
- /**
24250
- * @ngdoc input
24251
- * @name input[week]
24252
- *
24253
- * @description
24254
- * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
24255
- * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
24256
- * week format (yyyy-W##), for example: `2013-W02`.
24257
- *
24258
- * The model must always be a Date object, otherwise Angular will throw an error.
24259
- * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
24260
- *
24261
- * The timezone to be used to read/write the `Date` instance in the model can be defined using
24262
- * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
24263
- *
24264
- * @param {string} ngModel Assignable angular expression to data-bind to.
24265
- * @param {string=} name Property name of the form under which the control is published.
24266
- * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24267
- * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
24268
- * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
24269
- * native HTML5 constraint validation.
24270
- * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24271
- * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
24272
- * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
24273
- * native HTML5 constraint validation.
24274
- * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
24275
- * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
24276
- * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
24277
- * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
24278
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24279
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24280
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24281
- * `required` when you want to data-bind to the `required` attribute.
24282
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24283
- * interaction with the input element.
24284
- *
24285
- * @example
24286
- <example name="week-input-directive" module="weekExample">
24287
- <file name="index.html">
24288
- <script>
24289
- angular.module('weekExample', [])
24290
- .controller('DateController', ['$scope', function($scope) {
24291
- $scope.example = {
24292
- value: new Date(2013, 0, 3)
24293
- };
24294
- }]);
24295
- </script>
24296
- <form name="myForm" ng-controller="DateController as dateCtrl">
24297
- <label>Pick a date between in 2013:
24298
- <input id="exampleInput" type="week" name="input" ng-model="example.value"
24299
- placeholder="YYYY-W##" min="2012-W32"
24300
- max="2013-W52" required />
24301
- </label>
24302
- <div role="alert">
24303
- <span class="error" ng-show="myForm.input.$error.required">
24304
- Required!</span>
24305
- <span class="error" ng-show="myForm.input.$error.week">
24306
- Not a valid date!</span>
24307
- </div>
24308
- <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
24309
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24310
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24311
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24312
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24313
- </form>
24314
- </file>
24315
- <file name="protractor.js" type="protractor">
24316
- var value = element(by.binding('example.value | date: "yyyy-Www"'));
24317
- var valid = element(by.binding('myForm.input.$valid'));
24318
-
24319
- // currently protractor/webdriver does not support
24320
- // sending keys to all known HTML5 input controls
24321
- // for various browsers (https://github.com/angular/protractor/issues/562).
24322
- function setInput(val) {
24323
- // set the value of the element and force validation.
24324
- var scr = "var ipt = document.getElementById('exampleInput'); " +
24325
- "ipt.value = '" + val + "';" +
24326
- "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24327
- browser.executeScript(scr);
24328
- }
24329
-
24330
- it('should initialize to model', function() {
24331
- expect(value.getText()).toContain('2013-W01');
24332
- expect(valid.getText()).toContain('myForm.input.$valid = true');
24333
- });
24334
-
24335
- it('should be invalid if empty', function() {
24336
- setInput('');
24337
- expect(value.getText()).toEqual('value =');
24338
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24339
- });
24340
-
24341
- it('should be invalid if over max', function() {
24342
- setInput('2015-W01');
24343
- expect(value.getText()).toContain('');
24344
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24345
- });
24346
- </file>
24347
- </example>
24348
- */
24349
- 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
24350
-
24351
- /**
24352
- * @ngdoc input
24353
- * @name input[month]
24354
- *
24355
- * @description
24356
- * Input with month validation and transformation. In browsers that do not yet support
24357
- * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
24358
- * month format (yyyy-MM), for example: `2009-01`.
24359
- *
24360
- * The model must always be a Date object, otherwise Angular will throw an error.
24361
- * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
24362
- * If the model is not set to the first of the month, the next view to model update will set it
24363
- * to the first of the month.
24364
- *
24365
- * The timezone to be used to read/write the `Date` instance in the model can be defined using
24366
- * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
24367
- *
24368
- * @param {string} ngModel Assignable angular expression to data-bind to.
24369
- * @param {string=} name Property name of the form under which the control is published.
24370
- * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24371
- * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
24372
- * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
24373
- * native HTML5 constraint validation.
24374
- * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24375
- * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
24376
- * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
24377
- * native HTML5 constraint validation.
24378
- * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
24379
- * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
24380
- * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
24381
- * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
24382
-
24383
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24384
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24385
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24386
- * `required` when you want to data-bind to the `required` attribute.
24387
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24388
- * interaction with the input element.
24389
- *
24390
- * @example
24391
- <example name="month-input-directive" module="monthExample">
24392
- <file name="index.html">
24393
- <script>
24394
- angular.module('monthExample', [])
24395
- .controller('DateController', ['$scope', function($scope) {
24396
- $scope.example = {
24397
- value: new Date(2013, 9, 1)
24398
- };
24399
- }]);
24400
- </script>
24401
- <form name="myForm" ng-controller="DateController as dateCtrl">
24402
- <label for="exampleInput">Pick a month in 2013:</label>
24403
- <input id="exampleInput" type="month" name="input" ng-model="example.value"
24404
- placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
24405
- <div role="alert">
24406
- <span class="error" ng-show="myForm.input.$error.required">
24407
- Required!</span>
24408
- <span class="error" ng-show="myForm.input.$error.month">
24409
- Not a valid month!</span>
24410
- </div>
24411
- <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
24412
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24413
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24414
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24415
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24416
- </form>
24417
- </file>
24418
- <file name="protractor.js" type="protractor">
24419
- var value = element(by.binding('example.value | date: "yyyy-MM"'));
24420
- var valid = element(by.binding('myForm.input.$valid'));
24421
-
24422
- // currently protractor/webdriver does not support
24423
- // sending keys to all known HTML5 input controls
24424
- // for various browsers (https://github.com/angular/protractor/issues/562).
24425
- function setInput(val) {
24426
- // set the value of the element and force validation.
24427
- var scr = "var ipt = document.getElementById('exampleInput'); " +
24428
- "ipt.value = '" + val + "';" +
24429
- "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
24430
- browser.executeScript(scr);
24431
- }
24432
-
24433
- it('should initialize to model', function() {
24434
- expect(value.getText()).toContain('2013-10');
24435
- expect(valid.getText()).toContain('myForm.input.$valid = true');
24436
- });
24437
-
24438
- it('should be invalid if empty', function() {
24439
- setInput('');
24440
- expect(value.getText()).toEqual('value =');
24441
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24442
- });
24443
-
24444
- it('should be invalid if over max', function() {
24445
- setInput('2015-01');
24446
- expect(value.getText()).toContain('');
24447
- expect(valid.getText()).toContain('myForm.input.$valid = false');
24448
- });
24449
- </file>
24450
- </example>
24451
- */
24452
- 'month': createDateInputType('month', MONTH_REGEXP,
24453
- createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
24454
- 'yyyy-MM'),
24455
-
24456
- /**
24457
- * @ngdoc input
24458
- * @name input[number]
24459
- *
24460
- * @description
24461
- * Text input with number validation and transformation. Sets the `number` validation
24462
- * error if not a valid number.
24463
- *
24464
- * <div class="alert alert-warning">
24465
- * The model must always be of type `number` otherwise Angular will throw an error.
24466
- * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
24467
- * error docs for more information and an example of how to convert your model if necessary.
24468
- * </div>
24469
- *
24470
- * ## Issues with HTML5 constraint validation
24471
- *
24472
- * In browsers that follow the
24473
- * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
24474
- * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
24475
- * If a non-number is entered in the input, the browser will report the value as an empty string,
24476
- * which means the view / model values in `ngModel` and subsequently the scope value
24477
- * will also be an empty string.
24478
- *
24479
- *
24480
- * @param {string} ngModel Assignable angular expression to data-bind to.
24481
- * @param {string=} name Property name of the form under which the control is published.
24482
- * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
24483
- * Can be interpolated.
24484
- * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
24485
- * Can be interpolated.
24486
- * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`,
24487
- * but does not trigger HTML5 native validation. Takes an expression.
24488
- * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`,
24489
- * but does not trigger HTML5 native validation. Takes an expression.
24490
- * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint.
24491
- * Can be interpolated.
24492
- * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint,
24493
- * but does not trigger HTML5 native validation. Takes an expression.
24494
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24495
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24496
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24497
- * `required` when you want to data-bind to the `required` attribute.
24498
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
24499
- * minlength.
24500
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
24501
- * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
24502
- * any length.
24503
- * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
24504
- * that contains the regular expression body that will be converted to a regular expression
24505
- * as in the ngPattern directive.
24506
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
24507
- * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
24508
- * If the expression evaluates to a RegExp object, then this is used directly.
24509
- * If the expression evaluates to a string, then it will be converted to a RegExp
24510
- * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
24511
- * `new RegExp('^abc$')`.<br />
24512
- * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
24513
- * start at the index of the last search's match, thus not taking the whole input value into
24514
- * account.
24515
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24516
- * interaction with the input element.
24517
- *
24518
- * @example
24519
- <example name="number-input-directive" module="numberExample">
24520
- <file name="index.html">
24521
- <script>
24522
- angular.module('numberExample', [])
24523
- .controller('ExampleController', ['$scope', function($scope) {
24524
- $scope.example = {
24525
- value: 12
24526
- };
24527
- }]);
24528
- </script>
24529
- <form name="myForm" ng-controller="ExampleController">
24530
- <label>Number:
24531
- <input type="number" name="input" ng-model="example.value"
24532
- min="0" max="99" required>
24533
- </label>
24534
- <div role="alert">
24535
- <span class="error" ng-show="myForm.input.$error.required">
24536
- Required!</span>
24537
- <span class="error" ng-show="myForm.input.$error.number">
24538
- Not valid number!</span>
24539
- </div>
24540
- <tt>value = {{example.value}}</tt><br/>
24541
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24542
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24543
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24544
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24545
- </form>
24546
- </file>
24547
- <file name="protractor.js" type="protractor">
24548
- var value = element(by.binding('example.value'));
24549
- var valid = element(by.binding('myForm.input.$valid'));
24550
- var input = element(by.model('example.value'));
24551
-
24552
- it('should initialize to model', function() {
24553
- expect(value.getText()).toContain('12');
24554
- expect(valid.getText()).toContain('true');
24555
- });
24556
-
24557
- it('should be invalid if empty', function() {
24558
- input.clear();
24559
- input.sendKeys('');
24560
- expect(value.getText()).toEqual('value =');
24561
- expect(valid.getText()).toContain('false');
24562
- });
24563
-
24564
- it('should be invalid if over max', function() {
24565
- input.clear();
24566
- input.sendKeys('123');
24567
- expect(value.getText()).toEqual('value =');
24568
- expect(valid.getText()).toContain('false');
24569
- });
24570
- </file>
24571
- </example>
24572
- */
24573
- 'number': numberInputType,
24574
-
24575
-
24576
- /**
24577
- * @ngdoc input
24578
- * @name input[url]
24579
- *
24580
- * @description
24581
- * Text input with URL validation. Sets the `url` validation error key if the content is not a
24582
- * valid URL.
24583
- *
24584
- * <div class="alert alert-warning">
24585
- * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
24586
- * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
24587
- * the built-in validators (see the {@link guide/forms Forms guide})
24588
- * </div>
24589
- *
24590
- * @param {string} ngModel Assignable angular expression to data-bind to.
24591
- * @param {string=} name Property name of the form under which the control is published.
24592
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24593
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24594
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24595
- * `required` when you want to data-bind to the `required` attribute.
24596
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
24597
- * minlength.
24598
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
24599
- * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
24600
- * any length.
24601
- * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
24602
- * that contains the regular expression body that will be converted to a regular expression
24603
- * as in the ngPattern directive.
24604
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
24605
- * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
24606
- * If the expression evaluates to a RegExp object, then this is used directly.
24607
- * If the expression evaluates to a string, then it will be converted to a RegExp
24608
- * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
24609
- * `new RegExp('^abc$')`.<br />
24610
- * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
24611
- * start at the index of the last search's match, thus not taking the whole input value into
24612
- * account.
24613
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24614
- * interaction with the input element.
24615
- *
24616
- * @example
24617
- <example name="url-input-directive" module="urlExample">
24618
- <file name="index.html">
24619
- <script>
24620
- angular.module('urlExample', [])
24621
- .controller('ExampleController', ['$scope', function($scope) {
24622
- $scope.url = {
24623
- text: 'http://google.com'
24624
- };
24625
- }]);
24626
- </script>
24627
- <form name="myForm" ng-controller="ExampleController">
24628
- <label>URL:
24629
- <input type="url" name="input" ng-model="url.text" required>
24630
- <label>
24631
- <div role="alert">
24632
- <span class="error" ng-show="myForm.input.$error.required">
24633
- Required!</span>
24634
- <span class="error" ng-show="myForm.input.$error.url">
24635
- Not valid url!</span>
24636
- </div>
24637
- <tt>text = {{url.text}}</tt><br/>
24638
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24639
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24640
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24641
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24642
- <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
24643
- </form>
24644
- </file>
24645
- <file name="protractor.js" type="protractor">
24646
- var text = element(by.binding('url.text'));
24647
- var valid = element(by.binding('myForm.input.$valid'));
24648
- var input = element(by.model('url.text'));
24649
-
24650
- it('should initialize to model', function() {
24651
- expect(text.getText()).toContain('http://google.com');
24652
- expect(valid.getText()).toContain('true');
24653
- });
24654
-
24655
- it('should be invalid if empty', function() {
24656
- input.clear();
24657
- input.sendKeys('');
24658
-
24659
- expect(text.getText()).toEqual('text =');
24660
- expect(valid.getText()).toContain('false');
24661
- });
24662
-
24663
- it('should be invalid if not url', function() {
24664
- input.clear();
24665
- input.sendKeys('box');
24666
-
24667
- expect(valid.getText()).toContain('false');
24668
- });
24669
- </file>
24670
- </example>
24671
- */
24672
- 'url': urlInputType,
24673
-
24674
-
24675
- /**
24676
- * @ngdoc input
24677
- * @name input[email]
24678
- *
24679
- * @description
24680
- * Text input with email validation. Sets the `email` validation error key if not a valid email
24681
- * address.
24682
- *
24683
- * <div class="alert alert-warning">
24684
- * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
24685
- * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
24686
- * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
24687
- * </div>
24688
- *
24689
- * @param {string} ngModel Assignable angular expression to data-bind to.
24690
- * @param {string=} name Property name of the form under which the control is published.
24691
- * @param {string=} required Sets `required` validation error key if the value is not entered.
24692
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
24693
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
24694
- * `required` when you want to data-bind to the `required` attribute.
24695
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
24696
- * minlength.
24697
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
24698
- * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
24699
- * any length.
24700
- * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
24701
- * that contains the regular expression body that will be converted to a regular expression
24702
- * as in the ngPattern directive.
24703
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
24704
- * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
24705
- * If the expression evaluates to a RegExp object, then this is used directly.
24706
- * If the expression evaluates to a string, then it will be converted to a RegExp
24707
- * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
24708
- * `new RegExp('^abc$')`.<br />
24709
- * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
24710
- * start at the index of the last search's match, thus not taking the whole input value into
24711
- * account.
24712
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24713
- * interaction with the input element.
24714
- *
24715
- * @example
24716
- <example name="email-input-directive" module="emailExample">
24717
- <file name="index.html">
24718
- <script>
24719
- angular.module('emailExample', [])
24720
- .controller('ExampleController', ['$scope', function($scope) {
24721
- $scope.email = {
24722
- text: 'me@example.com'
24723
- };
24724
- }]);
24725
- </script>
24726
- <form name="myForm" ng-controller="ExampleController">
24727
- <label>Email:
24728
- <input type="email" name="input" ng-model="email.text" required>
24729
- </label>
24730
- <div role="alert">
24731
- <span class="error" ng-show="myForm.input.$error.required">
24732
- Required!</span>
24733
- <span class="error" ng-show="myForm.input.$error.email">
24734
- Not valid email!</span>
24735
- </div>
24736
- <tt>text = {{email.text}}</tt><br/>
24737
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
24738
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
24739
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24740
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24741
- <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
24742
- </form>
24743
- </file>
24744
- <file name="protractor.js" type="protractor">
24745
- var text = element(by.binding('email.text'));
24746
- var valid = element(by.binding('myForm.input.$valid'));
24747
- var input = element(by.model('email.text'));
24748
-
24749
- it('should initialize to model', function() {
24750
- expect(text.getText()).toContain('me@example.com');
24751
- expect(valid.getText()).toContain('true');
24752
- });
24753
-
24754
- it('should be invalid if empty', function() {
24755
- input.clear();
24756
- input.sendKeys('');
24757
- expect(text.getText()).toEqual('text =');
24758
- expect(valid.getText()).toContain('false');
24759
- });
24760
-
24761
- it('should be invalid if not email', function() {
24762
- input.clear();
24763
- input.sendKeys('xxx');
24764
-
24765
- expect(valid.getText()).toContain('false');
24766
- });
24767
- </file>
24768
- </example>
24769
- */
24770
- 'email': emailInputType,
24771
-
24772
-
24773
- /**
24774
- * @ngdoc input
24775
- * @name input[radio]
24776
- *
24777
- * @description
24778
- * HTML radio button.
24779
- *
24780
- * @param {string} ngModel Assignable angular expression to data-bind to.
24781
- * @param {string} value The value to which the `ngModel` expression should be set when selected.
24782
- * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
24783
- * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
24784
- * @param {string=} name Property name of the form under which the control is published.
24785
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24786
- * interaction with the input element.
24787
- * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
24788
- * is selected. Should be used instead of the `value` attribute if you need
24789
- * a non-string `ngModel` (`boolean`, `array`, ...).
24790
- *
24791
- * @example
24792
- <example name="radio-input-directive" module="radioExample">
24793
- <file name="index.html">
24794
- <script>
24795
- angular.module('radioExample', [])
24796
- .controller('ExampleController', ['$scope', function($scope) {
24797
- $scope.color = {
24798
- name: 'blue'
24799
- };
24800
- $scope.specialValue = {
24801
- "id": "12345",
24802
- "value": "green"
24803
- };
24804
- }]);
24805
- </script>
24806
- <form name="myForm" ng-controller="ExampleController">
24807
- <label>
24808
- <input type="radio" ng-model="color.name" value="red">
24809
- Red
24810
- </label><br/>
24811
- <label>
24812
- <input type="radio" ng-model="color.name" ng-value="specialValue">
24813
- Green
24814
- </label><br/>
24815
- <label>
24816
- <input type="radio" ng-model="color.name" value="blue">
24817
- Blue
24818
- </label><br/>
24819
- <tt>color = {{color.name | json}}</tt><br/>
24820
- </form>
24821
- Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
24822
- </file>
24823
- <file name="protractor.js" type="protractor">
24824
- it('should change state', function() {
24825
- var inputs = element.all(by.model('color.name'));
24826
- var color = element(by.binding('color.name'));
24827
-
24828
- expect(color.getText()).toContain('blue');
24829
-
24830
- inputs.get(0).click();
24831
- expect(color.getText()).toContain('red');
24832
-
24833
- inputs.get(1).click();
24834
- expect(color.getText()).toContain('green');
24835
- });
24836
- </file>
24837
- </example>
24838
- */
24839
- 'radio': radioInputType,
24840
-
24841
- /**
24842
- * @ngdoc input
24843
- * @name input[range]
24844
- *
24845
- * @description
24846
- * Native range input with validation and transformation.
24847
- *
24848
- * The model for the range input must always be a `Number`.
24849
- *
24850
- * IE9 and other browsers that do not support the `range` type fall back
24851
- * to a text input without any default values for `min`, `max` and `step`. Model binding,
24852
- * validation and number parsing are nevertheless supported.
24853
- *
24854
- * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
24855
- * in a way that never allows the input to hold an invalid value. That means:
24856
- * - any non-numerical value is set to `(max + min) / 2`.
24857
- * - any numerical value that is less than the current min val, or greater than the current max val
24858
- * is set to the min / max val respectively.
24859
- * - additionally, the current `step` is respected, so the nearest value that satisfies a step
24860
- * is used.
24861
- *
24862
- * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range))
24863
- * for more info.
24864
- *
24865
- * This has the following consequences for Angular:
24866
- *
24867
- * Since the element value should always reflect the current model value, a range input
24868
- * will set the bound ngModel expression to the value that the browser has set for the
24869
- * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
24870
- * if the application sets `model.value = null`, the browser will set the input to `'50'`.
24871
- * Angular will then set the model to `50`, to prevent input and model value being out of sync.
24872
- *
24873
- * That means the model for range will immediately be set to `50` after `ngModel` has been
24874
- * initialized. It also means a range input can never have the required error.
24875
- *
24876
- * This does not only affect changes to the model value, but also to the values of the `min`,
24877
- * `max`, and `step` attributes. When these change in a way that will cause the browser to modify
24878
- * the input value, Angular will also update the model value.
24879
- *
24880
- * Automatic value adjustment also means that a range input element can never have the `required`,
24881
- * `min`, or `max` errors.
24882
- *
24883
- * However, `step` is currently only fully implemented by Firefox. Other browsers have problems
24884
- * when the step value changes dynamically - they do not adjust the element value correctly, but
24885
- * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step`
24886
- * error on the input, and set the model to `undefined`.
24887
- *
24888
- * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
24889
- * not set the `min` and `max` attributes, which means that the browser won't automatically adjust
24890
- * the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
24891
- *
24892
- * @param {string} ngModel Assignable angular expression to data-bind to.
24893
- * @param {string=} name Property name of the form under which the control is published.
24894
- * @param {string=} min Sets the `min` validation to ensure that the value entered is greater
24895
- * than `min`. Can be interpolated.
24896
- * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
24897
- * Can be interpolated.
24898
- * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
24899
- * Can be interpolated.
24900
- * @param {string=} ngChange Angular expression to be executed when the ngModel value changes due
24901
- * to user interaction with the input element.
24902
- * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the
24903
- * element. **Note** : `ngChecked` should not be used alongside `ngModel`.
24904
- * Checkout {@link ng.directive:ngChecked ngChecked} for usage.
24905
- *
24906
- * @example
24907
- <example name="range-input-directive" module="rangeExample">
24908
- <file name="index.html">
24909
- <script>
24910
- angular.module('rangeExample', [])
24911
- .controller('ExampleController', ['$scope', function($scope) {
24912
- $scope.value = 75;
24913
- $scope.min = 10;
24914
- $scope.max = 90;
24915
- }]);
24916
- </script>
24917
- <form name="myForm" ng-controller="ExampleController">
24918
-
24919
- Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
24920
- <hr>
24921
- Model as number: <input type="number" ng-model="value"><br>
24922
- Min: <input type="number" ng-model="min"><br>
24923
- Max: <input type="number" ng-model="max"><br>
24924
- value = <code>{{value}}</code><br/>
24925
- myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
24926
- myForm.range.$error = <code>{{myForm.range.$error}}</code>
24927
- </form>
24928
- </file>
24929
- </example>
24930
-
24931
- * ## Range Input with ngMin & ngMax attributes
24932
-
24933
- * @example
24934
- <example name="range-input-directive-ng" module="rangeExample">
24935
- <file name="index.html">
24936
- <script>
24937
- angular.module('rangeExample', [])
24938
- .controller('ExampleController', ['$scope', function($scope) {
24939
- $scope.value = 75;
24940
- $scope.min = 10;
24941
- $scope.max = 90;
24942
- }]);
24943
- </script>
24944
- <form name="myForm" ng-controller="ExampleController">
24945
- Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
24946
- <hr>
24947
- Model as number: <input type="number" ng-model="value"><br>
24948
- Min: <input type="number" ng-model="min"><br>
24949
- Max: <input type="number" ng-model="max"><br>
24950
- value = <code>{{value}}</code><br/>
24951
- myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
24952
- myForm.range.$error = <code>{{myForm.range.$error}}</code>
24953
- </form>
24954
- </file>
24955
- </example>
24956
-
24957
- */
24958
- 'range': rangeInputType,
24959
-
24960
- /**
24961
- * @ngdoc input
24962
- * @name input[checkbox]
24963
- *
24964
- * @description
24965
- * HTML checkbox.
24966
- *
24967
- * @param {string} ngModel Assignable angular expression to data-bind to.
24968
- * @param {string=} name Property name of the form under which the control is published.
24969
- * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
24970
- * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
24971
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
24972
- * interaction with the input element.
24973
- *
24974
- * @example
24975
- <example name="checkbox-input-directive" module="checkboxExample">
24976
- <file name="index.html">
24977
- <script>
24978
- angular.module('checkboxExample', [])
24979
- .controller('ExampleController', ['$scope', function($scope) {
24980
- $scope.checkboxModel = {
24981
- value1 : true,
24982
- value2 : 'YES'
24983
- };
24984
- }]);
24985
- </script>
24986
- <form name="myForm" ng-controller="ExampleController">
24987
- <label>Value1:
24988
- <input type="checkbox" ng-model="checkboxModel.value1">
24989
- </label><br/>
24990
- <label>Value2:
24991
- <input type="checkbox" ng-model="checkboxModel.value2"
24992
- ng-true-value="'YES'" ng-false-value="'NO'">
24993
- </label><br/>
24994
- <tt>value1 = {{checkboxModel.value1}}</tt><br/>
24995
- <tt>value2 = {{checkboxModel.value2}}</tt><br/>
24996
- </form>
24997
- </file>
24998
- <file name="protractor.js" type="protractor">
24999
- it('should change state', function() {
25000
- var value1 = element(by.binding('checkboxModel.value1'));
25001
- var value2 = element(by.binding('checkboxModel.value2'));
25002
-
25003
- expect(value1.getText()).toContain('true');
25004
- expect(value2.getText()).toContain('YES');
25005
-
25006
- element(by.model('checkboxModel.value1')).click();
25007
- element(by.model('checkboxModel.value2')).click();
25008
-
25009
- expect(value1.getText()).toContain('false');
25010
- expect(value2.getText()).toContain('NO');
25011
- });
25012
- </file>
25013
- </example>
25014
- */
25015
- 'checkbox': checkboxInputType,
25016
-
25017
- 'hidden': noop,
25018
- 'button': noop,
25019
- 'submit': noop,
25020
- 'reset': noop,
25021
- 'file': noop
25022
- };
25023
-
25024
- function stringBasedInputType(ctrl) {
25025
- ctrl.$formatters.push(function(value) {
25026
- return ctrl.$isEmpty(value) ? value : value.toString();
25027
- });
25028
- }
25029
-
25030
- function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25031
- baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25032
- stringBasedInputType(ctrl);
25033
- }
25034
-
25035
- function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25036
- var type = lowercase(element[0].type);
25037
-
25038
- // In composition mode, users are still inputting intermediate text buffer,
25039
- // hold the listener until composition is done.
25040
- // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
25041
- if (!$sniffer.android) {
25042
- var composing = false;
25043
-
25044
- element.on('compositionstart', function() {
25045
- composing = true;
25046
- });
25047
-
25048
- element.on('compositionend', function() {
25049
- composing = false;
25050
- listener();
25051
- });
25052
- }
25053
-
25054
- var timeout;
25055
-
25056
- var listener = function(ev) {
25057
- if (timeout) {
25058
- $browser.defer.cancel(timeout);
25059
- timeout = null;
25060
- }
25061
- if (composing) return;
25062
- var value = element.val(),
25063
- event = ev && ev.type;
25064
-
25065
- // By default we will trim the value
25066
- // If the attribute ng-trim exists we will avoid trimming
25067
- // If input type is 'password', the value is never trimmed
25068
- if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
25069
- value = trim(value);
25070
- }
25071
-
25072
- // If a control is suffering from bad input (due to native validators), browsers discard its
25073
- // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
25074
- // control's value is the same empty value twice in a row.
25075
- if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
25076
- ctrl.$setViewValue(value, event);
25077
- }
25078
- };
25079
-
25080
- // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
25081
- // input event on backspace, delete or cut
25082
- if ($sniffer.hasEvent('input')) {
25083
- element.on('input', listener);
25084
- } else {
25085
- var deferListener = function(ev, input, origValue) {
25086
- if (!timeout) {
25087
- timeout = $browser.defer(function() {
25088
- timeout = null;
25089
- if (!input || input.value !== origValue) {
25090
- listener(ev);
25091
- }
25092
- });
25093
- }
25094
- };
25095
-
25096
- element.on('keydown', /** @this */ function(event) {
25097
- var key = event.keyCode;
25098
-
25099
- // ignore
25100
- // command modifiers arrows
25101
- if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
25102
-
25103
- deferListener(event, this, this.value);
25104
- });
25105
-
25106
- // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
25107
- if ($sniffer.hasEvent('paste')) {
25108
- element.on('paste cut', deferListener);
25109
- }
25110
- }
25111
-
25112
- // if user paste into input using mouse on older browser
25113
- // or form autocomplete on newer browser, we need "change" event to catch it
25114
- element.on('change', listener);
25115
-
25116
- // Some native input types (date-family) have the ability to change validity without
25117
- // firing any input/change events.
25118
- // For these event types, when native validators are present and the browser supports the type,
25119
- // check for validity changes on various DOM events.
25120
- if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
25121
- element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) {
25122
- if (!timeout) {
25123
- var validity = this[VALIDITY_STATE_PROPERTY];
25124
- var origBadInput = validity.badInput;
25125
- var origTypeMismatch = validity.typeMismatch;
25126
- timeout = $browser.defer(function() {
25127
- timeout = null;
25128
- if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
25129
- listener(ev);
25130
- }
25131
- });
25132
- }
25133
- });
25134
- }
25135
-
25136
- ctrl.$render = function() {
25137
- // Workaround for Firefox validation #12102.
25138
- var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
25139
- if (element.val() !== value) {
25140
- element.val(value);
25141
- }
25142
- };
25143
- }
25144
-
25145
- function weekParser(isoWeek, existingDate) {
25146
- if (isDate(isoWeek)) {
25147
- return isoWeek;
25148
- }
25149
-
25150
- if (isString(isoWeek)) {
25151
- WEEK_REGEXP.lastIndex = 0;
25152
- var parts = WEEK_REGEXP.exec(isoWeek);
25153
- if (parts) {
25154
- var year = +parts[1],
25155
- week = +parts[2],
25156
- hours = 0,
25157
- minutes = 0,
25158
- seconds = 0,
25159
- milliseconds = 0,
25160
- firstThurs = getFirstThursdayOfYear(year),
25161
- addDays = (week - 1) * 7;
25162
-
25163
- if (existingDate) {
25164
- hours = existingDate.getHours();
25165
- minutes = existingDate.getMinutes();
25166
- seconds = existingDate.getSeconds();
25167
- milliseconds = existingDate.getMilliseconds();
25168
- }
25169
-
25170
- return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
25171
- }
25172
- }
25173
-
25174
- return NaN;
25175
- }
25176
-
25177
- function createDateParser(regexp, mapping) {
25178
- return function(iso, date) {
25179
- var parts, map;
25180
-
25181
- if (isDate(iso)) {
25182
- return iso;
25183
- }
25184
-
25185
- if (isString(iso)) {
25186
- // When a date is JSON'ified to wraps itself inside of an extra
25187
- // set of double quotes. This makes the date parsing code unable
25188
- // to match the date string and parse it as a date.
25189
- if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') {
25190
- iso = iso.substring(1, iso.length - 1);
25191
- }
25192
- if (ISO_DATE_REGEXP.test(iso)) {
25193
- return new Date(iso);
25194
- }
25195
- regexp.lastIndex = 0;
25196
- parts = regexp.exec(iso);
25197
-
25198
- if (parts) {
25199
- parts.shift();
25200
- if (date) {
25201
- map = {
25202
- yyyy: date.getFullYear(),
25203
- MM: date.getMonth() + 1,
25204
- dd: date.getDate(),
25205
- HH: date.getHours(),
25206
- mm: date.getMinutes(),
25207
- ss: date.getSeconds(),
25208
- sss: date.getMilliseconds() / 1000
25209
- };
25210
- } else {
25211
- map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
25212
- }
25213
-
25214
- forEach(parts, function(part, index) {
25215
- if (index < mapping.length) {
25216
- map[mapping[index]] = +part;
25217
- }
25218
- });
25219
- return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
25220
- }
25221
- }
25222
-
25223
- return NaN;
25224
- };
25225
- }
25226
-
25227
- function createDateInputType(type, regexp, parseDate, format) {
25228
- return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
25229
- badInputChecker(scope, element, attr, ctrl);
25230
- baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25231
- var timezone = ctrl && ctrl.$options.getOption('timezone');
25232
- var previousDate;
25233
-
25234
- ctrl.$$parserName = type;
25235
- ctrl.$parsers.push(function(value) {
25236
- if (ctrl.$isEmpty(value)) return null;
25237
- if (regexp.test(value)) {
25238
- // Note: We cannot read ctrl.$modelValue, as there might be a different
25239
- // parser/formatter in the processing chain so that the model
25240
- // contains some different data format!
25241
- var parsedDate = parseDate(value, previousDate);
25242
- if (timezone) {
25243
- parsedDate = convertTimezoneToLocal(parsedDate, timezone);
25244
- }
25245
- return parsedDate;
25246
- }
25247
- return undefined;
25248
- });
25249
-
25250
- ctrl.$formatters.push(function(value) {
25251
- if (value && !isDate(value)) {
25252
- throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
25253
- }
25254
- if (isValidDate(value)) {
25255
- previousDate = value;
25256
- if (previousDate && timezone) {
25257
- previousDate = convertTimezoneToLocal(previousDate, timezone, true);
25258
- }
25259
- return $filter('date')(value, format, timezone);
25260
- } else {
25261
- previousDate = null;
25262
- return '';
25263
- }
25264
- });
25265
-
25266
- if (isDefined(attr.min) || attr.ngMin) {
25267
- var minVal;
25268
- ctrl.$validators.min = function(value) {
25269
- return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
25270
- };
25271
- attr.$observe('min', function(val) {
25272
- minVal = parseObservedDateValue(val);
25273
- ctrl.$validate();
25274
- });
25275
- }
25276
-
25277
- if (isDefined(attr.max) || attr.ngMax) {
25278
- var maxVal;
25279
- ctrl.$validators.max = function(value) {
25280
- return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
25281
- };
25282
- attr.$observe('max', function(val) {
25283
- maxVal = parseObservedDateValue(val);
25284
- ctrl.$validate();
25285
- });
25286
- }
25287
-
25288
- function isValidDate(value) {
25289
- // Invalid Date: getTime() returns NaN
25290
- return value && !(value.getTime && value.getTime() !== value.getTime());
25291
- }
25292
-
25293
- function parseObservedDateValue(val) {
25294
- return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
25295
- }
25296
- };
25297
- }
25298
-
25299
- function badInputChecker(scope, element, attr, ctrl) {
25300
- var node = element[0];
25301
- var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
25302
- if (nativeValidation) {
25303
- ctrl.$parsers.push(function(value) {
25304
- var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
25305
- return validity.badInput || validity.typeMismatch ? undefined : value;
25306
- });
25307
- }
25308
- }
25309
-
25310
- function numberFormatterParser(ctrl) {
25311
- ctrl.$$parserName = 'number';
25312
- ctrl.$parsers.push(function(value) {
25313
- if (ctrl.$isEmpty(value)) return null;
25314
- if (NUMBER_REGEXP.test(value)) return parseFloat(value);
25315
- return undefined;
25316
- });
25317
-
25318
- ctrl.$formatters.push(function(value) {
25319
- if (!ctrl.$isEmpty(value)) {
25320
- if (!isNumber(value)) {
25321
- throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
25322
- }
25323
- value = value.toString();
25324
- }
25325
- return value;
25326
- });
25327
- }
25328
-
25329
- function parseNumberAttrVal(val) {
25330
- if (isDefined(val) && !isNumber(val)) {
25331
- val = parseFloat(val);
25332
- }
25333
- return !isNumberNaN(val) ? val : undefined;
25334
- }
25335
-
25336
- function isNumberInteger(num) {
25337
- // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066
25338
- // (minus the assumption that `num` is a number)
25339
-
25340
- // eslint-disable-next-line no-bitwise
25341
- return (num | 0) === num;
25342
- }
25343
-
25344
- function countDecimals(num) {
25345
- var numString = num.toString();
25346
- var decimalSymbolIndex = numString.indexOf('.');
25347
-
25348
- if (decimalSymbolIndex === -1) {
25349
- if (-1 < num && num < 1) {
25350
- // It may be in the exponential notation format (`1e-X`)
25351
- var match = /e-(\d+)$/.exec(numString);
25352
-
25353
- if (match) {
25354
- return Number(match[1]);
25355
- }
25356
- }
25357
-
25358
- return 0;
25359
- }
25360
-
25361
- return numString.length - decimalSymbolIndex - 1;
25362
- }
25363
-
25364
- function isValidForStep(viewValue, stepBase, step) {
25365
- // At this point `stepBase` and `step` are expected to be non-NaN values
25366
- // and `viewValue` is expected to be a valid stringified number.
25367
- var value = Number(viewValue);
25368
-
25369
- var isNonIntegerValue = !isNumberInteger(value);
25370
- var isNonIntegerStepBase = !isNumberInteger(stepBase);
25371
- var isNonIntegerStep = !isNumberInteger(step);
25372
-
25373
- // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
25374
- // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
25375
- if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
25376
- var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
25377
- var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
25378
- var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;
25379
-
25380
- var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals);
25381
- var multiplier = Math.pow(10, decimalCount);
25382
-
25383
- value = value * multiplier;
25384
- stepBase = stepBase * multiplier;
25385
- step = step * multiplier;
25386
-
25387
- if (isNonIntegerValue) value = Math.round(value);
25388
- if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
25389
- if (isNonIntegerStep) step = Math.round(step);
25390
- }
25391
-
25392
- return (value - stepBase) % step === 0;
25393
- }
25394
-
25395
- function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25396
- badInputChecker(scope, element, attr, ctrl);
25397
- numberFormatterParser(ctrl);
25398
- baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25399
-
25400
- var minVal;
25401
- var maxVal;
25402
-
25403
- if (isDefined(attr.min) || attr.ngMin) {
25404
- ctrl.$validators.min = function(value) {
25405
- return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
25406
- };
25407
-
25408
- attr.$observe('min', function(val) {
25409
- minVal = parseNumberAttrVal(val);
25410
- // TODO(matsko): implement validateLater to reduce number of validations
25411
- ctrl.$validate();
25412
- });
25413
- }
25414
-
25415
- if (isDefined(attr.max) || attr.ngMax) {
25416
- ctrl.$validators.max = function(value) {
25417
- return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
25418
- };
25419
-
25420
- attr.$observe('max', function(val) {
25421
- maxVal = parseNumberAttrVal(val);
25422
- // TODO(matsko): implement validateLater to reduce number of validations
25423
- ctrl.$validate();
25424
- });
25425
- }
25426
-
25427
- if (isDefined(attr.step) || attr.ngStep) {
25428
- var stepVal;
25429
- ctrl.$validators.step = function(modelValue, viewValue) {
25430
- return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
25431
- isValidForStep(viewValue, minVal || 0, stepVal);
25432
- };
25433
-
25434
- attr.$observe('step', function(val) {
25435
- stepVal = parseNumberAttrVal(val);
25436
- // TODO(matsko): implement validateLater to reduce number of validations
25437
- ctrl.$validate();
25438
- });
25439
- }
25440
- }
25441
-
25442
- function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25443
- badInputChecker(scope, element, attr, ctrl);
25444
- numberFormatterParser(ctrl);
25445
- baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25446
-
25447
- var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
25448
- minVal = supportsRange ? 0 : undefined,
25449
- maxVal = supportsRange ? 100 : undefined,
25450
- stepVal = supportsRange ? 1 : undefined,
25451
- validity = element[0].validity,
25452
- hasMinAttr = isDefined(attr.min),
25453
- hasMaxAttr = isDefined(attr.max),
25454
- hasStepAttr = isDefined(attr.step);
25455
-
25456
- var originalRender = ctrl.$render;
25457
-
25458
- ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ?
25459
- //Browsers that implement range will set these values automatically, but reading the adjusted values after
25460
- //$render would cause the min / max validators to be applied with the wrong value
25461
- function rangeRender() {
25462
- originalRender();
25463
- ctrl.$setViewValue(element.val());
25464
- } :
25465
- originalRender;
25466
-
25467
- if (hasMinAttr) {
25468
- ctrl.$validators.min = supportsRange ?
25469
- // Since all browsers set the input to a valid value, we don't need to check validity
25470
- function noopMinValidator() { return true; } :
25471
- // non-support browsers validate the min val
25472
- function minValidator(modelValue, viewValue) {
25473
- return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
25474
- };
25475
-
25476
- setInitialValueAndObserver('min', minChange);
25477
- }
25478
-
25479
- if (hasMaxAttr) {
25480
- ctrl.$validators.max = supportsRange ?
25481
- // Since all browsers set the input to a valid value, we don't need to check validity
25482
- function noopMaxValidator() { return true; } :
25483
- // non-support browsers validate the max val
25484
- function maxValidator(modelValue, viewValue) {
25485
- return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
25486
- };
25487
-
25488
- setInitialValueAndObserver('max', maxChange);
25489
- }
25490
-
25491
- if (hasStepAttr) {
25492
- ctrl.$validators.step = supportsRange ?
25493
- function nativeStepValidator() {
25494
- // Currently, only FF implements the spec on step change correctly (i.e. adjusting the
25495
- // input element value to a valid value). It's possible that other browsers set the stepMismatch
25496
- // validity error instead, so we can at least report an error in that case.
25497
- return !validity.stepMismatch;
25498
- } :
25499
- // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
25500
- function stepValidator(modelValue, viewValue) {
25501
- return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
25502
- isValidForStep(viewValue, minVal || 0, stepVal);
25503
- };
25504
-
25505
- setInitialValueAndObserver('step', stepChange);
25506
- }
25507
-
25508
- function setInitialValueAndObserver(htmlAttrName, changeFn) {
25509
- // interpolated attributes set the attribute value only after a digest, but we need the
25510
- // attribute value when the input is first rendered, so that the browser can adjust the
25511
- // input value based on the min/max value
25512
- element.attr(htmlAttrName, attr[htmlAttrName]);
25513
- attr.$observe(htmlAttrName, changeFn);
25514
- }
25515
-
25516
- function minChange(val) {
25517
- minVal = parseNumberAttrVal(val);
25518
- // ignore changes before model is initialized
25519
- if (isNumberNaN(ctrl.$modelValue)) {
25520
- return;
25521
- }
25522
-
25523
- if (supportsRange) {
25524
- var elVal = element.val();
25525
- // IE11 doesn't set the el val correctly if the minVal is greater than the element value
25526
- if (minVal > elVal) {
25527
- elVal = minVal;
25528
- element.val(elVal);
25529
- }
25530
- ctrl.$setViewValue(elVal);
25531
- } else {
25532
- // TODO(matsko): implement validateLater to reduce number of validations
25533
- ctrl.$validate();
25534
- }
25535
- }
25536
-
25537
- function maxChange(val) {
25538
- maxVal = parseNumberAttrVal(val);
25539
- // ignore changes before model is initialized
25540
- if (isNumberNaN(ctrl.$modelValue)) {
25541
- return;
25542
- }
25543
-
25544
- if (supportsRange) {
25545
- var elVal = element.val();
25546
- // IE11 doesn't set the el val correctly if the maxVal is less than the element value
25547
- if (maxVal < elVal) {
25548
- element.val(maxVal);
25549
- // IE11 and Chrome don't set the value to the minVal when max < min
25550
- elVal = maxVal < minVal ? minVal : maxVal;
25551
- }
25552
- ctrl.$setViewValue(elVal);
25553
- } else {
25554
- // TODO(matsko): implement validateLater to reduce number of validations
25555
- ctrl.$validate();
25556
- }
25557
- }
25558
-
25559
- function stepChange(val) {
25560
- stepVal = parseNumberAttrVal(val);
25561
- // ignore changes before model is initialized
25562
- if (isNumberNaN(ctrl.$modelValue)) {
25563
- return;
25564
- }
25565
-
25566
- // Some browsers don't adjust the input value correctly, but set the stepMismatch error
25567
- if (supportsRange && ctrl.$viewValue !== element.val()) {
25568
- ctrl.$setViewValue(element.val());
25569
- } else {
25570
- // TODO(matsko): implement validateLater to reduce number of validations
25571
- ctrl.$validate();
25572
- }
25573
- }
25574
- }
25575
-
25576
- function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25577
- // Note: no badInputChecker here by purpose as `url` is only a validation
25578
- // in browsers, i.e. we can always read out input.value even if it is not valid!
25579
- baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25580
- stringBasedInputType(ctrl);
25581
-
25582
- ctrl.$$parserName = 'url';
25583
- ctrl.$validators.url = function(modelValue, viewValue) {
25584
- var value = modelValue || viewValue;
25585
- return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
25586
- };
25587
- }
25588
-
25589
- function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25590
- // Note: no badInputChecker here by purpose as `url` is only a validation
25591
- // in browsers, i.e. we can always read out input.value even if it is not valid!
25592
- baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
25593
- stringBasedInputType(ctrl);
25594
-
25595
- ctrl.$$parserName = 'email';
25596
- ctrl.$validators.email = function(modelValue, viewValue) {
25597
- var value = modelValue || viewValue;
25598
- return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
25599
- };
25600
- }
25601
-
25602
- function radioInputType(scope, element, attr, ctrl) {
25603
- var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false';
25604
- // make the name unique, if not defined
25605
- if (isUndefined(attr.name)) {
25606
- element.attr('name', nextUid());
25607
- }
25608
-
25609
- var listener = function(ev) {
25610
- var value;
25611
- if (element[0].checked) {
25612
- value = attr.value;
25613
- if (doTrim) {
25614
- value = trim(value);
25615
- }
25616
- ctrl.$setViewValue(value, ev && ev.type);
25617
- }
25618
- };
25619
-
25620
- element.on('click', listener);
25621
-
25622
- ctrl.$render = function() {
25623
- var value = attr.value;
25624
- if (doTrim) {
25625
- value = trim(value);
25626
- }
25627
- element[0].checked = (value === ctrl.$viewValue);
25628
- };
25629
-
25630
- attr.$observe('value', ctrl.$render);
25631
- }
25632
-
25633
- function parseConstantExpr($parse, context, name, expression, fallback) {
25634
- var parseFn;
25635
- if (isDefined(expression)) {
25636
- parseFn = $parse(expression);
25637
- if (!parseFn.constant) {
25638
- throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
25639
- '`{1}`.', name, expression);
25640
- }
25641
- return parseFn(context);
25642
- }
25643
- return fallback;
25644
- }
25645
-
25646
- function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
25647
- var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
25648
- var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
25649
-
25650
- var listener = function(ev) {
25651
- ctrl.$setViewValue(element[0].checked, ev && ev.type);
25652
- };
25653
-
25654
- element.on('click', listener);
25655
-
25656
- ctrl.$render = function() {
25657
- element[0].checked = ctrl.$viewValue;
25658
- };
25659
-
25660
- // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
25661
- // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
25662
- // it to a boolean.
25663
- ctrl.$isEmpty = function(value) {
25664
- return value === false;
25665
- };
25666
-
25667
- ctrl.$formatters.push(function(value) {
25668
- return equals(value, trueValue);
25669
- });
25670
-
25671
- ctrl.$parsers.push(function(value) {
25672
- return value ? trueValue : falseValue;
25673
- });
25674
- }
25675
-
25676
-
25677
- /**
25678
- * @ngdoc directive
25679
- * @name textarea
25680
- * @restrict E
25681
- *
25682
- * @description
25683
- * HTML textarea element control with angular data-binding. The data-binding and validation
25684
- * properties of this element are exactly the same as those of the
25685
- * {@link ng.directive:input input element}.
25686
- *
25687
- * @param {string} ngModel Assignable angular expression to data-bind to.
25688
- * @param {string=} name Property name of the form under which the control is published.
25689
- * @param {string=} required Sets `required` validation error key if the value is not entered.
25690
- * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
25691
- * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
25692
- * `required` when you want to data-bind to the `required` attribute.
25693
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
25694
- * minlength.
25695
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
25696
- * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
25697
- * length.
25698
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
25699
- * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
25700
- * If the expression evaluates to a RegExp object, then this is used directly.
25701
- * If the expression evaluates to a string, then it will be converted to a RegExp
25702
- * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
25703
- * `new RegExp('^abc$')`.<br />
25704
- * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
25705
- * start at the index of the last search's match, thus not taking the whole input value into
25706
- * account.
25707
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
25708
- * interaction with the input element.
25709
- * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
25710
- *
25711
- * @knownIssue
25712
- *
25713
- * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily
25714
- * insert the placeholder value as the textarea's content. If the placeholder value contains
25715
- * interpolation (`{{ ... }}`), an error will be logged in the console when Angular tries to update
25716
- * the value of the by-then-removed text node. This doesn't affect the functionality of the
25717
- * textarea, but can be undesirable.
25718
- *
25719
- * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of
25720
- * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can
25721
- * find more details on `ngAttr` in the
25722
- * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the
25723
- * Developer Guide.
25724
- */
25725
-
25726
-
25727
- /**
25728
- * @ngdoc directive
25729
- * @name input
25730
- * @restrict E
25731
- *
25732
- * @description
25733
- * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
25734
- * input state control, and validation.
25735
- * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
25736
- *
25737
- * <div class="alert alert-warning">
25738
- * **Note:** Not every feature offered is available for all input types.
25739
- * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
25740
- * </div>
25741
- *
25742
- * @param {string} ngModel Assignable angular expression to data-bind to.
25743
- * @param {string=} name Property name of the form under which the control is published.
25744
- * @param {string=} required Sets `required` validation error key if the value is not entered.
25745
- * @param {boolean=} ngRequired Sets `required` attribute if set to true
25746
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
25747
- * minlength.
25748
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
25749
- * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
25750
- * length.
25751
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
25752
- * value does not match a RegExp found by evaluating the Angular expression given in the attribute value.
25753
- * If the expression evaluates to a RegExp object, then this is used directly.
25754
- * If the expression evaluates to a string, then it will be converted to a RegExp
25755
- * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
25756
- * `new RegExp('^abc$')`.<br />
25757
- * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
25758
- * start at the index of the last search's match, thus not taking the whole input value into
25759
- * account.
25760
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
25761
- * interaction with the input element.
25762
- * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
25763
- * This parameter is ignored for input[type=password] controls, which will never trim the
25764
- * input.
25765
- *
25766
- * @example
25767
- <example name="input-directive" module="inputExample">
25768
- <file name="index.html">
25769
- <script>
25770
- angular.module('inputExample', [])
25771
- .controller('ExampleController', ['$scope', function($scope) {
25772
- $scope.user = {name: 'guest', last: 'visitor'};
25773
- }]);
25774
- </script>
25775
- <div ng-controller="ExampleController">
25776
- <form name="myForm">
25777
- <label>
25778
- User name:
25779
- <input type="text" name="userName" ng-model="user.name" required>
25780
- </label>
25781
- <div role="alert">
25782
- <span class="error" ng-show="myForm.userName.$error.required">
25783
- Required!</span>
25784
- </div>
25785
- <label>
25786
- Last name:
25787
- <input type="text" name="lastName" ng-model="user.last"
25788
- ng-minlength="3" ng-maxlength="10">
25789
- </label>
25790
- <div role="alert">
25791
- <span class="error" ng-show="myForm.lastName.$error.minlength">
25792
- Too short!</span>
25793
- <span class="error" ng-show="myForm.lastName.$error.maxlength">
25794
- Too long!</span>
25795
- </div>
25796
- </form>
25797
- <hr>
25798
- <tt>user = {{user}}</tt><br/>
25799
- <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
25800
- <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
25801
- <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
25802
- <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
25803
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
25804
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25805
- <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
25806
- <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
25807
- </div>
25808
- </file>
25809
- <file name="protractor.js" type="protractor">
25810
- var user = element(by.exactBinding('user'));
25811
- var userNameValid = element(by.binding('myForm.userName.$valid'));
25812
- var lastNameValid = element(by.binding('myForm.lastName.$valid'));
25813
- var lastNameError = element(by.binding('myForm.lastName.$error'));
25814
- var formValid = element(by.binding('myForm.$valid'));
25815
- var userNameInput = element(by.model('user.name'));
25816
- var userLastInput = element(by.model('user.last'));
25817
-
25818
- it('should initialize to model', function() {
25819
- expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
25820
- expect(userNameValid.getText()).toContain('true');
25821
- expect(formValid.getText()).toContain('true');
25822
- });
25823
-
25824
- it('should be invalid if empty when required', function() {
25825
- userNameInput.clear();
25826
- userNameInput.sendKeys('');
25827
-
25828
- expect(user.getText()).toContain('{"last":"visitor"}');
25829
- expect(userNameValid.getText()).toContain('false');
25830
- expect(formValid.getText()).toContain('false');
25831
- });
25832
-
25833
- it('should be valid if empty when min length is set', function() {
25834
- userLastInput.clear();
25835
- userLastInput.sendKeys('');
25836
-
25837
- expect(user.getText()).toContain('{"name":"guest","last":""}');
25838
- expect(lastNameValid.getText()).toContain('true');
25839
- expect(formValid.getText()).toContain('true');
25840
- });
25841
-
25842
- it('should be invalid if less than required min length', function() {
25843
- userLastInput.clear();
25844
- userLastInput.sendKeys('xx');
25845
-
25846
- expect(user.getText()).toContain('{"name":"guest"}');
25847
- expect(lastNameValid.getText()).toContain('false');
25848
- expect(lastNameError.getText()).toContain('minlength');
25849
- expect(formValid.getText()).toContain('false');
25850
- });
25851
-
25852
- it('should be invalid if longer than max length', function() {
25853
- userLastInput.clear();
25854
- userLastInput.sendKeys('some ridiculously long name');
25855
-
25856
- expect(user.getText()).toContain('{"name":"guest"}');
25857
- expect(lastNameValid.getText()).toContain('false');
25858
- expect(lastNameError.getText()).toContain('maxlength');
25859
- expect(formValid.getText()).toContain('false');
25860
- });
25861
- </file>
25862
- </example>
25863
- */
25864
- var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
25865
- function($browser, $sniffer, $filter, $parse) {
25866
- return {
25867
- restrict: 'E',
25868
- require: ['?ngModel'],
25869
- link: {
25870
- pre: function(scope, element, attr, ctrls) {
25871
- if (ctrls[0]) {
25872
- (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
25873
- $browser, $filter, $parse);
25874
- }
25875
- }
25876
- }
25877
- };
25878
- }];
25879
-
25880
-
25881
-
25882
- var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
25883
- /**
25884
- * @ngdoc directive
25885
- * @name ngValue
25886
- *
25887
- * @description
25888
- * Binds the given expression to the value of the element.
25889
- *
25890
- * It is mainly used on {@link input[radio] `input[radio]`} and option elements,
25891
- * so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its
25892
- * {@link select `select`} parent element) is set to the bound value. It is especially useful
25893
- * for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below.
25894
- *
25895
- * It can also be used to achieve one-way binding of a given expression to an input element
25896
- * such as an `input[text]` or a `textarea`, when that element does not use ngModel.
25897
- *
25898
- * @element input
25899
- * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
25900
- * and `value` property of the element.
25901
- *
25902
- * @example
25903
- <example name="ngValue-directive" module="valueExample">
25904
- <file name="index.html">
25905
- <script>
25906
- angular.module('valueExample', [])
25907
- .controller('ExampleController', ['$scope', function($scope) {
25908
- $scope.names = ['pizza', 'unicorns', 'robots'];
25909
- $scope.my = { favorite: 'unicorns' };
25910
- }]);
25911
- </script>
25912
- <form ng-controller="ExampleController">
25913
- <h2>Which is your favorite?</h2>
25914
- <label ng-repeat="name in names" for="{{name}}">
25915
- {{name}}
25916
- <input type="radio"
25917
- ng-model="my.favorite"
25918
- ng-value="name"
25919
- id="{{name}}"
25920
- name="favorite">
25921
- </label>
25922
- <div>You chose {{my.favorite}}</div>
25923
- </form>
25924
- </file>
25925
- <file name="protractor.js" type="protractor">
25926
- var favorite = element(by.binding('my.favorite'));
25927
-
25928
- it('should initialize to model', function() {
25929
- expect(favorite.getText()).toContain('unicorns');
25930
- });
25931
- it('should bind the values to the inputs', function() {
25932
- element.all(by.model('my.favorite')).get(0).click();
25933
- expect(favorite.getText()).toContain('pizza');
25934
- });
25935
- </file>
25936
- </example>
25937
- */
25938
- var ngValueDirective = function() {
25939
- /**
25940
- * inputs use the value attribute as their default value if the value property is not set.
25941
- * Once the value property has been set (by adding input), it will not react to changes to
25942
- * the value attribute anymore. Setting both attribute and property fixes this behavior, and
25943
- * makes it possible to use ngValue as a sort of one-way bind.
25944
- */
25945
- function updateElementValue(element, attr, value) {
25946
- // Support: IE9 only
25947
- // In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
25948
- var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
25949
- element.prop('value', propValue);
25950
- attr.$set('value', value);
25951
- }
25952
-
25953
- return {
25954
- restrict: 'A',
25955
- priority: 100,
25956
- compile: function(tpl, tplAttr) {
25957
- if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
25958
- return function ngValueConstantLink(scope, elm, attr) {
25959
- var value = scope.$eval(attr.ngValue);
25960
- updateElementValue(elm, attr, value);
25961
- };
25962
- } else {
25963
- return function ngValueLink(scope, elm, attr) {
25964
- scope.$watch(attr.ngValue, function valueWatchAction(value) {
25965
- updateElementValue(elm, attr, value);
25966
- });
25967
- };
25968
- }
25969
- }
25970
- };
25971
- };
25972
-
25973
- /**
25974
- * @ngdoc directive
25975
- * @name ngBind
25976
- * @restrict AC
25977
- *
25978
- * @description
25979
- * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
25980
- * with the value of a given expression, and to update the text content when the value of that
25981
- * expression changes.
25982
- *
25983
- * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
25984
- * `{{ expression }}` which is similar but less verbose.
25985
- *
25986
- * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
25987
- * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
25988
- * element attribute, it makes the bindings invisible to the user while the page is loading.
25989
- *
25990
- * An alternative solution to this problem would be using the
25991
- * {@link ng.directive:ngCloak ngCloak} directive.
25992
- *
25993
- *
25994
- * @element ANY
25995
- * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
25996
- *
25997
- * @example
25998
- * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
25999
- <example module="bindExample" name="ng-bind">
26000
- <file name="index.html">
26001
- <script>
26002
- angular.module('bindExample', [])
26003
- .controller('ExampleController', ['$scope', function($scope) {
26004
- $scope.name = 'Whirled';
26005
- }]);
26006
- </script>
26007
- <div ng-controller="ExampleController">
26008
- <label>Enter name: <input type="text" ng-model="name"></label><br>
26009
- Hello <span ng-bind="name"></span>!
26010
- </div>
26011
- </file>
26012
- <file name="protractor.js" type="protractor">
26013
- it('should check ng-bind', function() {
26014
- var nameInput = element(by.model('name'));
26015
-
26016
- expect(element(by.binding('name')).getText()).toBe('Whirled');
26017
- nameInput.clear();
26018
- nameInput.sendKeys('world');
26019
- expect(element(by.binding('name')).getText()).toBe('world');
26020
- });
26021
- </file>
26022
- </example>
26023
- */
26024
- var ngBindDirective = ['$compile', function($compile) {
26025
- return {
26026
- restrict: 'AC',
26027
- compile: function ngBindCompile(templateElement) {
26028
- $compile.$$addBindingClass(templateElement);
26029
- return function ngBindLink(scope, element, attr) {
26030
- $compile.$$addBindingInfo(element, attr.ngBind);
26031
- element = element[0];
26032
- scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
26033
- element.textContent = stringify(value);
26034
- });
26035
- };
26036
- }
26037
- };
26038
- }];
26039
-
26040
-
26041
- /**
26042
- * @ngdoc directive
26043
- * @name ngBindTemplate
26044
- *
26045
- * @description
26046
- * The `ngBindTemplate` directive specifies that the element
26047
- * text content should be replaced with the interpolation of the template
26048
- * in the `ngBindTemplate` attribute.
26049
- * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
26050
- * expressions. This directive is needed since some HTML elements
26051
- * (such as TITLE and OPTION) cannot contain SPAN elements.
26052
- *
26053
- * @element ANY
26054
- * @param {string} ngBindTemplate template of form
26055
- * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
26056
- *
26057
- * @example
26058
- * Try it here: enter text in text box and watch the greeting change.
26059
- <example module="bindExample" name="ng-bind-template">
26060
- <file name="index.html">
26061
- <script>
26062
- angular.module('bindExample', [])
26063
- .controller('ExampleController', ['$scope', function($scope) {
26064
- $scope.salutation = 'Hello';
26065
- $scope.name = 'World';
26066
- }]);
26067
- </script>
26068
- <div ng-controller="ExampleController">
26069
- <label>Salutation: <input type="text" ng-model="salutation"></label><br>
26070
- <label>Name: <input type="text" ng-model="name"></label><br>
26071
- <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
26072
- </div>
26073
- </file>
26074
- <file name="protractor.js" type="protractor">
26075
- it('should check ng-bind', function() {
26076
- var salutationElem = element(by.binding('salutation'));
26077
- var salutationInput = element(by.model('salutation'));
26078
- var nameInput = element(by.model('name'));
26079
-
26080
- expect(salutationElem.getText()).toBe('Hello World!');
26081
-
26082
- salutationInput.clear();
26083
- salutationInput.sendKeys('Greetings');
26084
- nameInput.clear();
26085
- nameInput.sendKeys('user');
26086
-
26087
- expect(salutationElem.getText()).toBe('Greetings user!');
26088
- });
26089
- </file>
26090
- </example>
26091
- */
26092
- var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
26093
- return {
26094
- compile: function ngBindTemplateCompile(templateElement) {
26095
- $compile.$$addBindingClass(templateElement);
26096
- return function ngBindTemplateLink(scope, element, attr) {
26097
- var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
26098
- $compile.$$addBindingInfo(element, interpolateFn.expressions);
26099
- element = element[0];
26100
- attr.$observe('ngBindTemplate', function(value) {
26101
- element.textContent = isUndefined(value) ? '' : value;
26102
- });
26103
- };
26104
- }
26105
- };
26106
- }];
26107
-
26108
-
26109
- /**
26110
- * @ngdoc directive
26111
- * @name ngBindHtml
26112
- *
26113
- * @description
26114
- * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
26115
- * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
26116
- * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
26117
- * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
26118
- * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
26119
- *
26120
- * You may also bypass sanitization for values you know are safe. To do so, bind to
26121
- * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
26122
- * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
26123
- *
26124
- * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
26125
- * will have an exception (instead of an exploit.)
26126
- *
26127
- * @element ANY
26128
- * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
26129
- *
26130
- * @example
26131
-
26132
- <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html">
26133
- <file name="index.html">
26134
- <div ng-controller="ExampleController">
26135
- <p ng-bind-html="myHTML"></p>
26136
- </div>
26137
- </file>
26138
-
26139
- <file name="script.js">
26140
- angular.module('bindHtmlExample', ['ngSanitize'])
26141
- .controller('ExampleController', ['$scope', function($scope) {
26142
- $scope.myHTML =
26143
- 'I am an <code>HTML</code>string with ' +
26144
- '<a href="#">links!</a> and other <em>stuff</em>';
26145
- }]);
26146
- </file>
26147
-
26148
- <file name="protractor.js" type="protractor">
26149
- it('should check ng-bind-html', function() {
26150
- expect(element(by.binding('myHTML')).getText()).toBe(
26151
- 'I am an HTMLstring with links! and other stuff');
26152
- });
26153
- </file>
26154
- </example>
26155
- */
26156
- var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
26157
- return {
26158
- restrict: 'A',
26159
- compile: function ngBindHtmlCompile(tElement, tAttrs) {
26160
- var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
26161
- var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
26162
- // Unwrap the value to compare the actual inner safe value, not the wrapper object.
26163
- return $sce.valueOf(val);
26164
- });
26165
- $compile.$$addBindingClass(tElement);
26166
-
26167
- return function ngBindHtmlLink(scope, element, attr) {
26168
- $compile.$$addBindingInfo(element, attr.ngBindHtml);
26169
-
26170
- scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
26171
- // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
26172
- var value = ngBindHtmlGetter(scope);
26173
- element.html($sce.getTrustedHtml(value) || '');
26174
- });
26175
- };
26176
- }
26177
- };
26178
- }];
26179
-
26180
- /**
26181
- * @ngdoc directive
26182
- * @name ngChange
26183
- *
26184
- * @description
26185
- * Evaluate the given expression when the user changes the input.
26186
- * The expression is evaluated immediately, unlike the JavaScript onchange event
26187
- * which only triggers at the end of a change (usually, when the user leaves the
26188
- * form element or presses the return key).
26189
- *
26190
- * The `ngChange` expression is only evaluated when a change in the input value causes
26191
- * a new value to be committed to the model.
26192
- *
26193
- * It will not be evaluated:
26194
- * * if the value returned from the `$parsers` transformation pipeline has not changed
26195
- * * if the input has continued to be invalid since the model will stay `null`
26196
- * * if the model is changed programmatically and not by a change to the input value
26197
- *
26198
- *
26199
- * Note, this directive requires `ngModel` to be present.
26200
- *
26201
- * @element input
26202
- * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
26203
- * in input value.
26204
- *
26205
- * @example
26206
- * <example name="ngChange-directive" module="changeExample">
26207
- * <file name="index.html">
26208
- * <script>
26209
- * angular.module('changeExample', [])
26210
- * .controller('ExampleController', ['$scope', function($scope) {
26211
- * $scope.counter = 0;
26212
- * $scope.change = function() {
26213
- * $scope.counter++;
26214
- * };
26215
- * }]);
26216
- * </script>
26217
- * <div ng-controller="ExampleController">
26218
- * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
26219
- * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
26220
- * <label for="ng-change-example2">Confirmed</label><br />
26221
- * <tt>debug = {{confirmed}}</tt><br/>
26222
- * <tt>counter = {{counter}}</tt><br/>
26223
- * </div>
26224
- * </file>
26225
- * <file name="protractor.js" type="protractor">
26226
- * var counter = element(by.binding('counter'));
26227
- * var debug = element(by.binding('confirmed'));
26228
- *
26229
- * it('should evaluate the expression if changing from view', function() {
26230
- * expect(counter.getText()).toContain('0');
26231
- *
26232
- * element(by.id('ng-change-example1')).click();
26233
- *
26234
- * expect(counter.getText()).toContain('1');
26235
- * expect(debug.getText()).toContain('true');
26236
- * });
26237
- *
26238
- * it('should not evaluate the expression if changing from model', function() {
26239
- * element(by.id('ng-change-example2')).click();
26240
-
26241
- * expect(counter.getText()).toContain('0');
26242
- * expect(debug.getText()).toContain('true');
26243
- * });
26244
- * </file>
26245
- * </example>
26246
- */
26247
- var ngChangeDirective = valueFn({
26248
- restrict: 'A',
26249
- require: 'ngModel',
26250
- link: function(scope, element, attr, ctrl) {
26251
- ctrl.$viewChangeListeners.push(function() {
26252
- scope.$eval(attr.ngChange);
26253
- });
26254
- }
26255
- });
26256
-
26257
- /* exported
26258
- ngClassDirective,
26259
- ngClassEvenDirective,
26260
- ngClassOddDirective
26261
- */
26262
-
26263
- function classDirective(name, selector) {
26264
- name = 'ngClass' + name;
26265
- var indexWatchExpression;
26266
-
26267
- return ['$parse', function($parse) {
26268
- return {
26269
- restrict: 'AC',
26270
- link: function(scope, element, attr) {
26271
- var expression = attr[name].trim();
26272
- var isOneTime = (expression.charAt(0) === ':') && (expression.charAt(1) === ':');
26273
-
26274
- var watchInterceptor = isOneTime ? toFlatValue : toClassString;
26275
- var watchExpression = $parse(expression, watchInterceptor);
26276
- var watchAction = isOneTime ? ngClassOneTimeWatchAction : ngClassWatchAction;
26277
-
26278
- var classCounts = element.data('$classCounts');
26279
- var oldModulo = true;
26280
- var oldClassString;
26281
-
26282
- if (!classCounts) {
26283
- // Use createMap() to prevent class assumptions involving property
26284
- // names in Object.prototype
26285
- classCounts = createMap();
26286
- element.data('$classCounts', classCounts);
26287
- }
26288
-
26289
- if (name !== 'ngClass') {
26290
- if (!indexWatchExpression) {
26291
- indexWatchExpression = $parse('$index', function moduloTwo($index) {
26292
- // eslint-disable-next-line no-bitwise
26293
- return $index & 1;
26294
- });
26295
- }
26296
-
26297
- scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
26298
- }
26299
-
26300
- scope.$watch(watchExpression, watchAction, isOneTime);
26301
-
26302
- function addClasses(classString) {
26303
- classString = digestClassCounts(split(classString), 1);
26304
- attr.$addClass(classString);
26305
- }
26306
-
26307
- function removeClasses(classString) {
26308
- classString = digestClassCounts(split(classString), -1);
26309
- attr.$removeClass(classString);
26310
- }
26311
-
26312
- function updateClasses(oldClassString, newClassString) {
26313
- var oldClassArray = split(oldClassString);
26314
- var newClassArray = split(newClassString);
26315
-
26316
- var toRemoveArray = arrayDifference(oldClassArray, newClassArray);
26317
- var toAddArray = arrayDifference(newClassArray, oldClassArray);
26318
-
26319
- var toRemoveString = digestClassCounts(toRemoveArray, -1);
26320
- var toAddString = digestClassCounts(toAddArray, 1);
26321
-
26322
- attr.$addClass(toAddString);
26323
- attr.$removeClass(toRemoveString);
26324
- }
26325
-
26326
- function digestClassCounts(classArray, count) {
26327
- var classesToUpdate = [];
26328
-
26329
- forEach(classArray, function(className) {
26330
- if (count > 0 || classCounts[className]) {
26331
- classCounts[className] = (classCounts[className] || 0) + count;
26332
- if (classCounts[className] === +(count > 0)) {
26333
- classesToUpdate.push(className);
26334
- }
26335
- }
26336
- });
26337
-
26338
- return classesToUpdate.join(' ');
26339
- }
26340
-
26341
- function ngClassIndexWatchAction(newModulo) {
26342
- // This watch-action should run before the `ngClass[OneTime]WatchAction()`, thus it
26343
- // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
26344
- // `ngClass[OneTime]WatchAction()` will update the classes.
26345
- if (newModulo === selector) {
26346
- addClasses(oldClassString);
26347
- } else {
26348
- removeClasses(oldClassString);
26349
- }
26350
-
26351
- oldModulo = newModulo;
26352
- }
26353
-
26354
- function ngClassOneTimeWatchAction(newClassValue) {
26355
- var newClassString = toClassString(newClassValue);
26356
-
26357
- if (newClassString !== oldClassString) {
26358
- ngClassWatchAction(newClassString);
26359
- }
26360
- }
26361
-
26362
- function ngClassWatchAction(newClassString) {
26363
- if (oldModulo === selector) {
26364
- updateClasses(oldClassString, newClassString);
26365
- }
26366
-
26367
- oldClassString = newClassString;
26368
- }
26369
- }
26370
- };
26371
- }];
26372
-
26373
- // Helpers
26374
- function arrayDifference(tokens1, tokens2) {
26375
- if (!tokens1 || !tokens1.length) return [];
26376
- if (!tokens2 || !tokens2.length) return tokens1;
26377
-
26378
- var values = [];
26379
-
26380
- outer:
26381
- for (var i = 0; i < tokens1.length; i++) {
26382
- var token = tokens1[i];
26383
- for (var j = 0; j < tokens2.length; j++) {
26384
- if (token === tokens2[j]) continue outer;
26385
- }
26386
- values.push(token);
26387
- }
26388
-
26389
- return values;
26390
- }
26391
-
26392
- function split(classString) {
26393
- return classString && classString.split(' ');
26394
- }
26395
-
26396
- function toClassString(classValue) {
26397
- var classString = classValue;
26398
-
26399
- if (isArray(classValue)) {
26400
- classString = classValue.map(toClassString).join(' ');
26401
- } else if (isObject(classValue)) {
26402
- classString = Object.keys(classValue).
26403
- filter(function(key) { return classValue[key]; }).
26404
- join(' ');
26405
- }
26406
-
26407
- return classString;
26408
- }
26409
-
26410
- function toFlatValue(classValue) {
26411
- var flatValue = classValue;
26412
-
26413
- if (isArray(classValue)) {
26414
- flatValue = classValue.map(toFlatValue);
26415
- } else if (isObject(classValue)) {
26416
- var hasUndefined = false;
26417
-
26418
- flatValue = Object.keys(classValue).filter(function(key) {
26419
- var value = classValue[key];
26420
-
26421
- if (!hasUndefined && isUndefined(value)) {
26422
- hasUndefined = true;
26423
- }
26424
-
26425
- return value;
26426
- });
26427
-
26428
- if (hasUndefined) {
26429
- // Prevent the `oneTimeLiteralWatchInterceptor` from unregistering
26430
- // the watcher, by including at least one `undefined` value.
26431
- flatValue.push(undefined);
26432
- }
26433
- }
26434
-
26435
- return flatValue;
26436
- }
26437
- }
26438
-
26439
- /**
26440
- * @ngdoc directive
26441
- * @name ngClass
26442
- * @restrict AC
26443
- *
26444
- * @description
26445
- * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
26446
- * an expression that represents all classes to be added.
26447
- *
26448
- * The directive operates in three different ways, depending on which of three types the expression
26449
- * evaluates to:
26450
- *
26451
- * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
26452
- * names.
26453
- *
26454
- * 2. If the expression evaluates to an object, then for each key-value pair of the
26455
- * object with a truthy value the corresponding key is used as a class name.
26456
- *
26457
- * 3. If the expression evaluates to an array, each element of the array should either be a string as in
26458
- * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
26459
- * to give you more control over what CSS classes appear. See the code below for an example of this.
26460
- *
26461
- *
26462
- * The directive won't add duplicate classes if a particular class was already set.
26463
- *
26464
- * When the expression changes, the previously added classes are removed and only then are the
26465
- * new classes added.
26466
- *
26467
- * @knownIssue
26468
- * You should not use {@link guide/interpolation interpolation} in the value of the `class`
26469
- * attribute, when using the `ngClass` directive on the same element.
26470
- * See {@link guide/interpolation#known-issues here} for more info.
26471
- *
26472
- * @animations
26473
- * | Animation | Occurs |
26474
- * |----------------------------------|-------------------------------------|
26475
- * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
26476
- * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
26477
- *
26478
- * @element ANY
26479
- * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
26480
- * of the evaluation can be a string representing space delimited class
26481
- * names, an array, or a map of class names to boolean values. In the case of a map, the
26482
- * names of the properties whose values are truthy will be added as css classes to the
26483
- * element.
26484
- *
26485
- * @example Example that demonstrates basic bindings via ngClass directive.
26486
- <example name="ng-class">
26487
- <file name="index.html">
26488
- <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
26489
- <label>
26490
- <input type="checkbox" ng-model="deleted">
26491
- deleted (apply "strike" class)
26492
- </label><br>
26493
- <label>
26494
- <input type="checkbox" ng-model="important">
26495
- important (apply "bold" class)
26496
- </label><br>
26497
- <label>
26498
- <input type="checkbox" ng-model="error">
26499
- error (apply "has-error" class)
26500
- </label>
26501
- <hr>
26502
- <p ng-class="style">Using String Syntax</p>
26503
- <input type="text" ng-model="style"
26504
- placeholder="Type: bold strike red" aria-label="Type: bold strike red">
26505
- <hr>
26506
- <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
26507
- <input ng-model="style1"
26508
- placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
26509
- <input ng-model="style2"
26510
- placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
26511
- <input ng-model="style3"
26512
- placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
26513
- <hr>
26514
- <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
26515
- <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
26516
- <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
26517
- </file>
26518
- <file name="style.css">
26519
- .strike {
26520
- text-decoration: line-through;
26521
- }
26522
- .bold {
26523
- font-weight: bold;
26524
- }
26525
- .red {
26526
- color: red;
26527
- }
26528
- .has-error {
26529
- color: red;
26530
- background-color: yellow;
26531
- }
26532
- .orange {
26533
- color: orange;
26534
- }
26535
- </file>
26536
- <file name="protractor.js" type="protractor">
26537
- var ps = element.all(by.css('p'));
26538
-
26539
- it('should let you toggle the class', function() {
26540
-
26541
- expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
26542
- expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
26543
-
26544
- element(by.model('important')).click();
26545
- expect(ps.first().getAttribute('class')).toMatch(/bold/);
26546
-
26547
- element(by.model('error')).click();
26548
- expect(ps.first().getAttribute('class')).toMatch(/has-error/);
26549
- });
26550
-
26551
- it('should let you toggle string example', function() {
26552
- expect(ps.get(1).getAttribute('class')).toBe('');
26553
- element(by.model('style')).clear();
26554
- element(by.model('style')).sendKeys('red');
26555
- expect(ps.get(1).getAttribute('class')).toBe('red');
26556
- });
26557
-
26558
- it('array example should have 3 classes', function() {
26559
- expect(ps.get(2).getAttribute('class')).toBe('');
26560
- element(by.model('style1')).sendKeys('bold');
26561
- element(by.model('style2')).sendKeys('strike');
26562
- element(by.model('style3')).sendKeys('red');
26563
- expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
26564
- });
26565
-
26566
- it('array with map example should have 2 classes', function() {
26567
- expect(ps.last().getAttribute('class')).toBe('');
26568
- element(by.model('style4')).sendKeys('bold');
26569
- element(by.model('warning')).click();
26570
- expect(ps.last().getAttribute('class')).toBe('bold orange');
26571
- });
26572
- </file>
26573
- </example>
26574
-
26575
- ## Animations
26576
-
26577
- The example below demonstrates how to perform animations using ngClass.
26578
-
26579
- <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class">
26580
- <file name="index.html">
26581
- <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
26582
- <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
26583
- <br>
26584
- <span class="base-class" ng-class="myVar">Sample Text</span>
26585
- </file>
26586
- <file name="style.css">
26587
- .base-class {
26588
- transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
26589
- }
26590
-
26591
- .base-class.my-class {
26592
- color: red;
26593
- font-size:3em;
26594
- }
26595
- </file>
26596
- <file name="protractor.js" type="protractor">
26597
- it('should check ng-class', function() {
26598
- expect(element(by.css('.base-class')).getAttribute('class')).not.
26599
- toMatch(/my-class/);
26600
-
26601
- element(by.id('setbtn')).click();
26602
-
26603
- expect(element(by.css('.base-class')).getAttribute('class')).
26604
- toMatch(/my-class/);
26605
-
26606
- element(by.id('clearbtn')).click();
26607
-
26608
- expect(element(by.css('.base-class')).getAttribute('class')).not.
26609
- toMatch(/my-class/);
26610
- });
26611
- </file>
26612
- </example>
26613
-
26614
-
26615
- ## ngClass and pre-existing CSS3 Transitions/Animations
26616
- The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
26617
- Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
26618
- any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
26619
- to view the step by step details of {@link $animate#addClass $animate.addClass} and
26620
- {@link $animate#removeClass $animate.removeClass}.
26621
- */
26622
- var ngClassDirective = classDirective('', true);
26623
-
26624
- /**
26625
- * @ngdoc directive
26626
- * @name ngClassOdd
26627
- * @restrict AC
26628
- *
26629
- * @description
26630
- * The `ngClassOdd` and `ngClassEven` directives work exactly as
26631
- * {@link ng.directive:ngClass ngClass}, except they work in
26632
- * conjunction with `ngRepeat` and take effect only on odd (even) rows.
26633
- *
26634
- * This directive can be applied only within the scope of an
26635
- * {@link ng.directive:ngRepeat ngRepeat}.
26636
- *
26637
- * @element ANY
26638
- * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
26639
- * of the evaluation can be a string representing space delimited class names or an array.
26640
- *
26641
- * @example
26642
- <example name="ng-class-odd">
26643
- <file name="index.html">
26644
- <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
26645
- <li ng-repeat="name in names">
26646
- <span ng-class-odd="'odd'" ng-class-even="'even'">
26647
- {{name}}
26648
- </span>
26649
- </li>
26650
- </ol>
26651
- </file>
26652
- <file name="style.css">
26653
- .odd {
26654
- color: red;
26655
- }
26656
- .even {
26657
- color: blue;
26658
- }
26659
- </file>
26660
- <file name="protractor.js" type="protractor">
26661
- it('should check ng-class-odd and ng-class-even', function() {
26662
- expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
26663
- toMatch(/odd/);
26664
- expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
26665
- toMatch(/even/);
26666
- });
26667
- </file>
26668
- </example>
26669
- */
26670
- var ngClassOddDirective = classDirective('Odd', 0);
26671
-
26672
- /**
26673
- * @ngdoc directive
26674
- * @name ngClassEven
26675
- * @restrict AC
26676
- *
26677
- * @description
26678
- * The `ngClassOdd` and `ngClassEven` directives work exactly as
26679
- * {@link ng.directive:ngClass ngClass}, except they work in
26680
- * conjunction with `ngRepeat` and take effect only on odd (even) rows.
26681
- *
26682
- * This directive can be applied only within the scope of an
26683
- * {@link ng.directive:ngRepeat ngRepeat}.
26684
- *
26685
- * @element ANY
26686
- * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
26687
- * result of the evaluation can be a string representing space delimited class names or an array.
26688
- *
26689
- * @example
26690
- <example name="ng-class-even">
26691
- <file name="index.html">
26692
- <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
26693
- <li ng-repeat="name in names">
26694
- <span ng-class-odd="'odd'" ng-class-even="'even'">
26695
- {{name}} &nbsp; &nbsp; &nbsp;
26696
- </span>
26697
- </li>
26698
- </ol>
26699
- </file>
26700
- <file name="style.css">
26701
- .odd {
26702
- color: red;
26703
- }
26704
- .even {
26705
- color: blue;
26706
- }
26707
- </file>
26708
- <file name="protractor.js" type="protractor">
26709
- it('should check ng-class-odd and ng-class-even', function() {
26710
- expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
26711
- toMatch(/odd/);
26712
- expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
26713
- toMatch(/even/);
26714
- });
26715
- </file>
26716
- </example>
26717
- */
26718
- var ngClassEvenDirective = classDirective('Even', 1);
26719
-
26720
- /**
26721
- * @ngdoc directive
26722
- * @name ngCloak
26723
- * @restrict AC
26724
- *
26725
- * @description
26726
- * The `ngCloak` directive is used to prevent the Angular html template from being briefly
26727
- * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
26728
- * directive to avoid the undesirable flicker effect caused by the html template display.
26729
- *
26730
- * The directive can be applied to the `<body>` element, but the preferred usage is to apply
26731
- * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
26732
- * of the browser view.
26733
- *
26734
- * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
26735
- * `angular.min.js`.
26736
- * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
26737
- *
26738
- * ```css
26739
- * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
26740
- * display: none !important;
26741
- * }
26742
- * ```
26743
- *
26744
- * When this css rule is loaded by the browser, all html elements (including their children) that
26745
- * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
26746
- * during the compilation of the template it deletes the `ngCloak` element attribute, making
26747
- * the compiled element visible.
26748
- *
26749
- * For the best result, the `angular.js` script must be loaded in the head section of the html
26750
- * document; alternatively, the css rule above must be included in the external stylesheet of the
26751
- * application.
26752
- *
26753
- * @element ANY
26754
- *
26755
- * @example
26756
- <example name="ng-cloak">
26757
- <file name="index.html">
26758
- <div id="template1" ng-cloak>{{ 'hello' }}</div>
26759
- <div id="template2" class="ng-cloak">{{ 'world' }}</div>
26760
- </file>
26761
- <file name="protractor.js" type="protractor">
26762
- it('should remove the template directive and css class', function() {
26763
- expect($('#template1').getAttribute('ng-cloak')).
26764
- toBeNull();
26765
- expect($('#template2').getAttribute('ng-cloak')).
26766
- toBeNull();
26767
- });
26768
- </file>
26769
- </example>
26770
- *
26771
- */
26772
- var ngCloakDirective = ngDirective({
26773
- compile: function(element, attr) {
26774
- attr.$set('ngCloak', undefined);
26775
- element.removeClass('ng-cloak');
26776
- }
26777
- });
26778
-
26779
- /**
26780
- * @ngdoc directive
26781
- * @name ngController
26782
- *
26783
- * @description
26784
- * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
26785
- * supports the principles behind the Model-View-Controller design pattern.
26786
- *
26787
- * MVC components in angular:
26788
- *
26789
- * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
26790
- * are accessed through bindings.
26791
- * * View — The template (HTML with data bindings) that is rendered into the View.
26792
- * * Controller — The `ngController` directive specifies a Controller class; the class contains business
26793
- * logic behind the application to decorate the scope with functions and values
26794
- *
26795
- * Note that you can also attach controllers to the DOM by declaring it in a route definition
26796
- * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
26797
- * again using `ng-controller` in the template itself. This will cause the controller to be attached
26798
- * and executed twice.
26799
- *
26800
- * @element ANY
26801
- * @scope
26802
- * @priority 500
26803
- * @param {expression} ngController Name of a constructor function registered with the current
26804
- * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
26805
- * that on the current scope evaluates to a constructor function.
26806
- *
26807
- * The controller instance can be published into a scope property by specifying
26808
- * `ng-controller="as propertyName"`.
26809
- *
26810
- * If the current `$controllerProvider` is configured to use globals (via
26811
- * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
26812
- * also be the name of a globally accessible constructor function (deprecated, not recommended).
26813
- *
26814
- * @example
26815
- * Here is a simple form for editing user contact information. Adding, removing, clearing, and
26816
- * greeting are methods declared on the controller (see source tab). These methods can
26817
- * easily be called from the angular markup. Any changes to the data are automatically reflected
26818
- * in the View without the need for a manual update.
26819
- *
26820
- * Two different declaration styles are included below:
26821
- *
26822
- * * one binds methods and properties directly onto the controller using `this`:
26823
- * `ng-controller="SettingsController1 as settings"`
26824
- * * one injects `$scope` into the controller:
26825
- * `ng-controller="SettingsController2"`
26826
- *
26827
- * The second option is more common in the Angular community, and is generally used in boilerplates
26828
- * and in this guide. However, there are advantages to binding properties directly to the controller
26829
- * and avoiding scope.
26830
- *
26831
- * * Using `controller as` makes it obvious which controller you are accessing in the template when
26832
- * multiple controllers apply to an element.
26833
- * * If you are writing your controllers as classes you have easier access to the properties and
26834
- * methods, which will appear on the scope, from inside the controller code.
26835
- * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
26836
- * inheritance masking primitives.
26837
- *
26838
- * This example demonstrates the `controller as` syntax.
26839
- *
26840
- * <example name="ngControllerAs" module="controllerAsExample">
26841
- * <file name="index.html">
26842
- * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
26843
- * <label>Name: <input type="text" ng-model="settings.name"/></label>
26844
- * <button ng-click="settings.greet()">greet</button><br/>
26845
- * Contact:
26846
- * <ul>
26847
- * <li ng-repeat="contact in settings.contacts">
26848
- * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
26849
- * <option>phone</option>
26850
- * <option>email</option>
26851
- * </select>
26852
- * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
26853
- * <button ng-click="settings.clearContact(contact)">clear</button>
26854
- * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
26855
- * </li>
26856
- * <li><button ng-click="settings.addContact()">add</button></li>
26857
- * </ul>
26858
- * </div>
26859
- * </file>
26860
- * <file name="app.js">
26861
- * angular.module('controllerAsExample', [])
26862
- * .controller('SettingsController1', SettingsController1);
26863
- *
26864
- * function SettingsController1() {
26865
- * this.name = 'John Smith';
26866
- * this.contacts = [
26867
- * {type: 'phone', value: '408 555 1212'},
26868
- * {type: 'email', value: 'john.smith@example.org'}
26869
- * ];
26870
- * }
26871
- *
26872
- * SettingsController1.prototype.greet = function() {
26873
- * alert(this.name);
26874
- * };
26875
- *
26876
- * SettingsController1.prototype.addContact = function() {
26877
- * this.contacts.push({type: 'email', value: 'yourname@example.org'});
26878
- * };
26879
- *
26880
- * SettingsController1.prototype.removeContact = function(contactToRemove) {
26881
- * var index = this.contacts.indexOf(contactToRemove);
26882
- * this.contacts.splice(index, 1);
26883
- * };
26884
- *
26885
- * SettingsController1.prototype.clearContact = function(contact) {
26886
- * contact.type = 'phone';
26887
- * contact.value = '';
26888
- * };
26889
- * </file>
26890
- * <file name="protractor.js" type="protractor">
26891
- * it('should check controller as', function() {
26892
- * var container = element(by.id('ctrl-as-exmpl'));
26893
- * expect(container.element(by.model('settings.name'))
26894
- * .getAttribute('value')).toBe('John Smith');
26895
- *
26896
- * var firstRepeat =
26897
- * container.element(by.repeater('contact in settings.contacts').row(0));
26898
- * var secondRepeat =
26899
- * container.element(by.repeater('contact in settings.contacts').row(1));
26900
- *
26901
- * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26902
- * .toBe('408 555 1212');
26903
- *
26904
- * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
26905
- * .toBe('john.smith@example.org');
26906
- *
26907
- * firstRepeat.element(by.buttonText('clear')).click();
26908
- *
26909
- * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26910
- * .toBe('');
26911
- *
26912
- * container.element(by.buttonText('add')).click();
26913
- *
26914
- * expect(container.element(by.repeater('contact in settings.contacts').row(2))
26915
- * .element(by.model('contact.value'))
26916
- * .getAttribute('value'))
26917
- * .toBe('yourname@example.org');
26918
- * });
26919
- * </file>
26920
- * </example>
26921
- *
26922
- * This example demonstrates the "attach to `$scope`" style of controller.
26923
- *
26924
- * <example name="ngController" module="controllerExample">
26925
- * <file name="index.html">
26926
- * <div id="ctrl-exmpl" ng-controller="SettingsController2">
26927
- * <label>Name: <input type="text" ng-model="name"/></label>
26928
- * <button ng-click="greet()">greet</button><br/>
26929
- * Contact:
26930
- * <ul>
26931
- * <li ng-repeat="contact in contacts">
26932
- * <select ng-model="contact.type" id="select_{{$index}}">
26933
- * <option>phone</option>
26934
- * <option>email</option>
26935
- * </select>
26936
- * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
26937
- * <button ng-click="clearContact(contact)">clear</button>
26938
- * <button ng-click="removeContact(contact)">X</button>
26939
- * </li>
26940
- * <li>[ <button ng-click="addContact()">add</button> ]</li>
26941
- * </ul>
26942
- * </div>
26943
- * </file>
26944
- * <file name="app.js">
26945
- * angular.module('controllerExample', [])
26946
- * .controller('SettingsController2', ['$scope', SettingsController2]);
26947
- *
26948
- * function SettingsController2($scope) {
26949
- * $scope.name = 'John Smith';
26950
- * $scope.contacts = [
26951
- * {type:'phone', value:'408 555 1212'},
26952
- * {type:'email', value:'john.smith@example.org'}
26953
- * ];
26954
- *
26955
- * $scope.greet = function() {
26956
- * alert($scope.name);
26957
- * };
26958
- *
26959
- * $scope.addContact = function() {
26960
- * $scope.contacts.push({type:'email', value:'yourname@example.org'});
26961
- * };
26962
- *
26963
- * $scope.removeContact = function(contactToRemove) {
26964
- * var index = $scope.contacts.indexOf(contactToRemove);
26965
- * $scope.contacts.splice(index, 1);
26966
- * };
26967
- *
26968
- * $scope.clearContact = function(contact) {
26969
- * contact.type = 'phone';
26970
- * contact.value = '';
26971
- * };
26972
- * }
26973
- * </file>
26974
- * <file name="protractor.js" type="protractor">
26975
- * it('should check controller', function() {
26976
- * var container = element(by.id('ctrl-exmpl'));
26977
- *
26978
- * expect(container.element(by.model('name'))
26979
- * .getAttribute('value')).toBe('John Smith');
26980
- *
26981
- * var firstRepeat =
26982
- * container.element(by.repeater('contact in contacts').row(0));
26983
- * var secondRepeat =
26984
- * container.element(by.repeater('contact in contacts').row(1));
26985
- *
26986
- * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26987
- * .toBe('408 555 1212');
26988
- * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
26989
- * .toBe('john.smith@example.org');
26990
- *
26991
- * firstRepeat.element(by.buttonText('clear')).click();
26992
- *
26993
- * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
26994
- * .toBe('');
26995
- *
26996
- * container.element(by.buttonText('add')).click();
26997
- *
26998
- * expect(container.element(by.repeater('contact in contacts').row(2))
26999
- * .element(by.model('contact.value'))
27000
- * .getAttribute('value'))
27001
- * .toBe('yourname@example.org');
27002
- * });
27003
- * </file>
27004
- *</example>
27005
-
27006
- */
27007
- var ngControllerDirective = [function() {
27008
- return {
27009
- restrict: 'A',
27010
- scope: true,
27011
- controller: '@',
27012
- priority: 500
27013
- };
27014
- }];
27015
-
27016
- /**
27017
- * @ngdoc directive
27018
- * @name ngCsp
27019
- *
27020
- * @restrict A
27021
- * @element ANY
27022
- * @description
27023
- *
27024
- * Angular has some features that can conflict with certain restrictions that are applied when using
27025
- * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
27026
- *
27027
- * If you intend to implement CSP with these rules then you must tell Angular not to use these
27028
- * features.
27029
- *
27030
- * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
27031
- *
27032
- *
27033
- * The following default rules in CSP affect Angular:
27034
- *
27035
- * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute
27036
- * code from strings is forbidden. Angular makes use of this in the {@link $parse} service to
27037
- * provide a 30% increase in the speed of evaluating Angular expressions. (This CSP rule can be
27038
- * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would
27039
- * weaken the protections offered by CSP.)
27040
- *
27041
- * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden.
27042
- * This prevents apps from injecting custom styles directly into the document. Angular makes use of
27043
- * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these
27044
- * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css`
27045
- * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but
27046
- * it is generally not recommended as it would weaken the protections offered by CSP.)
27047
- *
27048
- * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking dynamic code
27049
- * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically
27050
- * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a
27051
- * CSP error to be logged in the console:
27052
- *
27053
- * ```
27054
- * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
27055
- * script in the following Content Security Policy directive: "default-src 'self'". Note that
27056
- * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
27057
- * ```
27058
- *
27059
- * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
27060
- * directive on an element of the HTML document that appears before the `<script>` tag that loads
27061
- * the `angular.js` file.
27062
- *
27063
- * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
27064
- *
27065
- * You can specify which of the CSP related Angular features should be deactivated by providing
27066
- * a value for the `ng-csp` attribute. The options are as follows:
27067
- *
27068
- * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
27069
- *
27070
- * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings
27071
- *
27072
- * You can use these values in the following combinations:
27073
- *
27074
- *
27075
- * * No declaration means that Angular will assume that you can do inline styles, but it will do
27076
- * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous
27077
- * versions of Angular.
27078
- *
27079
- * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
27080
- * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous
27081
- * versions of Angular.
27082
- *
27083
- * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can
27084
- * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
27085
- *
27086
- * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
27087
- * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
27088
- *
27089
- * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
27090
- * styles nor use eval, which is the same as an empty: ng-csp.
27091
- * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
27092
- *
27093
- * @example
27094
- * This example shows how to apply the `ngCsp` directive to the `html` tag.
27095
- ```html
27096
- <!doctype html>
27097
- <html ng-app ng-csp>
27098
- ...
27099
- ...
27100
- </html>
27101
- ```
27102
- * @example
27103
- <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! -->
27104
- <example name="example.csp" module="cspExample" ng-csp="true">
27105
- <file name="index.html">
27106
- <div ng-controller="MainController as ctrl">
27107
- <div>
27108
- <button ng-click="ctrl.inc()" id="inc">Increment</button>
27109
- <span id="counter">
27110
- {{ctrl.counter}}
27111
- </span>
27112
- </div>
27113
-
27114
- <div>
27115
- <button ng-click="ctrl.evil()" id="evil">Evil</button>
27116
- <span id="evilError">
27117
- {{ctrl.evilError}}
27118
- </span>
27119
- </div>
27120
- </div>
27121
- </file>
27122
- <file name="script.js">
27123
- angular.module('cspExample', [])
27124
- .controller('MainController', function MainController() {
27125
- this.counter = 0;
27126
- this.inc = function() {
27127
- this.counter++;
27128
- };
27129
- this.evil = function() {
27130
- try {
27131
- eval('1+2'); // eslint-disable-line no-eval
27132
- } catch (e) {
27133
- this.evilError = e.message;
27134
- }
27135
- };
27136
- });
27137
- </file>
27138
- <file name="protractor.js" type="protractor">
27139
- var util, webdriver;
27140
-
27141
- var incBtn = element(by.id('inc'));
27142
- var counter = element(by.id('counter'));
27143
- var evilBtn = element(by.id('evil'));
27144
- var evilError = element(by.id('evilError'));
27145
-
27146
- function getAndClearSevereErrors() {
27147
- return browser.manage().logs().get('browser').then(function(browserLog) {
27148
- return browserLog.filter(function(logEntry) {
27149
- return logEntry.level.value > webdriver.logging.Level.WARNING.value;
27150
- });
27151
- });
27152
- }
27153
-
27154
- function clearErrors() {
27155
- getAndClearSevereErrors();
27156
- }
27157
-
27158
- function expectNoErrors() {
27159
- getAndClearSevereErrors().then(function(filteredLog) {
27160
- expect(filteredLog.length).toEqual(0);
27161
- if (filteredLog.length) {
27162
- console.log('browser console errors: ' + util.inspect(filteredLog));
27163
- }
27164
- });
27165
- }
27166
-
27167
- function expectError(regex) {
27168
- getAndClearSevereErrors().then(function(filteredLog) {
27169
- var found = false;
27170
- filteredLog.forEach(function(log) {
27171
- if (log.message.match(regex)) {
27172
- found = true;
27173
- }
27174
- });
27175
- if (!found) {
27176
- throw new Error('expected an error that matches ' + regex);
27177
- }
27178
- });
27179
- }
27180
-
27181
- beforeEach(function() {
27182
- util = require('util');
27183
- webdriver = require('selenium-webdriver');
27184
- });
27185
-
27186
- // For now, we only test on Chrome,
27187
- // as Safari does not load the page with Protractor's injected scripts,
27188
- // and Firefox webdriver always disables content security policy (#6358)
27189
- if (browser.params.browser !== 'chrome') {
27190
- return;
27191
- }
27192
-
27193
- it('should not report errors when the page is loaded', function() {
27194
- // clear errors so we are not dependent on previous tests
27195
- clearErrors();
27196
- // Need to reload the page as the page is already loaded when
27197
- // we come here
27198
- browser.driver.getCurrentUrl().then(function(url) {
27199
- browser.get(url);
27200
- });
27201
- expectNoErrors();
27202
- });
27203
-
27204
- it('should evaluate expressions', function() {
27205
- expect(counter.getText()).toEqual('0');
27206
- incBtn.click();
27207
- expect(counter.getText()).toEqual('1');
27208
- expectNoErrors();
27209
- });
27210
-
27211
- it('should throw and report an error when using "eval"', function() {
27212
- evilBtn.click();
27213
- expect(evilError.getText()).toMatch(/Content Security Policy/);
27214
- expectError(/Content Security Policy/);
27215
- });
27216
- </file>
27217
- </example>
27218
- */
27219
-
27220
- // `ngCsp` is not implemented as a proper directive any more, because we need it be processed while
27221
- // we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()`
27222
- // fn that looks for the `ng-csp` attribute anywhere in the current doc.
27223
-
27224
- /**
27225
- * @ngdoc directive
27226
- * @name ngClick
27227
- *
27228
- * @description
27229
- * The ngClick directive allows you to specify custom behavior when
27230
- * an element is clicked.
27231
- *
27232
- * @element ANY
27233
- * @priority 0
27234
- * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
27235
- * click. ({@link guide/expression#-event- Event object is available as `$event`})
27236
- *
27237
- * @example
27238
- <example name="ng-click">
27239
- <file name="index.html">
27240
- <button ng-click="count = count + 1" ng-init="count=0">
27241
- Increment
27242
- </button>
27243
- <span>
27244
- count: {{count}}
27245
- </span>
27246
- </file>
27247
- <file name="protractor.js" type="protractor">
27248
- it('should check ng-click', function() {
27249
- expect(element(by.binding('count')).getText()).toMatch('0');
27250
- element(by.css('button')).click();
27251
- expect(element(by.binding('count')).getText()).toMatch('1');
27252
- });
27253
- </file>
27254
- </example>
27255
- */
27256
- /*
27257
- * A collecti