vendor: 1,377,909 bytes, lines 1-36600
1/** 2 * @license AngularJS v1.8.3 3 * (c) 2010-2020 Google LLC. http://angularjs.org 4 * License: MIT 5 */ 6(function(window) {'use strict'; 7 8/* exported 9 minErrConfig, 10 errorHandlingConfig, 11 isValidObjectMaxDepth 12*/ 13 14var minErrConfig = { 15 objectMaxDepth: 5, 16 urlErrorParamsEnabled: true 17}; 18 19/** 20 * @ngdoc function 21 * @name angular.errorHandlingConfig 22 * @module ng 23 * @kind function 24 * 25 * @description 26 * Configure several aspects of error handling in AngularJS if used as a setter or return the 27 * current configuration if used as a getter. The following options are supported: 28 * 29 * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages. 30 * 31 * Omitted or undefined options will leave the corresponding configuration values unchanged. 32 * 33 * @param {Object=} config - The configuration object. May only contain the options that need to be 34 * updated. Supported keys: 35 * 36 * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a 37 * non-positive or non-numeric value, removes the max depth limit. 38 * Default: 5 39 * 40 * * `urlErrorParamsEnabled` **{Boolean}** - Specifies whether the generated error url will 41 * contain the parameters of the thrown error. Disabling the parameters can be useful if the 42 * generated error url is very long. 43 * 44 * Default: true. When used without argument, it returns the current value. 45 */ 46function errorHandlingConfig(config) { 47 if (isObject(config)) { 48 if (isDefined(config.objectMaxDepth)) { 49 minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; 50 } 51 if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) { 52 minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled; 53 } 54 } else { 55 return minErrConfig; 56 } 57} 58 59/** 60 * @private 61 * @param {Number} maxDepth 62 * @return {boolean} 63 */ 64function isValidObjectMaxDepth(maxDepth) { 65 return isNumber(maxDepth) && maxDepth > 0; 66} 67 68 69/** 70 * @description 71 * 72 * This object provides a utility for producing rich Error messages within 73 * AngularJS. It can be called as follows: 74 * 75 * var exampleMinErr = minErr('example'); 76 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 77 * 78 * The above creates an instance of minErr in the example namespace. The 79 * resulting error will have a namespaced error code of example.one. The 80 * resulting error will replace {0} with the value of foo, and {1} with the 81 * value of bar. The object is not restricted in the number of arguments it can 82 * take. 83 * 84 * If fewer arguments are specified than necessary for interpolation, the extra 85 * interpolation markers will be preserved in the final string. 86 * 87 * Since data will be parsed statically during a build step, some restrictions 88 * are applied with respect to how minErr instances are created and called. 89 * Instances should have names of the form namespaceMinErr for a minErr created 90 * using minErr('namespace'). Error codes, namespaces and template strings 91 * should all be static strings, not variables or general expressions. 92 * 93 * @param {string} module The namespace to use for the new minErr instance. 94 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 95 * error from returned function, for cases when a particular type of error is useful. 96 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 97 */ 98 99function minErr(module, ErrorConstructor) { 100 ErrorConstructor = ErrorConstructor || Error; 101 102 var url = 'https://errors.angularjs.org/1.8.3/'; 103 var regex = url.replace('.', '\\.') + '[\\s\\S]*'; 104 var errRegExp = new RegExp(regex, 'g'); 105 106 return function() { 107 var code = arguments[0], 108 template = arguments[1], 109 message = '[' + (module ? module + ':' : '') + code + '] ', 110 templateArgs = sliceArgs(arguments, 2).map(function(arg) { 111 return toDebugString(arg, minErrConfig.objectMaxDepth); 112 }), 113 paramPrefix, i; 114 115 // A minErr message has two parts: the message itself and the url that contains the 116 // encoded message. 117 // The message's parameters can contain other error messages which also include error urls. 118 // To prevent the messages from getting too long, we strip the error urls from the parameters. 119 120 message += template.replace(/\{\d+\}/g, function(match) { 121 var index = +match.slice(1, -1); 122 123 if (index < templateArgs.length) { 124 return templateArgs[index].replace(errRegExp, ''); 125 } 126 127 return match; 128 }); 129 130 message += '\n' + url + (module ? module + '/' : '') + code; 131 132 if (minErrConfig.urlErrorParamsEnabled) { 133 for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { 134 message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); 135 } 136 } 137 138 return new ErrorConstructor(message); 139 }; 140} 141 142/* We need to tell ESLint what variables are being exported */ 143/* exported 144 angular, 145 msie, 146 jqLite, 147 jQuery, 148 slice, 149 splice, 150 push, 151 toString, 152 minErrConfig, 153 errorHandlingConfig, 154 isValidObjectMaxDepth, 155 ngMinErr, 156 angularModule, 157 uid, 158 REGEX_STRING_REGEXP, 159 VALIDITY_STATE_PROPERTY, 160 161 lowercase, 162 uppercase, 163 nodeName_, 164 isArrayLike, 165 forEach, 166 forEachSorted, 167 reverseParams, 168 nextUid, 169 setHashKey, 170 extend, 171 toInt, 172 inherit, 173 merge, 174 noop, 175 identity, 176 valueFn, 177 isUndefined, 178 isDefined, 179 isObject, 180 isBlankObject, 181 isString, 182 isNumber, 183 isNumberNaN, 184 isDate, 185 isError, 186 isArray, 187 isFunction, 188 isRegExp, 189 isWindow, 190 isScope, 191 isFile, 192 isFormData, 193 isBlob, 194 isBoolean, 195 isPromiseLike, 196 trim, 197 escapeForRegexp, 198 isElement, 199 makeMap, 200 includes, 201 arrayRemove, 202 copy, 203 simpleCompare, 204 equals, 205 csp, 206 jq, 207 concat, 208 sliceArgs, 209 bind, 210 toJsonReplacer, 211 toJson, 212 fromJson, 213 convertTimezoneToLocal, 214 timezoneToOffset, 215 addDateMinutes, 216 startingTag, 217 tryDecodeURIComponent, 218 parseKeyValue, 219 toKeyValue, 220 encodeUriSegment, 221 encodeUriQuery, 222 angularInit, 223 bootstrap, 224 getTestability, 225 snake_case, 226 bindJQuery, 227 assertArg, 228 assertArgFn, 229 assertNotHasOwnProperty, 230 getter, 231 getBlockNodes, 232 hasOwnProperty, 233 createMap, 234 stringify, 235 UNSAFE_restoreLegacyJqLiteXHTMLReplacement, 236 237 NODE_TYPE_ELEMENT, 238 NODE_TYPE_ATTRIBUTE, 239 NODE_TYPE_TEXT, 240 NODE_TYPE_COMMENT, 241 NODE_TYPE_DOCUMENT, 242 NODE_TYPE_DOCUMENT_FRAGMENT 243*/ 244 245//////////////////////////////////// 246 247/** 248 * @ngdoc module 249 * @name ng 250 * @module ng 251 * @installation 252 * @description 253 * 254 * The ng module is loaded by default when an AngularJS application is started. The module itself 255 * contains the essential components for an AngularJS application to function. The table below 256 * lists a high level breakdown of each of the services/factories, filters, directives and testing 257 * components available within this core module. 258 * 259 */ 260 261var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 262 263// The name of a form control's ValidityState property. 264// This is used so that it's possible for internal tests to create mock ValidityStates. 265var VALIDITY_STATE_PROPERTY = 'validity'; 266 267 268var hasOwnProperty = Object.prototype.hasOwnProperty; 269 270/** 271 * @private 272 * 273 * @description Converts the specified string to lowercase. 274 * @param {string} string String to be converted to lowercase. 275 * @returns {string} Lowercased string. 276 */ 277var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 278 279/** 280 * @private 281 * 282 * @description Converts the specified string to uppercase. 283 * @param {string} string String to be converted to uppercase. 284 * @returns {string} Uppercased string. 285 */ 286var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 287 288 289var 290 msie, // holds major version number for IE, or NaN if UA is not IE. 291 jqLite, // delay binding since jQuery could be loaded after us. 292 jQuery, // delay binding 293 slice = [].slice, 294 splice = [].splice, 295 push = [].push, 296 toString = Object.prototype.toString, 297 getPrototypeOf = Object.getPrototypeOf, 298 ngMinErr = minErr('ng'), 299 300 /** @name angular */ 301 angular = window.angular || (window.angular = {}), 302 angularModule, 303 uid = 0; 304 305// Support: IE 9-11 only 306/** 307 * documentMode is an IE-only property 308 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 309 */ 310msie = window.document.documentMode; 311 312 313/** 314 * @private 315 * @param {*} obj 316 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 317 * String ...) 318 */ 319function isArrayLike(obj) { 320 321 // `null`, `undefined` and `window` are not array-like 322 if (obj == null || isWindow(obj)) return false; 323 324 // arrays, strings and jQuery/jqLite objects are array like 325 // * jqLite is either the jQuery or jqLite constructor function 326 // * we have to check the existence of jqLite first as this method is called 327 // via the forEach method when constructing the jqLite object in the first place 328 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 329 330 // Support: iOS 8.2 (not reproducible in simulator) 331 // "length" in obj used to prevent JIT error (gh-11508) 332 var length = 'length' in Object(obj) && obj.length; 333 334 // NodeList objects (with `item` method) and 335 // other objects with suitable length characteristics are array-like 336 return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function'); 337 338} 339 340/** 341 * @ngdoc function 342 * @name angular.forEach 343 * @module ng 344 * @kind function 345 * 346 * @description 347 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 348 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 349 * is the value of an object property or an array element, `key` is the object property key or 350 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 351 * 352 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 353 * using the `hasOwnProperty` method. 354 * 355 * Unlike ES262's 356 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 357 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 358 * return the value provided. 359 * 360 ```js 361 var values = {name: 'misko', gender: 'male'}; 362 var log = []; 363 angular.forEach(values, function(value, key) { 364 this.push(key + ': ' + value); 365 }, log); 366 expect(log).toEqual(['name: misko', 'gender: male']); 367 ``` 368 * 369 * @param {Object|Array} obj Object to iterate over. 370 * @param {Function} iterator Iterator function. 371 * @param {Object=} context Object to become context (`this`) for the iterator function. 372 * @returns {Object|Array} Reference to `obj`. 373 */ 374 375function forEach(obj, iterator, context) { 376 var key, length; 377 if (obj) { 378 if (isFunction(obj)) { 379 for (key in obj) { 380 if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) { 381 iterator.call(context, obj[key], key, obj); 382 } 383 } 384 } else if (isArray(obj) || isArrayLike(obj)) { 385 var isPrimitive = typeof obj !== 'object'; 386 for (key = 0, length = obj.length; key < length; key++) { 387 if (isPrimitive || key in obj) { 388 iterator.call(context, obj[key], key, obj); 389 } 390 } 391 } else if (obj.forEach && obj.forEach !== forEach) { 392 obj.forEach(iterator, context, obj); 393 } else if (isBlankObject(obj)) { 394 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 395 for (key in obj) { 396 iterator.call(context, obj[key], key, obj); 397 } 398 } else if (typeof obj.hasOwnProperty === 'function') { 399 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 400 for (key in obj) { 401 if (obj.hasOwnProperty(key)) { 402 iterator.call(context, obj[key], key, obj); 403 } 404 } 405 } else { 406 // Slow path for objects which do not have a method `hasOwnProperty` 407 for (key in obj) { 408 if (hasOwnProperty.call(obj, key)) { 409 iterator.call(context, obj[key], key, obj); 410 } 411 } 412 } 413 } 414 return obj; 415} 416 417function forEachSorted(obj, iterator, context) { 418 var keys = Object.keys(obj).sort(); 419 for (var i = 0; i < keys.length; i++) { 420 iterator.call(context, obj[keys[i]], keys[i]); 421 } 422 return keys; 423} 424 425 426/** 427 * when using forEach the params are value, key, but it is often useful to have key, value. 428 * @param {function(string, *)} iteratorFn 429 * @returns {function(*, string)} 430 */ 431function reverseParams(iteratorFn) { 432 return function(value, key) {iteratorFn(key, value);}; 433} 434 435/** 436 * A consistent way of creating unique IDs in angular. 437 * 438 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 439 * we hit number precision issues in JavaScript. 440 * 441 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 442 * 443 * @returns {number} an unique alpha-numeric string 444 */ 445function nextUid() { 446 return ++uid; 447} 448 449 450/** 451 * Set or clear the hashkey for an object. 452 * @param obj object 453 * @param h the hashkey (!truthy to delete the hashkey) 454 */ 455function setHashKey(obj, h) { 456 if (h) { 457 obj.$$hashKey = h; 458 } else { 459 delete obj.$$hashKey; 460 } 461} 462 463 464function baseExtend(dst, objs, deep) { 465 var h = dst.$$hashKey; 466 467 for (var i = 0, ii = objs.length; i < ii; ++i) { 468 var obj = objs[i]; 469 if (!isObject(obj) && !isFunction(obj)) continue; 470 var keys = Object.keys(obj); 471 for (var j = 0, jj = keys.length; j < jj; j++) { 472 var key = keys[j]; 473 var src = obj[key]; 474 475 if (deep && isObject(src)) { 476 if (isDate(src)) { 477 dst[key] = new Date(src.valueOf()); 478 } else if (isRegExp(src)) { 479 dst[key] = new RegExp(src); 480 } else if (src.nodeName) { 481 dst[key] = src.cloneNode(true); 482 } else if (isElement(src)) { 483 dst[key] = src.clone(); 484 } else { 485 if (key !== '__proto__') { 486 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 487 baseExtend(dst[key], [src], true); 488 } 489 } 490 } else { 491 dst[key] = src; 492 } 493 } 494 } 495 496 setHashKey(dst, h); 497 return dst; 498} 499 500/** 501 * @ngdoc function 502 * @name angular.extend 503 * @module ng 504 * @kind function 505 * 506 * @description 507 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 508 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 509 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 510 * 511 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 512 * {@link angular.merge} for this. 513 * 514 * @param {Object} dst Destination object. 515 * @param {...Object} src Source object(s). 516 * @returns {Object} Reference to `dst`. 517 */ 518function extend(dst) { 519 return baseExtend(dst, slice.call(arguments, 1), false); 520} 521 522 523/** 524* @ngdoc function 525* @name angular.merge 526* @module ng 527* @kind function 528* 529* @description 530* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 531* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 532* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 533* 534* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 535* objects, performing a deep copy. 536* 537* @deprecated 538* sinceVersion="1.6.5" 539* This function is deprecated, but will not be removed in the 1.x lifecycle. 540* There are edge cases (see {@link angular.merge#known-issues known issues}) that are not 541* supported by this function. We suggest using another, similar library for all-purpose merging, 542* such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge). 543* 544* @knownIssue 545* This is a list of (known) object types that are not handled correctly by this function: 546* - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) 547* - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream) 548* - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient) 549* - AngularJS {@link $rootScope.Scope scopes}; 550* 551* `angular.merge` also does not support merging objects with circular references. 552* 553* @param {Object} dst Destination object. 554* @param {...Object} src Source object(s). 555* @returns {Object} Reference to `dst`. 556*/ 557function merge(dst) { 558 return baseExtend(dst, slice.call(arguments, 1), true); 559} 560 561 562 563function toInt(str) { 564 return parseInt(str, 10); 565} 566 567var isNumberNaN = Number.isNaN || function isNumberNaN(num) { 568 // eslint-disable-next-line no-self-compare 569 return num !== num; 570}; 571 572 573function inherit(parent, extra) { 574 return extend(Object.create(parent), extra); 575} 576 577/** 578 * @ngdoc function 579 * @name angular.noop 580 * @module ng 581 * @kind function 582 * 583 * @description 584 * A function that performs no operations. This function can be useful when writing code in the 585 * functional style. 586 ```js 587 function foo(callback) { 588 var result = calculateResult(); 589 (callback || angular.noop)(result); 590 } 591 ``` 592 */ 593function noop() {} 594noop.$inject = []; 595 596 597/** 598 * @ngdoc function 599 * @name angular.identity 600 * @module ng 601 * @kind function 602 * 603 * @description 604 * A function that returns its first argument. This function is useful when writing code in the 605 * functional style. 606 * 607 ```js 608 function transformer(transformationFn, value) { 609 return (transformationFn || angular.identity)(value); 610 }; 611 612 // E.g. 613 function getResult(fn, input) { 614 return (fn || angular.identity)(input); 615 }; 616 617 getResult(function(n) { return n * 2; }, 21); // returns 42 618 getResult(null, 21); // returns 21 619 getResult(undefined, 21); // returns 21 620 ``` 621 * 622 * @param {*} value to be returned. 623 * @returns {*} the value passed in. 624 */ 625function identity($) {return $;} 626identity.$inject = []; 627 628 629function valueFn(value) {return function valueRef() {return value;};} 630 631function hasCustomToString(obj) { 632 return isFunction(obj.toString) && obj.toString !== toString; 633} 634 635 636/** 637 * @ngdoc function 638 * @name angular.isUndefined 639 * @module ng 640 * @kind function 641 * 642 * @description 643 * Determines if a reference is undefined. 644 * 645 * @param {*} value Reference to check. 646 * @returns {boolean} True if `value` is undefined. 647 */ 648function isUndefined(value) {return typeof value === 'undefined';} 649 650 651/** 652 * @ngdoc function 653 * @name angular.isDefined 654 * @module ng 655 * @kind function 656 * 657 * @description 658 * Determines if a reference is defined. 659 * 660 * @param {*} value Reference to check. 661 * @returns {boolean} True if `value` is defined. 662 */ 663function isDefined(value) {return typeof value !== 'undefined';} 664 665 666/** 667 * @ngdoc function 668 * @name angular.isObject 669 * @module ng 670 * @kind function 671 * 672 * @description 673 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 674 * considered to be objects. Note that JavaScript arrays are objects. 675 * 676 * @param {*} value Reference to check. 677 * @returns {boolean} True if `value` is an `Object` but not `null`. 678 */ 679function isObject(value) { 680 // http://jsperf.com/isobject4 681 return value !== null && typeof value === 'object'; 682} 683 684 685/** 686 * Determine if a value is an object with a null prototype 687 * 688 * @returns {boolean} True if `value` is an `Object` with a null prototype 689 */ 690function isBlankObject(value) { 691 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 692} 693 694 695/** 696 * @ngdoc function 697 * @name angular.isString 698 * @module ng 699 * @kind function 700 * 701 * @description 702 * Determines if a reference is a `String`. 703 * 704 * @param {*} value Reference to check. 705 * @returns {boolean} True if `value` is a `String`. 706 */ 707function isString(value) {return typeof value === 'string';} 708 709 710/** 711 * @ngdoc function 712 * @name angular.isNumber 713 * @module ng 714 * @kind function 715 * 716 * @description 717 * Determines if a reference is a `Number`. 718 * 719 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 720 * 721 * If you wish to exclude these then you can use the native 722 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 723 * method. 724 * 725 * @param {*} value Reference to check. 726 * @returns {boolean} True if `value` is a `Number`. 727 */ 728function isNumber(value) {return typeof value === 'number';} 729 730 731/** 732 * @ngdoc function 733 * @name angular.isDate 734 * @module ng 735 * @kind function 736 * 737 * @description 738 * Determines if a value is a date. 739 * 740 * @param {*} value Reference to check. 741 * @returns {boolean} True if `value` is a `Date`. 742 */ 743function isDate(value) { 744 return toString.call(value) === '[object Date]'; 745} 746 747 748/** 749 * @ngdoc function 750 * @name angular.isArray 751 * @module ng 752 * @kind function 753 * 754 * @description 755 * Determines if a reference is an `Array`. 756 * 757 * @param {*} value Reference to check. 758 * @returns {boolean} True if `value` is an `Array`. 759 */ 760function isArray(arr) { 761 return Array.isArray(arr) || arr instanceof Array; 762} 763 764/** 765 * @description 766 * Determines if a reference is an `Error`. 767 * Loosely based on https://www.npmjs.com/package/iserror 768 * 769 * @param {*} value Reference to check. 770 * @returns {boolean} True if `value` is an `Error`. 771 */ 772function isError(value) { 773 var tag = toString.call(value); 774 switch (tag) { 775 case '[object Error]': return true; 776 case '[object Exception]': return true; 777 case '[object DOMException]': return true; 778 default: return value instanceof Error; 779 } 780} 781 782/** 783 * @ngdoc function 784 * @name angular.isFunction 785 * @module ng 786 * @kind function 787 * 788 * @description 789 * Determines if a reference is a `Function`. 790 * 791 * @param {*} value Reference to check. 792 * @returns {boolean} True if `value` is a `Function`. 793 */ 794function isFunction(value) {return typeof value === 'function';} 795 796 797/** 798 * Determines if a value is a regular expression object. 799 * 800 * @private 801 * @param {*} value Reference to check. 802 * @returns {boolean} True if `value` is a `RegExp`. 803 */ 804function isRegExp(value) { 805 return toString.call(value) === '[object RegExp]'; 806} 807 808 809/** 810 * Checks if `obj` is a window object. 811 * 812 * @private 813 * @param {*} obj Object to check 814 * @returns {boolean} True if `obj` is a window obj. 815 */ 816function isWindow(obj) { 817 return obj && obj.window === obj; 818} 819 820 821function isScope(obj) { 822 return obj && obj.$evalAsync && obj.$watch; 823} 824 825 826function isFile(obj) { 827 return toString.call(obj) === '[object File]'; 828} 829 830 831function isFormData(obj) { 832 return toString.call(obj) === '[object FormData]'; 833} 834 835 836function isBlob(obj) { 837 return toString.call(obj) === '[object Blob]'; 838} 839 840 841function isBoolean(value) { 842 return typeof value === 'boolean'; 843} 844 845 846function isPromiseLike(obj) { 847 return obj && isFunction(obj.then); 848} 849 850 851var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/; 852function isTypedArray(value) { 853 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 854} 855 856function isArrayBuffer(obj) { 857 return toString.call(obj) === '[object ArrayBuffer]'; 858} 859 860 861var trim = function(value) { 862 return isString(value) ? value.trim() : value; 863}; 864 865// Copied from: 866// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 867// Prereq: s is a string. 868var escapeForRegexp = function(s) { 869 return s 870 .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1') 871 // eslint-disable-next-line no-control-regex 872 .replace(/\x08/g, '\\x08'); 873}; 874 875 876/** 877 * @ngdoc function 878 * @name angular.isElement 879 * @module ng 880 * @kind function 881 * 882 * @description 883 * Determines if a reference is a DOM element (or wrapped jQuery element). 884 * 885 * @param {*} value Reference to check. 886 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 887 */ 888function isElement(node) { 889 return !!(node && 890 (node.nodeName // We are a direct element. 891 || (node.prop && node.attr && node.find))); // We have an on and find method part of jQuery API. 892} 893 894/** 895 * @param str 'key1,key2,...' 896 * @returns {object} in the form of {key1:true, key2:true, ...} 897 */ 898function makeMap(str) { 899 var obj = {}, items = str.split(','), i; 900 for (i = 0; i < items.length; i++) { 901 obj[items[i]] = true; 902 } 903 return obj; 904} 905 906 907function nodeName_(element) { 908 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 909} 910 911function includes(array, obj) { 912 return Array.prototype.indexOf.call(array, obj) !== -1; 913} 914 915function arrayRemove(array, value) { 916 var index = array.indexOf(value); 917 if (index >= 0) { 918 array.splice(index, 1); 919 } 920 return index; 921} 922 923/** 924 * @ngdoc function 925 * @name angular.copy 926 * @module ng 927 * @kind function 928 * 929 * @description 930 * Creates a deep copy of `source`, which should be an object or an array. This functions is used 931 * internally, mostly in the change-detection code. It is not intended as an all-purpose copy 932 * function, and has several limitations (see below). 933 * 934 * * If no destination is supplied, a copy of the object or array is created. 935 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 936 * are deleted and then all elements/properties from the source are copied to it. 937 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 938 * * If `source` is identical to `destination` an exception will be thrown. 939 * 940 * <br /> 941 * 942 * <div class="alert alert-warning"> 943 * Only enumerable properties are taken into account. Non-enumerable properties (both on `source` 944 * and on `destination`) will be ignored. 945 * </div> 946 * 947 * <div class="alert alert-warning"> 948 * `angular.copy` does not check if destination and source are of the same type. It's the 949 * developer's responsibility to make sure they are compatible. 950 * </div> 951 * 952 * @knownIssue 953 * This is a non-exhaustive list of object types / features that are not handled correctly by 954 * `angular.copy`. Note that since this functions is used by the change detection code, this 955 * means binding or watching objects of these types (or that include these types) might not work 956 * correctly. 957 * - [`File`](https://developer.mozilla.org/docs/Web/API/File) 958 * - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) 959 * - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData) 960 * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream) 961 * - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set) 962 * - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) 963 * - [`getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/ 964 * [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) 965 * 966 * @param {*} source The source that will be used to make a copy. Can be any type, including 967 * primitives, `null`, and `undefined`. 968 * @param {(Object|Array)=} destination Destination into which the source is copied. If provided, 969 * must be of the same type as `source`. 970 * @returns {*} The copy or updated `destination`, if `destination` was specified. 971 * 972 * @example 973 <example module="copyExample" name="angular-copy"> 974 <file name="index.html"> 975 <div ng-controller="ExampleController"> 976 <form novalidate class="simple-form"> 977 <label>Name: <input type="text" ng-model="user.name" /></label><br /> 978 <label>Age: <input type="number" ng-model="user.age" /></label><br /> 979 Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label> 980 <label><input type="radio" ng-model="user.gender" value="female" />female</label><br /> 981 <button ng-click="reset()">RESET</button> 982 <button ng-click="update(user)">SAVE</button> 983 </form> 984 <pre>form = {{user | json}}</pre> 985 <pre>leader = {{leader | json}}</pre> 986 </div> 987 </file> 988 <file name="script.js"> 989 // Module: copyExample 990 angular. 991 module('copyExample', []). 992 controller('ExampleController', ['$scope', function($scope) { 993 $scope.leader = {}; 994 995 $scope.reset = function() { 996 // Example with 1 argument 997 $scope.user = angular.copy($scope.leader); 998 }; 999 1000 $scope.update = function(user) { 1001 // Example with 2 arguments 1002 angular.copy(user, $scope.leader); 1003 }; 1004 1005 $scope.reset(); 1006 }]); 1007 </file> 1008 </example> 1009 */ 1010function copy(source, destination, maxDepth) { 1011 var stackSource = []; 1012 var stackDest = []; 1013 maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN; 1014 1015 if (destination) { 1016 if (isTypedArray(destination) || isArrayBuffer(destination)) { 1017 throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.'); 1018 } 1019 if (source === destination) { 1020 throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.'); 1021 } 1022 1023 // Empty the destination object 1024 if (isArray(destination)) { 1025 destination.length = 0; 1026 } else { 1027 forEach(destination, function(value, key) { 1028 if (key !== '$$hashKey') { 1029 delete destination[key]; 1030 } 1031 }); 1032 } 1033 1034 stackSource.push(source); 1035 stackDest.push(destination); 1036 return copyRecurse(source, destination, maxDepth); 1037 } 1038 1039 return copyElement(source, maxDepth); 1040 1041 function copyRecurse(source, destination, maxDepth) { 1042 maxDepth--; 1043 if (maxDepth < 0) { 1044 return '...'; 1045 } 1046 var h = destination.$$hashKey; 1047 var key; 1048 if (isArray(source)) { 1049 for (var i = 0, ii = source.length; i < ii; i++) { 1050 destination.push(copyElement(source[i], maxDepth)); 1051 } 1052 } else if (isBlankObject(source)) { 1053 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 1054 for (key in source) { 1055 destination[key] = copyElement(source[key], maxDepth); 1056 } 1057 } else if (source && typeof source.hasOwnProperty === 'function') { 1058 // Slow path, which must rely on hasOwnProperty 1059 for (key in source) { 1060 if (source.hasOwnProperty(key)) { 1061 destination[key] = copyElement(source[key], maxDepth); 1062 } 1063 } 1064 } else { 1065 // Slowest path --- hasOwnProperty can't be called as a method 1066 for (key in source) { 1067 if (hasOwnProperty.call(source, key)) { 1068 destination[key] = copyElement(source[key], maxDepth); 1069 } 1070 } 1071 } 1072 setHashKey(destination, h); 1073 return destination; 1074 } 1075 1076 function copyElement(source, maxDepth) { 1077 // Simple values 1078 if (!isObject(source)) { 1079 return source; 1080 } 1081 1082 // Already copied values 1083 var index = stackSource.indexOf(source); 1084 if (index !== -1) { 1085 return stackDest[index]; 1086 } 1087 1088 if (isWindow(source) || isScope(source)) { 1089 throw ngMinErr('cpws', 1090 'Can\'t copy! Making copies of Window or Scope instances is not supported.'); 1091 } 1092 1093 var needsRecurse = false; 1094 var destination = copyType(source); 1095 1096 if (destination === undefined) { 1097 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 1098 needsRecurse = true; 1099 } 1100 1101 stackSource.push(source); 1102 stackDest.push(destination); 1103 1104 return needsRecurse 1105 ? copyRecurse(source, destination, maxDepth) 1106 : destination; 1107 } 1108 1109 function copyType(source) { 1110 switch (toString.call(source)) { 1111 case '[object Int8Array]': 1112 case '[object Int16Array]': 1113 case '[object Int32Array]': 1114 case '[object Float32Array]': 1115 case '[object Float64Array]': 1116 case '[object Uint8Array]': 1117 case '[object Uint8ClampedArray]': 1118 case '[object Uint16Array]': 1119 case '[object Uint32Array]': 1120 return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length); 1121 1122 case '[object ArrayBuffer]': 1123 // Support: IE10 1124 if (!source.slice) { 1125 // If we're in this case we know the environment supports ArrayBuffer 1126 /* eslint-disable no-undef */ 1127 var copied = new ArrayBuffer(source.byteLength); 1128 new Uint8Array(copied).set(new Uint8Array(source)); 1129 /* eslint-enable */ 1130 return copied; 1131 } 1132 return source.slice(0); 1133 1134 case '[object Boolean]': 1135 case '[object Number]': 1136 case '[object String]': 1137 case '[object Date]': 1138 return new source.constructor(source.valueOf()); 1139 1140 case '[object RegExp]': 1141 var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]); 1142 re.lastIndex = source.lastIndex; 1143 return re; 1144 1145 case '[object Blob]': 1146 return new source.constructor([source], {type: source.type}); 1147 } 1148 1149 if (isFunction(source.cloneNode)) { 1150 return source.cloneNode(true); 1151 } 1152 } 1153} 1154 1155 1156// eslint-disable-next-line no-self-compare 1157function simpleCompare(a, b) { return a === b || (a !== a && b !== b); } 1158 1159 1160/** 1161 * @ngdoc function 1162 * @name angular.equals 1163 * @module ng 1164 * @kind function 1165 * 1166 * @description 1167 * Determines if two objects or two values are equivalent. Supports value types, regular 1168 * expressions, arrays and objects. 1169 * 1170 * Two objects or values are considered equivalent if at least one of the following is true: 1171 * 1172 * * Both objects or values pass `===` comparison. 1173 * * Both objects or values are of the same type and all of their properties are equal by 1174 * comparing them with `angular.equals`. 1175 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1176 * * Both values represent the same regular expression (In JavaScript, 1177 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1178 * representation matches). 1179 * 1180 * During a property comparison, properties of `function` type and properties with names 1181 * that begin with `$` are ignored. 1182 * 1183 * Scope and DOMWindow objects are being compared only by identify (`===`). 1184 * 1185 * @param {*} o1 Object or value to compare. 1186 * @param {*} o2 Object or value to compare. 1187 * @returns {boolean} True if arguments are equal. 1188 * 1189 * @example 1190 <example module="equalsExample" name="equalsExample"> 1191 <file name="index.html"> 1192 <div ng-controller="ExampleController"> 1193 <form novalidate> 1194 <h3>User 1</h3> 1195 Name: <input type="text" ng-model="user1.name"> 1196 Age: <input type="number" ng-model="user1.age"> 1197 1198 <h3>User 2</h3> 1199 Name: <input type="text" ng-model="user2.name"> 1200 Age: <input type="number" ng-model="user2.age"> 1201 1202 <div> 1203 <br/> 1204 <input type="button" value="Compare" ng-click="compare()"> 1205 </div> 1206 User 1: <pre>{{user1 | json}}</pre> 1207 User 2: <pre>{{user2 | json}}</pre> 1208 Equal: <pre>{{result}}</pre> 1209 </form> 1210 </div> 1211 </file> 1212 <file name="script.js"> 1213 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { 1214 $scope.user1 = {}; 1215 $scope.user2 = {}; 1216 $scope.compare = function() { 1217 $scope.result = angular.equals($scope.user1, $scope.user2); 1218 }; 1219 }]); 1220 </file> 1221 </example> 1222 */ 1223function equals(o1, o2) { 1224 if (o1 === o2) return true; 1225 if (o1 === null || o2 === null) return false; 1226 // eslint-disable-next-line no-self-compare 1227 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1228 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1229 if (t1 === t2 && t1 === 'object') { 1230 if (isArray(o1)) { 1231 if (!isArray(o2)) return false; 1232 if ((length = o1.length) === o2.length) { 1233 for (key = 0; key < length; key++) { 1234 if (!equals(o1[key], o2[key])) return false; 1235 } 1236 return true; 1237 } 1238 } else if (isDate(o1)) { 1239 if (!isDate(o2)) return false; 1240 return simpleCompare(o1.getTime(), o2.getTime()); 1241 } else if (isRegExp(o1)) { 1242 if (!isRegExp(o2)) return false; 1243 return o1.toString() === o2.toString(); 1244 } else { 1245 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1246 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1247 keySet = createMap(); 1248 for (key in o1) { 1249 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1250 if (!equals(o1[key], o2[key])) return false; 1251 keySet[key] = true; 1252 } 1253 for (key in o2) { 1254 if (!(key in keySet) && 1255 key.charAt(0) !== '$' && 1256 isDefined(o2[key]) && 1257 !isFunction(o2[key])) return false; 1258 } 1259 return true; 1260 } 1261 } 1262 return false; 1263} 1264 1265var csp = function() { 1266 if (!isDefined(csp.rules)) { 1267 1268 1269 var ngCspElement = (window.document.querySelector('[ng-csp]') || 1270 window.document.querySelector('[data-ng-csp]')); 1271 1272 if (ngCspElement) { 1273 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1274 ngCspElement.getAttribute('data-ng-csp'); 1275 csp.rules = { 1276 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1277 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1278 }; 1279 } else { 1280 csp.rules = { 1281 noUnsafeEval: noUnsafeEval(), 1282 noInlineStyle: false 1283 }; 1284 } 1285 } 1286 1287 return csp.rules; 1288 1289 function noUnsafeEval() { 1290 try { 1291 // eslint-disable-next-line no-new, no-new-func 1292 new Function(''); 1293 return false; 1294 } catch (e) { 1295 return true; 1296 } 1297 } 1298}; 1299 1300/** 1301 * @ngdoc directive 1302 * @module ng 1303 * @name ngJq 1304 * 1305 * @element ANY 1306 * @param {string=} ngJq the name of the library available under `window` 1307 * to be used for angular.element 1308 * @description 1309 * Use this directive to force the angular.element library. This should be 1310 * used to force either jqLite by leaving ng-jq blank or setting the name of 1311 * the jquery variable under window (eg. jQuery). 1312 * 1313 * Since AngularJS looks for this directive when it is loaded (doesn't wait for the 1314 * DOMContentLoaded event), it must be placed on an element that comes before the script 1315 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1316 * others ignored. 1317 * 1318 * @example 1319 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1320 ```html 1321 <!doctype html> 1322 <html ng-app ng-jq> 1323 ... 1324 ... 1325 </html> 1326 ``` 1327 * @example 1328 * This example shows how to use a jQuery based library of a different name. 1329 * The library name must be available at the top most 'window'. 1330 ```html 1331 <!doctype html> 1332 <html ng-app ng-jq="jQueryLib"> 1333 ... 1334 ... 1335 </html> 1336 ``` 1337 */ 1338var jq = function() { 1339 if (isDefined(jq.name_)) return jq.name_; 1340 var el; 1341 var i, ii = ngAttrPrefixes.length, prefix, name; 1342 for (i = 0; i < ii; ++i) { 1343 prefix = ngAttrPrefixes[i]; 1344 el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]'); 1345 if (el) { 1346 name = el.getAttribute(prefix + 'jq'); 1347 break; 1348 } 1349 } 1350 1351 return (jq.name_ = name); 1352}; 1353 1354function concat(array1, array2, index) { 1355 return array1.concat(slice.call(array2, index)); 1356} 1357 1358function sliceArgs(args, startIndex) { 1359 return slice.call(args, startIndex || 0); 1360} 1361 1362 1363/** 1364 * @ngdoc function 1365 * @name angular.bind 1366 * @module ng 1367 * @kind function 1368 * 1369 * @description 1370 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1371 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1372 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1373 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1374 * 1375 * @param {Object} self Context which `fn` should be evaluated in. 1376 * @param {function()} fn Function to be bound. 1377 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1378 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1379 */ 1380function bind(self, fn) { 1381 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1382 if (isFunction(fn) && !(fn instanceof RegExp)) { 1383 return curryArgs.length 1384 ? function() { 1385 return arguments.length 1386 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1387 : fn.apply(self, curryArgs); 1388 } 1389 : function() { 1390 return arguments.length 1391 ? fn.apply(self, arguments) 1392 : fn.call(self); 1393 }; 1394 } else { 1395 // In IE, native methods are not functions so they cannot be bound (note: they don't need to be). 1396 return fn; 1397 } 1398} 1399 1400 1401function toJsonReplacer(key, value) { 1402 var val = value; 1403 1404 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1405 val = undefined; 1406 } else if (isWindow(value)) { 1407 val = '$WINDOW'; 1408 } else if (value && window.document === value) { 1409 val = '$DOCUMENT'; 1410 } else if (isScope(value)) { 1411 val = '$SCOPE'; 1412 } 1413 1414 return val; 1415} 1416 1417 1418/** 1419 * @ngdoc function 1420 * @name angular.toJson 1421 * @module ng 1422 * @kind function 1423 * 1424 * @description 1425 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1426 * stripped since AngularJS uses this notation internally. 1427 * 1428 * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON. 1429 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1430 * If set to an integer, the JSON output will contain that many spaces per indentation. 1431 * @returns {string|undefined} JSON-ified string representing `obj`. 1432 * @knownIssue 1433 * 1434 * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date` 1435 * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the 1436 * `Date.prototype.toJSON` method as follows: 1437 * 1438 * ``` 1439 * var _DatetoJSON = Date.prototype.toJSON; 1440 * Date.prototype.toJSON = function() { 1441 * try { 1442 * return _DatetoJSON.call(this); 1443 * } catch(e) { 1444 * if (e instanceof RangeError) { 1445 * return null; 1446 * } 1447 * throw e; 1448 * } 1449 * }; 1450 * ``` 1451 * 1452 * See https://github.com/angular/angular.js/pull/14221 for more information. 1453 */ 1454function toJson(obj, pretty) { 1455 if (isUndefined(obj)) return undefined; 1456 if (!isNumber(pretty)) { 1457 pretty = pretty ? 2 : null; 1458 } 1459 return JSON.stringify(obj, toJsonReplacer, pretty); 1460} 1461 1462 1463/** 1464 * @ngdoc function 1465 * @name angular.fromJson 1466 * @module ng 1467 * @kind function 1468 * 1469 * @description 1470 * Deserializes a JSON string. 1471 * 1472 * @param {string} json JSON string to deserialize. 1473 * @returns {Object|Array|string|number} Deserialized JSON string. 1474 */ 1475function fromJson(json) { 1476 return isString(json) 1477 ? JSON.parse(json) 1478 : json; 1479} 1480 1481 1482var ALL_COLONS = /:/g; 1483function timezoneToOffset(timezone, fallback) { 1484 // Support: IE 9-11 only, Edge 13-15+ 1485 // IE/Edge do not "understand" colon (`:`) in timezone 1486 timezone = timezone.replace(ALL_COLONS, ''); 1487 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1488 return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1489} 1490 1491 1492function addDateMinutes(date, minutes) { 1493 date = new Date(date.getTime()); 1494 date.setMinutes(date.getMinutes() + minutes); 1495 return date; 1496} 1497 1498 1499function convertTimezoneToLocal(date, timezone, reverse) { 1500 reverse = reverse ? -1 : 1; 1501 var dateTimezoneOffset = date.getTimezoneOffset(); 1502 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 1503 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 1504} 1505 1506 1507/** 1508 * @returns {string} Returns the string representation of the element. 1509 */ 1510function startingTag(element) { 1511 element = jqLite(element).clone().empty(); 1512 var elemHtml = jqLite('<div></div>').append(element).html(); 1513 try { 1514 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1515 elemHtml. 1516 match(/^(<[^>]+>)/)[1]. 1517 replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); 1518 } catch (e) { 1519 return lowercase(elemHtml); 1520 } 1521 1522} 1523 1524 1525///////////////////////////////////////////////// 1526 1527/** 1528 * Tries to decode the URI component without throwing an exception. 1529 * 1530 * @private 1531 * @param str value potential URI component to check. 1532 * @returns {boolean} True if `value` can be decoded 1533 * with the decodeURIComponent function. 1534 */ 1535function tryDecodeURIComponent(value) { 1536 try { 1537 return decodeURIComponent(value); 1538 } catch (e) { 1539 // Ignore any invalid uri component. 1540 } 1541} 1542 1543 1544/** 1545 * Parses an escaped url query string into key-value pairs. 1546 * @returns {Object.<string,boolean|Array>} 1547 */ 1548function parseKeyValue(/**string*/keyValue) { 1549 var obj = {}; 1550 forEach((keyValue || '').split('&'), function(keyValue) { 1551 var splitPoint, key, val; 1552 if (keyValue) { 1553 key = keyValue = keyValue.replace(/\+/g,'%20'); 1554 splitPoint = keyValue.indexOf('='); 1555 if (splitPoint !== -1) { 1556 key = keyValue.substring(0, splitPoint); 1557 val = keyValue.substring(splitPoint + 1); 1558 } 1559 key = tryDecodeURIComponent(key); 1560 if (isDefined(key)) { 1561 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1562 if (!hasOwnProperty.call(obj, key)) { 1563 obj[key] = val; 1564 } else if (isArray(obj[key])) { 1565 obj[key].push(val); 1566 } else { 1567 obj[key] = [obj[key],val]; 1568 } 1569 } 1570 } 1571 }); 1572 return obj; 1573} 1574 1575function toKeyValue(obj) { 1576 var parts = []; 1577 forEach(obj, function(value, key) { 1578 if (isArray(value)) { 1579 forEach(value, function(arrayValue) { 1580 parts.push(encodeUriQuery(key, true) + 1581 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1582 }); 1583 } else { 1584 parts.push(encodeUriQuery(key, true) + 1585 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1586 } 1587 }); 1588 return parts.length ? parts.join('&') : ''; 1589} 1590 1591 1592/** 1593 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1594 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1595 * segments: 1596 * segment = *pchar 1597 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1598 * pct-encoded = "%" HEXDIG HEXDIG 1599 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1600 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1601 * / "*" / "+" / "," / ";" / "=" 1602 */ 1603function encodeUriSegment(val) { 1604 return encodeUriQuery(val, true). 1605 replace(/%26/gi, '&'). 1606 replace(/%3D/gi, '='). 1607 replace(/%2B/gi, '+'); 1608} 1609 1610 1611/** 1612 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1613 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1614 * encoded per http://tools.ietf.org/html/rfc3986: 1615 * query = *( pchar / "/" / "?" ) 1616 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1617 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1618 * pct-encoded = "%" HEXDIG HEXDIG 1619 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1620 * / "*" / "+" / "," / ";" / "=" 1621 */ 1622function encodeUriQuery(val, pctEncodeSpaces) { 1623 return encodeURIComponent(val). 1624 replace(/%40/gi, '@'). 1625 replace(/%3A/gi, ':'). 1626 replace(/%24/g, '$'). 1627 replace(/%2C/gi, ','). 1628 replace(/%3B/gi, ';'). 1629 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1630} 1631 1632var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1633 1634function getNgAttribute(element, ngAttr) { 1635 var attr, i, ii = ngAttrPrefixes.length; 1636 for (i = 0; i < ii; ++i) { 1637 attr = ngAttrPrefixes[i] + ngAttr; 1638 if (isString(attr = element.getAttribute(attr))) { 1639 return attr; 1640 } 1641 } 1642 return null; 1643} 1644 1645function allowAutoBootstrap(document) { 1646 var script = document.currentScript; 1647 1648 if (!script) { 1649 // Support: IE 9-11 only 1650 // IE does not have `document.currentScript` 1651 return true; 1652 } 1653 1654 // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack 1655 if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) { 1656 return false; 1657 } 1658 1659 var attributes = script.attributes; 1660 var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')]; 1661 1662 return srcs.every(function(src) { 1663 if (!src) { 1664 return true; 1665 } 1666 if (!src.value) { 1667 return false; 1668 } 1669 1670 var link = document.createElement('a'); 1671 link.href = src.value; 1672 1673 if (document.location.origin === link.origin) { 1674 // Same-origin resources are always allowed, even for banned URL schemes. 1675 return true; 1676 } 1677 // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. 1678 // This is to prevent angular.js bundled with browser extensions from being used to bypass the 1679 // content security policy in web pages and other browser extensions. 1680 switch (link.protocol) { 1681 case 'http:': 1682 case 'https:': 1683 case 'ftp:': 1684 case 'blob:': 1685 case 'file:': 1686 case 'data:': 1687 return true; 1688 default: 1689 return false; 1690 } 1691 }); 1692} 1693 1694// Cached as it has to run during loading so that document.currentScript is available. 1695var isAutoBootstrapAllowed = allowAutoBootstrap(window.document); 1696 1697/** 1698 * @ngdoc directive 1699 * @name ngApp 1700 * @module ng 1701 * 1702 * @element ANY 1703 * @param {angular.Module} ngApp an optional application 1704 * {@link angular.module module} name to load. 1705 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1706 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1707 * do not use explicit function annotation (and are thus unsuitable for minification), as described 1708 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1709 * tracking down the root of these bugs. 1710 * 1711 * @description 1712 * 1713 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1714 * designates the **root element** of the application and is typically placed near the root element 1715 * of the page - e.g. on the `<body>` or `<html>` tags. 1716 * 1717 * There are a few things to keep in mind when using `ngApp`: 1718 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1719 * found in the document will be used to define the root element to auto-bootstrap as an 1720 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1721 * {@link angular.bootstrap} instead. 1722 * - AngularJS applications cannot be nested within each other. 1723 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. 1724 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and 1725 * {@link ngRoute.ngView `ngView`}. 1726 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 1727 * causing animations to stop working and making the injector inaccessible from outside the app. 1728 * 1729 * You can specify an **AngularJS module** to be used as the root module for the application. This 1730 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1731 * should contain the application code needed or have dependencies on other modules that will 1732 * contain the code. See {@link angular.module} for more information. 1733 * 1734 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1735 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1736 * would not be resolved to `3`. 1737 * 1738 * @example 1739 * 1740 * ### Simple Usage 1741 * 1742 * `ngApp` is the easiest, and most common way to bootstrap an application. 1743 * 1744 <example module="ngAppDemo" name="ng-app"> 1745 <file name="index.html"> 1746 <div ng-controller="ngAppDemoController"> 1747 I can add: {{a}} + {{b}} = {{ a+b }} 1748 </div> 1749 </file> 1750 <file name="script.js"> 1751 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1752 $scope.a = 1; 1753 $scope.b = 2; 1754 }); 1755 </file> 1756 </example> 1757 * 1758 * @example 1759 * 1760 * ### With `ngStrictDi` 1761 * 1762 * Using `ngStrictDi`, you would see something like this: 1763 * 1764 <example ng-app-included="true" name="strict-di"> 1765 <file name="index.html"> 1766 <div ng-app="ngAppStrictDemo" ng-strict-di> 1767 <div ng-controller="GoodController1"> 1768 I can add: {{a}} + {{b}} = {{ a+b }} 1769 1770 <p>This renders because the controller does not fail to 1771 instantiate, by using explicit annotation style (see 1772 script.js for details) 1773 </p> 1774 </div> 1775 1776 <div ng-controller="GoodController2"> 1777 Name: <input ng-model="name"><br /> 1778 Hello, {{name}}! 1779 1780 <p>This renders because the controller does not fail to 1781 instantiate, by using explicit annotation style 1782 (see script.js for details) 1783 </p> 1784 </div> 1785 1786 <div ng-controller="BadController"> 1787 I can add: {{a}} + {{b}} = {{ a+b }} 1788 1789 <p>The controller could not be instantiated, due to relying 1790 on automatic function annotations (which are disabled in 1791 strict mode). As such, the content of this section is not 1792 interpolated, and there should be an error in your web console. 1793 </p> 1794 </div> 1795 </div> 1796 </file> 1797 <file name="script.js"> 1798 angular.module('ngAppStrictDemo', []) 1799 // BadController will fail to instantiate, due to relying on automatic function annotation, 1800 // rather than an explicit annotation 1801 .controller('BadController', function($scope) { 1802 $scope.a = 1; 1803 $scope.b = 2; 1804 }) 1805 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1806 // due to using explicit annotations using the array style and $inject property, respectively. 1807 .controller('GoodController1', ['$scope', function($scope) { 1808 $scope.a = 1; 1809 $scope.b = 2; 1810 }]) 1811 .controller('GoodController2', GoodController2); 1812 function GoodController2($scope) { 1813 $scope.name = 'World'; 1814 } 1815 GoodController2.$inject = ['$scope']; 1816 </file> 1817 <file name="style.css"> 1818 div[ng-controller] { 1819 margin-bottom: 1em; 1820 -webkit-border-radius: 4px; 1821 border-radius: 4px; 1822 border: 1px solid; 1823 padding: .5em; 1824 } 1825 div[ng-controller^=Good] { 1826 border-color: #d6e9c6; 1827 background-color: #dff0d8; 1828 color: #3c763d; 1829 } 1830 div[ng-controller^=Bad] { 1831 border-color: #ebccd1; 1832 background-color: #f2dede; 1833 color: #a94442; 1834 margin-bottom: 0; 1835 } 1836 </file> 1837 </example> 1838 */ 1839function angularInit(element, bootstrap) { 1840 var appElement, 1841 module, 1842 config = {}; 1843 1844 // The element `element` has priority over any other element. 1845 forEach(ngAttrPrefixes, function(prefix) { 1846 var name = prefix + 'app'; 1847 1848 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1849 appElement = element; 1850 module = element.getAttribute(name); 1851 } 1852 }); 1853 forEach(ngAttrPrefixes, function(prefix) { 1854 var name = prefix + 'app'; 1855 var candidate; 1856 1857 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1858 appElement = candidate; 1859 module = candidate.getAttribute(name); 1860 } 1861 }); 1862 if (appElement) { 1863 if (!isAutoBootstrapAllowed) { 1864 window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' + 1865 'an extension, document.location.href does not match.'); 1866 return; 1867 } 1868 config.strictDi = getNgAttribute(appElement, 'strict-di') !== null; 1869 bootstrap(appElement, module ? [module] : [], config); 1870 } 1871} 1872 1873/** 1874 * @ngdoc function 1875 * @name angular.bootstrap 1876 * @module ng 1877 * @description 1878 * Use this function to manually start up AngularJS application. 1879 * 1880 * For more information, see the {@link guide/bootstrap Bootstrap guide}. 1881 * 1882 * AngularJS will detect if it has been loaded into the browser more than once and only allow the 1883 * first loaded script to be bootstrapped and will report a warning to the browser console for 1884 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1885 * multiple instances of AngularJS try to work on the DOM. 1886 * 1887 * <div class="alert alert-warning"> 1888 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually. 1889 * They must use {@link ng.directive:ngApp ngApp}. 1890 * </div> 1891 * 1892 * <div class="alert alert-warning"> 1893 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, 1894 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. 1895 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 1896 * causing animations to stop working and making the injector inaccessible from outside the app. 1897 * </div> 1898 * 1899 * ```html 1900 * <!doctype html> 1901 * <html> 1902 * <body> 1903 * <div ng-controller="WelcomeController"> 1904 * {{greeting}} 1905 * </div> 1906 * 1907 * <script src="angular.js"></script> 1908 * <script> 1909 * var app = angular.module('demo', []) 1910 * .controller('WelcomeController', function($scope) { 1911 * $scope.greeting = 'Welcome!'; 1912 * }); 1913 * angular.bootstrap(document, ['demo']); 1914 * </script> 1915 * </body> 1916 * </html> 1917 * ``` 1918 * 1919 * @param {DOMElement} element DOM element which is the root of AngularJS application. 1920 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1921 * Each item in the array should be the name of a predefined module or a (DI annotated) 1922 * function that will be invoked by the injector as a `config` block. 1923 * See: {@link angular.module modules} 1924 * @param {Object=} config an object for defining configuration options for the application. The 1925 * following keys are supported: 1926 * 1927 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1928 * assist in finding bugs which break minified code. Defaults to `false`. 1929 * 1930 * @returns {auto.$injector} Returns the newly created injector for this app. 1931 */ 1932function bootstrap(element, modules, config) { 1933 if (!isObject(config)) config = {}; 1934 var defaultConfig = { 1935 strictDi: false 1936 }; 1937 config = extend(defaultConfig, config); 1938 var doBootstrap = function() { 1939 element = jqLite(element); 1940 1941 if (element.injector()) { 1942 var tag = (element[0] === window.document) ? 'document' : startingTag(element); 1943 // Encode angle brackets to prevent input from being sanitized to empty string #8683. 1944 throw ngMinErr( 1945 'btstrpd', 1946 'App already bootstrapped with this element \'{0}\'', 1947 tag.replace(/</,'<').replace(/>/,'>')); 1948 } 1949 1950 modules = modules || []; 1951 modules.unshift(['$provide', function($provide) { 1952 $provide.value('$rootElement', element); 1953 }]); 1954 1955 if (config.debugInfoEnabled) { 1956 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1957 modules.push(['$compileProvider', function($compileProvider) { 1958 $compileProvider.debugInfoEnabled(true); 1959 }]); 1960 } 1961 1962 modules.unshift('ng'); 1963 var injector = createInjector(modules, config.strictDi); 1964 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1965 function bootstrapApply(scope, element, compile, injector) { 1966 scope.$apply(function() { 1967 element.data('$injector', injector); 1968 compile(element)(scope); 1969 }); 1970 }] 1971 ); 1972 return injector; 1973 }; 1974 1975 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1976 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1977 1978 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1979 config.debugInfoEnabled = true; 1980 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1981 } 1982 1983 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1984 return doBootstrap(); 1985 } 1986 1987 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1988 angular.resumeBootstrap = function(extraModules) { 1989 forEach(extraModules, function(module) { 1990 modules.push(module); 1991 }); 1992 return doBootstrap(); 1993 }; 1994 1995 if (isFunction(angular.resumeDeferredBootstrap)) { 1996 angular.resumeDeferredBootstrap(); 1997 } 1998} 1999 2000/** 2001 * @ngdoc function 2002 * @name angular.reloadWithDebugInfo 2003 * @module ng 2004 * @description 2005 * Use this function to reload the current application with debug information turned on. 2006 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 2007 * 2008 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 2009 */ 2010function reloadWithDebugInfo() { 2011 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 2012 window.location.reload(); 2013} 2014 2015/** 2016 * @name angular.getTestability 2017 * @module ng 2018 * @description 2019 * Get the testability service for the instance of AngularJS on the given 2020 * element. 2021 * @param {DOMElement} element DOM element which is the root of AngularJS application. 2022 */ 2023function getTestability(rootElement) { 2024 var injector = angular.element(rootElement).injector(); 2025 if (!injector) { 2026 throw ngMinErr('test', 2027 'no injector found for element argument to getTestability'); 2028 } 2029 return injector.get('$$testability'); 2030} 2031 2032var SNAKE_CASE_REGEXP = /[A-Z]/g; 2033function snake_case(name, separator) { 2034 separator = separator || '_'; 2035 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 2036 return (pos ? separator : '') + letter.toLowerCase(); 2037 }); 2038} 2039 2040var bindJQueryFired = false; 2041function bindJQuery() { 2042 var originalCleanData; 2043 2044 if (bindJQueryFired) { 2045 return; 2046 } 2047 2048 // bind to jQuery if present; 2049 var jqName = jq(); 2050 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 2051 !jqName ? undefined : // use jqLite 2052 window[jqName]; // use jQuery specified by `ngJq` 2053 2054 // Use jQuery if it exists with proper functionality, otherwise default to us. 2055 // AngularJS 1.2+ requires jQuery 1.7+ for on()/off() support. 2056 // AngularJS 1.3+ technically requires at least jQuery 2.1+ but it may work with older 2057 // versions. It will not work for sure with jQuery <1.7, though. 2058 if (jQuery && jQuery.fn.on) { 2059 jqLite = jQuery; 2060 extend(jQuery.fn, { 2061 scope: JQLitePrototype.scope, 2062 isolateScope: JQLitePrototype.isolateScope, 2063 controller: /** @type {?} */ (JQLitePrototype).controller, 2064 injector: JQLitePrototype.injector, 2065 inheritedData: JQLitePrototype.inheritedData 2066 }); 2067 } else { 2068 jqLite = JQLite; 2069 } 2070 2071 // All nodes removed from the DOM via various jqLite/jQuery APIs like .remove() 2072 // are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire 2073 // the $destroy event on all removed nodes. 2074 originalCleanData = jqLite.cleanData; 2075 jqLite.cleanData = function(elems) { 2076 var events; 2077 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 2078 events = (jqLite._data(elem) || {}).events; 2079 if (events && events.$destroy) { 2080 jqLite(elem).triggerHandler('$destroy'); 2081 } 2082 } 2083 originalCleanData(elems); 2084 }; 2085 2086 angular.element = jqLite; 2087 2088 // Prevent double-proxying. 2089 bindJQueryFired = true; 2090} 2091 2092/** 2093 * @ngdoc function 2094 * @name angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement 2095 * @module ng 2096 * @kind function 2097 * 2098 * @description 2099 * Restores the pre-1.8 behavior of jqLite that turns XHTML-like strings like 2100 * `<div /><span />` to `<div></div><span></span>` instead of `<div><span></span></div>`. 2101 * The new behavior is a security fix. Thus, if you need to call this function, please try to adjust 2102 * your code for this change and remove your use of this function as soon as possible. 2103 2104 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the 2105 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details 2106 * about the workarounds. 2107 */ 2108function UNSAFE_restoreLegacyJqLiteXHTMLReplacement() { 2109 JQLite.legacyXHTMLReplacement = true; 2110} 2111 2112/** 2113 * throw error if the argument is falsy. 2114 */ 2115function assertArg(arg, name, reason) { 2116 if (!arg) { 2117 throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); 2118 } 2119 return arg; 2120} 2121 2122function assertArgFn(arg, name, acceptArrayAnnotation) { 2123 if (acceptArrayAnnotation && isArray(arg)) { 2124 arg = arg[arg.length - 1]; 2125 } 2126 2127 assertArg(isFunction(arg), name, 'not a function, got ' + 2128 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 2129 return arg; 2130} 2131 2132/** 2133 * throw error if the name given is hasOwnProperty 2134 * @param {String} name the name to test 2135 * @param {String} context the context in which the name is used, such as module or directive 2136 */ 2137function assertNotHasOwnProperty(name, context) { 2138 if (name === 'hasOwnProperty') { 2139 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2140 } 2141} 2142 2143/** 2144 * Return the value accessible from the object by path. Any undefined traversals are ignored 2145 * @param {Object} obj starting object 2146 * @param {String} path path to traverse 2147 * @param {boolean} [bindFnToScope=true] 2148 * @returns {Object} value as accessible by path 2149 */ 2150//TODO(misko): this function needs to be removed 2151function getter(obj, path, bindFnToScope) { 2152 if (!path) return obj; 2153 var keys = path.split('.'); 2154 var key; 2155 var lastInstance = obj; 2156 var len = keys.length; 2157 2158 for (var i = 0; i < len; i++) { 2159 key = keys[i]; 2160 if (obj) { 2161 obj = (lastInstance = obj)[key]; 2162 } 2163 } 2164 if (!bindFnToScope && isFunction(obj)) { 2165 return bind(lastInstance, obj); 2166 } 2167 return obj; 2168} 2169 2170/** 2171 * Return the DOM siblings between the first and last node in the given array. 2172 * @param {Array} array like object 2173 * @returns {Array} the inputted object or a jqLite collection containing the nodes 2174 */ 2175function getBlockNodes(nodes) { 2176 // TODO(perf): update `nodes` instead of creating a new object? 2177 var node = nodes[0]; 2178 var endNode = nodes[nodes.length - 1]; 2179 var blockNodes; 2180 2181 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 2182 if (blockNodes || nodes[i] !== node) { 2183 if (!blockNodes) { 2184 blockNodes = jqLite(slice.call(nodes, 0, i)); 2185 } 2186 blockNodes.push(node); 2187 } 2188 } 2189 2190 return blockNodes || nodes; 2191} 2192 2193 2194/** 2195 * Creates a new object without a prototype. This object is useful for lookup without having to 2196 * guard against prototypically inherited properties via hasOwnProperty. 2197 * 2198 * Related micro-benchmarks: 2199 * - http://jsperf.com/object-create2 2200 * - http://jsperf.com/proto-map-lookup/2 2201 * - http://jsperf.com/for-in-vs-object-keys2 2202 * 2203 * @returns {Object} 2204 */ 2205function createMap() { 2206 return Object.create(null); 2207} 2208 2209function stringify(value) { 2210 if (value == null) { // null || undefined 2211 return ''; 2212 } 2213 switch (typeof value) { 2214 case 'string': 2215 break; 2216 case 'number': 2217 value = '' + value; 2218 break; 2219 default: 2220 if (hasCustomToString(value) && !isArray(value) && !isDate(value)) { 2221 value = value.toString(); 2222 } else { 2223 value = toJson(value); 2224 } 2225 } 2226 2227 return value; 2228} 2229 2230var NODE_TYPE_ELEMENT = 1; 2231var NODE_TYPE_ATTRIBUTE = 2; 2232var NODE_TYPE_TEXT = 3; 2233var NODE_TYPE_COMMENT = 8; 2234var NODE_TYPE_DOCUMENT = 9; 2235var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 2236 2237/** 2238 * @ngdoc type 2239 * @name angular.Module 2240 * @module ng 2241 * @description 2242 * 2243 * Interface for configuring AngularJS {@link angular.module modules}. 2244 */ 2245 2246function setupModuleLoader(window) { 2247 2248 var $injectorMinErr = minErr('$injector'); 2249 var ngMinErr = minErr('ng'); 2250 2251 function ensure(obj, name, factory) { 2252 return obj[name] || (obj[name] = factory()); 2253 } 2254 2255 var angular = ensure(window, 'angular', Object); 2256 2257 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 2258 angular.$$minErr = angular.$$minErr || minErr; 2259 2260 return ensure(angular, 'module', function() { 2261 /** @type {Object.<string, angular.Module>} */ 2262 var modules = {}; 2263 2264 /** 2265 * @ngdoc function 2266 * @name angular.module 2267 * @module ng 2268 * @description 2269 * 2270 * The `angular.module` is a global place for creating, registering and retrieving AngularJS 2271 * modules. 2272 * All modules (AngularJS core or 3rd party) that should be available to an application must be 2273 * registered using this mechanism. 2274 * 2275 * Passing one argument retrieves an existing {@link angular.Module}, 2276 * whereas passing more than one argument creates a new {@link angular.Module} 2277 * 2278 * 2279 * # Module 2280 * 2281 * A module is a collection of services, directives, controllers, filters, and configuration information. 2282 * `angular.module` is used to configure the {@link auto.$injector $injector}. 2283 * 2284 * ```js 2285 * // Create a new module 2286 * var myModule = angular.module('myModule', []); 2287 * 2288 * // register a new service 2289 * myModule.value('appName', 'MyCoolApp'); 2290 * 2291 * // configure existing services inside initialization blocks. 2292 * myModule.config(['$locationProvider', function($locationProvider) { 2293 * // Configure existing providers 2294 * $locationProvider.hashPrefix('!'); 2295 * }]); 2296 * ``` 2297 * 2298 * Then you can create an injector and load your modules like this: 2299 * 2300 * ```js 2301 * var injector = angular.injector(['ng', 'myModule']) 2302 * ``` 2303 * 2304 * However it's more likely that you'll just use 2305 * {@link ng.directive:ngApp ngApp} or 2306 * {@link angular.bootstrap} to simplify this process for you. 2307 * 2308 * @param {!string} name The name of the module to create or retrieve. 2309 * @param {!Array.<string>=} requires If specified then new module is being created. If 2310 * unspecified then the module is being retrieved for further configuration. 2311 * @param {Function=} configFn Optional configuration function for the module. Same as 2312 * {@link angular.Module#config Module#config()}. 2313 * @returns {angular.Module} new module with the {@link angular.Module} api. 2314 */ 2315 return function module(name, requires, configFn) { 2316 2317 var info = {}; 2318 2319 var assertNotHasOwnProperty = function(name, context) { 2320 if (name === 'hasOwnProperty') { 2321 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2322 } 2323 }; 2324 2325 assertNotHasOwnProperty(name, 'module'); 2326 if (requires && modules.hasOwnProperty(name)) { 2327 modules[name] = null; 2328 } 2329 return ensure(modules, name, function() { 2330 if (!requires) { 2331 throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' + 2332 'the module name or forgot to load it. If registering a module ensure that you ' + 2333 'specify the dependencies as the second argument.', name); 2334 } 2335 2336 /** @type {!Array.<Array.<*>>} */ 2337 var invokeQueue = []; 2338 2339 /** @type {!Array.<Function>} */ 2340 var configBlocks = []; 2341 2342 /** @type {!Array.<Function>} */ 2343 var runBlocks = []; 2344 2345 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2346 2347 /** @type {angular.Module} */ 2348 var moduleInstance = { 2349 // Private state 2350 _invokeQueue: invokeQueue, 2351 _configBlocks: configBlocks, 2352 _runBlocks: runBlocks, 2353 2354 /** 2355 * @ngdoc method 2356 * @name angular.Module#info 2357 * @module ng 2358 * 2359 * @param {Object=} info Information about the module 2360 * @returns {Object|Module} The current info object for this module if called as a getter, 2361 * or `this` if called as a setter. 2362 * 2363 * @description 2364 * Read and write custom information about this module. 2365 * For example you could put the version of the module in here. 2366 * 2367 * ```js 2368 * angular.module('myModule', []).info({ version: '1.0.0' }); 2369 * ``` 2370 * 2371 * The version could then be read back out by accessing the module elsewhere: 2372 * 2373 * ``` 2374 * var version = angular.module('myModule').info().version; 2375 * ``` 2376 * 2377 * You can also retrieve this information during runtime via the 2378 * {@link $injector#modules `$injector.modules`} property: 2379 * 2380 * ```js 2381 * var version = $injector.modules['myModule'].info().version; 2382 * ``` 2383 */ 2384 info: function(value) { 2385 if (isDefined(value)) { 2386 if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value'); 2387 info = value; 2388 return this; 2389 } 2390 return info; 2391 }, 2392 2393 /** 2394 * @ngdoc property 2395 * @name angular.Module#requires 2396 * @module ng 2397 * 2398 * @description 2399 * Holds the list of modules which the injector will load before the current module is 2400 * loaded. 2401 */ 2402 requires: requires, 2403 2404 /** 2405 * @ngdoc property 2406 * @name angular.Module#name 2407 * @module ng 2408 * 2409 * @description 2410 * Name of the module. 2411 */ 2412 name: name, 2413 2414 2415 /** 2416 * @ngdoc method 2417 * @name angular.Module#provider 2418 * @module ng 2419 * @param {string} name service name 2420 * @param {Function} providerType Construction function for creating new instance of the 2421 * service. 2422 * @description 2423 * See {@link auto.$provide#provider $provide.provider()}. 2424 */ 2425 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2426 2427 /** 2428 * @ngdoc method 2429 * @name angular.Module#factory 2430 * @module ng 2431 * @param {string} name service name 2432 * @param {Function} providerFunction Function for creating new instance of the service. 2433 * @description 2434 * See {@link auto.$provide#factory $provide.factory()}. 2435 */ 2436 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2437 2438 /** 2439 * @ngdoc method 2440 * @name angular.Module#service 2441 * @module ng 2442 * @param {string} name service name 2443 * @param {Function} constructor A constructor function that will be instantiated. 2444 * @description 2445 * See {@link auto.$provide#service $provide.service()}. 2446 */ 2447 service: invokeLaterAndSetModuleName('$provide', 'service'), 2448 2449 /** 2450 * @ngdoc method 2451 * @name angular.Module#value 2452 * @module ng 2453 * @param {string} name service name 2454 * @param {*} object Service instance object. 2455 * @description 2456 * See {@link auto.$provide#value $provide.value()}. 2457 */ 2458 value: invokeLater('$provide', 'value'), 2459 2460 /** 2461 * @ngdoc method 2462 * @name angular.Module#constant 2463 * @module ng 2464 * @param {string} name constant name 2465 * @param {*} object Constant value. 2466 * @description 2467 * Because the constants are fixed, they get applied before other provide methods. 2468 * See {@link auto.$provide#constant $provide.constant()}. 2469 */ 2470 constant: invokeLater('$provide', 'constant', 'unshift'), 2471 2472 /** 2473 * @ngdoc method 2474 * @name angular.Module#decorator 2475 * @module ng 2476 * @param {string} name The name of the service to decorate. 2477 * @param {Function} decorFn This function will be invoked when the service needs to be 2478 * instantiated and should return the decorated service instance. 2479 * @description 2480 * See {@link auto.$provide#decorator $provide.decorator()}. 2481 */ 2482 decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks), 2483 2484 /** 2485 * @ngdoc method 2486 * @name angular.Module#animation 2487 * @module ng 2488 * @param {string} name animation name 2489 * @param {Function} animationFactory Factory function for creating new instance of an 2490 * animation. 2491 * @description 2492 * 2493 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2494 * 2495 * 2496 * Defines an animation hook that can be later used with 2497 * {@link $animate $animate} service and directives that use this service. 2498 * 2499 * ```js 2500 * module.animation('.animation-name', function($inject1, $inject2) { 2501 * return { 2502 * eventName : function(element, done) { 2503 * //code to run the animation 2504 * //once complete, then run done() 2505 * return function cancellationFunction(element) { 2506 * //code to cancel the animation 2507 * } 2508 * } 2509 * } 2510 * }) 2511 * ``` 2512 * 2513 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2514 * {@link ngAnimate ngAnimate module} for more information. 2515 */ 2516 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2517 2518 /** 2519 * @ngdoc method 2520 * @name angular.Module#filter 2521 * @module ng 2522 * @param {string} name Filter name - this must be a valid AngularJS expression identifier 2523 * @param {Function} filterFactory Factory function for creating new instance of filter. 2524 * @description 2525 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2526 * 2527 * <div class="alert alert-warning"> 2528 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 2529 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2530 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2531 * (`myapp_subsection_filterx`). 2532 * </div> 2533 */ 2534 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2535 2536 /** 2537 * @ngdoc method 2538 * @name angular.Module#controller 2539 * @module ng 2540 * @param {string|Object} name Controller name, or an object map of controllers where the 2541 * keys are the names and the values are the constructors. 2542 * @param {Function} constructor Controller constructor function. 2543 * @description 2544 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2545 */ 2546 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2547 2548 /** 2549 * @ngdoc method 2550 * @name angular.Module#directive 2551 * @module ng 2552 * @param {string|Object} name Directive name, or an object map of directives where the 2553 * keys are the names and the values are the factories. 2554 * @param {Function} directiveFactory Factory function for creating new instance of 2555 * directives. 2556 * @description 2557 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2558 */ 2559 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2560 2561 /** 2562 * @ngdoc method 2563 * @name angular.Module#component 2564 * @module ng 2565 * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`), 2566 * or an object map of components where the keys are the names and the values are the component definition objects. 2567 * @param {Object} options Component definition object (a simplified 2568 * {@link ng.$compile#directive-definition-object directive definition object}) 2569 * 2570 * @description 2571 * See {@link ng.$compileProvider#component $compileProvider.component()}. 2572 */ 2573 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 2574 2575 /** 2576 * @ngdoc method 2577 * @name angular.Module#config 2578 * @module ng 2579 * @param {Function} configFn Execute this function on module load. Useful for service 2580 * configuration. 2581 * @description 2582 * Use this method to configure services by injecting their 2583 * {@link angular.Module#provider `providers`}, e.g. for adding routes to the 2584 * {@link ngRoute.$routeProvider $routeProvider}. 2585 * 2586 * Note that you can only inject {@link angular.Module#provider `providers`} and 2587 * {@link angular.Module#constant `constants`} into this function. 2588 * 2589 * For more about how to configure services, see 2590 * {@link providers#provider-recipe Provider Recipe}. 2591 */ 2592 config: config, 2593 2594 /** 2595 * @ngdoc method 2596 * @name angular.Module#run 2597 * @module ng 2598 * @param {Function} initializationFn Execute this function after injector creation. 2599 * Useful for application initialization. 2600 * @description 2601 * Use this method to register work which should be performed when the injector is done 2602 * loading all modules. 2603 */ 2604 run: function(block) { 2605 runBlocks.push(block); 2606 return this; 2607 } 2608 }; 2609 2610 if (configFn) { 2611 config(configFn); 2612 } 2613 2614 return moduleInstance; 2615 2616 /** 2617 * @param {string} provider 2618 * @param {string} method 2619 * @param {String=} insertMethod 2620 * @returns {angular.Module} 2621 */ 2622 function invokeLater(provider, method, insertMethod, queue) { 2623 if (!queue) queue = invokeQueue; 2624 return function() { 2625 queue[insertMethod || 'push']([provider, method, arguments]); 2626 return moduleInstance; 2627 }; 2628 } 2629 2630 /** 2631 * @param {string} provider 2632 * @param {string} method 2633 * @returns {angular.Module} 2634 */ 2635 function invokeLaterAndSetModuleName(provider, method, queue) { 2636 if (!queue) queue = invokeQueue; 2637 return function(recipeName, factoryFunction) { 2638 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2639 queue.push([provider, method, arguments]); 2640 return moduleInstance; 2641 }; 2642 } 2643 }); 2644 }; 2645 }); 2646 2647} 2648 2649/* global shallowCopy: true */ 2650 2651/** 2652 * Creates a shallow copy of an object, an array or a primitive. 2653 * 2654 * Assumes that there are no proto properties for objects. 2655 */ 2656function shallowCopy(src, dst) { 2657 if (isArray(src)) { 2658 dst = dst || []; 2659 2660 for (var i = 0, ii = src.length; i < ii; i++) { 2661 dst[i] = src[i]; 2662 } 2663 } else if (isObject(src)) { 2664 dst = dst || {}; 2665 2666 for (var key in src) { 2667 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 2668 dst[key] = src[key]; 2669 } 2670 } 2671 } 2672 2673 return dst || src; 2674} 2675 2676/* exported toDebugString */ 2677 2678function serializeObject(obj, maxDepth) { 2679 var seen = []; 2680 2681 // There is no direct way to stringify object until reaching a specific depth 2682 // and a very deep object can cause a performance issue, so we copy the object 2683 // based on this specific depth and then stringify it. 2684 if (isValidObjectMaxDepth(maxDepth)) { 2685 // This file is also included in `angular-loader`, so `copy()` might not always be available in 2686 // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed. 2687 obj = angular.copy(obj, null, maxDepth); 2688 } 2689 return JSON.stringify(obj, function(key, val) { 2690 val = toJsonReplacer(key, val); 2691 if (isObject(val)) { 2692 2693 if (seen.indexOf(val) >= 0) return '...'; 2694 2695 seen.push(val); 2696 } 2697 return val; 2698 }); 2699} 2700 2701function toDebugString(obj, maxDepth) { 2702 if (typeof obj === 'function') { 2703 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2704 } else if (isUndefined(obj)) { 2705 return 'undefined'; 2706 } else if (typeof obj !== 'string') { 2707 return serializeObject(obj, maxDepth); 2708 } 2709 return obj; 2710} 2711 2712/* global angularModule: true, 2713 version: true, 2714 2715 $CompileProvider, 2716 2717 htmlAnchorDirective, 2718 inputDirective, 2719 hiddenInputBrowserCacheDirective, 2720 formDirective, 2721 scriptDirective, 2722 selectDirective, 2723 optionDirective, 2724 ngBindDirective, 2725 ngBindHtmlDirective, 2726 ngBindTemplateDirective, 2727 ngClassDirective, 2728 ngClassEvenDirective, 2729 ngClassOddDirective, 2730 ngCloakDirective, 2731 ngControllerDirective, 2732 ngFormDirective, 2733 ngHideDirective, 2734 ngIfDirective, 2735 ngIncludeDirective, 2736 ngIncludeFillContentDirective, 2737 ngInitDirective, 2738 ngNonBindableDirective, 2739 ngPluralizeDirective, 2740 ngRefDirective, 2741 ngRepeatDirective, 2742 ngShowDirective, 2743 ngStyleDirective, 2744 ngSwitchDirective, 2745 ngSwitchWhenDirective, 2746 ngSwitchDefaultDirective, 2747 ngOptionsDirective, 2748 ngTranscludeDirective, 2749 ngModelDirective, 2750 ngListDirective, 2751 ngChangeDirective, 2752 patternDirective, 2753 patternDirective, 2754 requiredDirective, 2755 requiredDirective, 2756 minlengthDirective, 2757 minlengthDirective, 2758 maxlengthDirective, 2759 maxlengthDirective, 2760 ngValueDirective, 2761 ngModelOptionsDirective, 2762 ngAttributeAliasDirectives, 2763 ngEventDirectives, 2764 2765 $AnchorScrollProvider, 2766 $AnimateProvider, 2767 $CoreAnimateCssProvider, 2768 $$CoreAnimateJsProvider, 2769 $$CoreAnimateQueueProvider, 2770 $$AnimateRunnerFactoryProvider, 2771 $$AnimateAsyncRunFactoryProvider, 2772 $BrowserProvider, 2773 $CacheFactoryProvider, 2774 $ControllerProvider, 2775 $DateProvider, 2776 $DocumentProvider, 2777 $$IsDocumentHiddenProvider, 2778 $ExceptionHandlerProvider, 2779 $FilterProvider, 2780 $$ForceReflowProvider, 2781 $InterpolateProvider, 2782 $$IntervalFactoryProvider, 2783 $IntervalProvider, 2784 $HttpProvider, 2785 $HttpParamSerializerProvider, 2786 $HttpParamSerializerJQLikeProvider, 2787 $HttpBackendProvider, 2788 $xhrFactoryProvider, 2789 $jsonpCallbacksProvider, 2790 $LocationProvider, 2791 $LogProvider, 2792 $$MapProvider, 2793 $ParseProvider, 2794 $RootScopeProvider, 2795 $QProvider, 2796 $$QProvider, 2797 $$SanitizeUriProvider, 2798 $SceProvider, 2799 $SceDelegateProvider, 2800 $SnifferProvider, 2801 $$TaskTrackerFactoryProvider, 2802 $TemplateCacheProvider, 2803 $TemplateRequestProvider, 2804 $$TestabilityProvider, 2805 $TimeoutProvider, 2806 $$RAFProvider, 2807 $WindowProvider, 2808 $$jqLiteProvider, 2809 $$CookieReaderProvider 2810*/ 2811 2812 2813/** 2814 * @ngdoc object 2815 * @name angular.version 2816 * @module ng 2817 * @description 2818 * An object that contains information about the current AngularJS version. 2819 * 2820 * This object has the following properties: 2821 * 2822 * - `full` – `{string}` – Full version string, such as "0.9.18". 2823 * - `major` – `{number}` – Major version number, such as "0". 2824 * - `minor` – `{number}` – Minor version number, such as "9". 2825 * - `dot` – `{number}` – Dot version number, such as "18". 2826 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". 2827 */ 2828var version = { 2829 // These placeholder strings will be replaced by grunt's `build` task. 2830 // They need to be double- or single-quoted. 2831 full: '1.8.3', 2832 major: 1, 2833 minor: 8, 2834 dot: 3, 2835 codeName: 'ultimate-farewell' 2836}; 2837 2838 2839function publishExternalAPI(angular) { 2840 extend(angular, { 2841 'errorHandlingConfig': errorHandlingConfig, 2842 'bootstrap': bootstrap, 2843 'copy': copy, 2844 'extend': extend, 2845 'merge': merge, 2846 'equals': equals, 2847 'element': jqLite, 2848 'forEach': forEach, 2849 'injector': createInjector, 2850 'noop': noop, 2851 'bind': bind, 2852 'toJson': toJson, 2853 'fromJson': fromJson, 2854 'identity': identity, 2855 'isUndefined': isUndefined, 2856 'isDefined': isDefined, 2857 'isString': isString, 2858 'isFunction': isFunction, 2859 'isObject': isObject, 2860 'isNumber': isNumber, 2861 'isElement': isElement, 2862 'isArray': isArray, 2863 'version': version, 2864 'isDate': isDate, 2865 'callbacks': {$$counter: 0}, 2866 'getTestability': getTestability, 2867 'reloadWithDebugInfo': reloadWithDebugInfo, 2868 'UNSAFE_restoreLegacyJqLiteXHTMLReplacement': UNSAFE_restoreLegacyJqLiteXHTMLReplacement, 2869 '$$minErr': minErr, 2870 '$$csp': csp, 2871 '$$encodeUriSegment': encodeUriSegment, 2872 '$$encodeUriQuery': encodeUriQuery, 2873 '$$lowercase': lowercase, 2874 '$$stringify': stringify, 2875 '$$uppercase': uppercase 2876 }); 2877 2878 angularModule = setupModuleLoader(window); 2879 2880 angularModule('ng', ['ngLocale'], ['$provide', 2881 function ngModule($provide) { 2882 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2883 $provide.provider({ 2884 $$sanitizeUri: $$SanitizeUriProvider 2885 }); 2886 $provide.provider('$compile', $CompileProvider). 2887 directive({ 2888 a: htmlAnchorDirective, 2889 input: inputDirective, 2890 textarea: inputDirective, 2891 form: formDirective, 2892 script: scriptDirective, 2893 select: selectDirective, 2894 option: optionDirective, 2895 ngBind: ngBindDirective, 2896 ngBindHtml: ngBindHtmlDirective, 2897 ngBindTemplate: ngBindTemplateDirective, 2898 ngClass: ngClassDirective, 2899 ngClassEven: ngClassEvenDirective, 2900 ngClassOdd: ngClassOddDirective, 2901 ngCloak: ngCloakDirective, 2902 ngController: ngControllerDirective, 2903 ngForm: ngFormDirective, 2904 ngHide: ngHideDirective, 2905 ngIf: ngIfDirective, 2906 ngInclude: ngIncludeDirective, 2907 ngInit: ngInitDirective, 2908 ngNonBindable: ngNonBindableDirective, 2909 ngPluralize: ngPluralizeDirective, 2910 ngRef: ngRefDirective, 2911 ngRepeat: ngRepeatDirective, 2912 ngShow: ngShowDirective, 2913 ngStyle: ngStyleDirective, 2914 ngSwitch: ngSwitchDirective, 2915 ngSwitchWhen: ngSwitchWhenDirective, 2916 ngSwitchDefault: ngSwitchDefaultDirective, 2917 ngOptions: ngOptionsDirective, 2918 ngTransclude: ngTranscludeDirective, 2919 ngModel: ngModelDirective, 2920 ngList: ngListDirective, 2921 ngChange: ngChangeDirective, 2922 pattern: patternDirective, 2923 ngPattern: patternDirective, 2924 required: requiredDirective, 2925 ngRequired: requiredDirective, 2926 minlength: minlengthDirective, 2927 ngMinlength: minlengthDirective, 2928 maxlength: maxlengthDirective, 2929 ngMaxlength: maxlengthDirective, 2930 ngValue: ngValueDirective, 2931 ngModelOptions: ngModelOptionsDirective 2932 }). 2933 directive({ 2934 ngInclude: ngIncludeFillContentDirective, 2935 input: hiddenInputBrowserCacheDirective 2936 }). 2937 directive(ngAttributeAliasDirectives). 2938 directive(ngEventDirectives); 2939 $provide.provider({ 2940 $anchorScroll: $AnchorScrollProvider, 2941 $animate: $AnimateProvider, 2942 $animateCss: $CoreAnimateCssProvider, 2943 $$animateJs: $$CoreAnimateJsProvider, 2944 $$animateQueue: $$CoreAnimateQueueProvider, 2945 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 2946 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 2947 $browser: $BrowserProvider, 2948 $cacheFactory: $CacheFactoryProvider, 2949 $controller: $ControllerProvider, 2950 $document: $DocumentProvider, 2951 $$isDocumentHidden: $$IsDocumentHiddenProvider, 2952 $exceptionHandler: $ExceptionHandlerProvider, 2953 $filter: $FilterProvider, 2954 $$forceReflow: $$ForceReflowProvider, 2955 $interpolate: $InterpolateProvider, 2956 $interval: $IntervalProvider, 2957 $$intervalFactory: $$IntervalFactoryProvider, 2958 $http: $HttpProvider, 2959 $httpParamSerializer: $HttpParamSerializerProvider, 2960 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2961 $httpBackend: $HttpBackendProvider, 2962 $xhrFactory: $xhrFactoryProvider, 2963 $jsonpCallbacks: $jsonpCallbacksProvider, 2964 $location: $LocationProvider, 2965 $log: $LogProvider, 2966 $parse: $ParseProvider, 2967 $rootScope: $RootScopeProvider, 2968 $q: $QProvider, 2969 $$q: $$QProvider, 2970 $sce: $SceProvider, 2971 $sceDelegate: $SceDelegateProvider, 2972 $sniffer: $SnifferProvider, 2973 $$taskTrackerFactory: $$TaskTrackerFactoryProvider, 2974 $templateCache: $TemplateCacheProvider, 2975 $templateRequest: $TemplateRequestProvider, 2976 $$testability: $$TestabilityProvider, 2977 $timeout: $TimeoutProvider, 2978 $window: $WindowProvider, 2979 $$rAF: $$RAFProvider, 2980 $$jqLite: $$jqLiteProvider, 2981 $$Map: $$MapProvider, 2982 $$cookieReader: $$CookieReaderProvider 2983 }); 2984 } 2985 ]) 2986 .info({ angularVersion: '1.8.3' }); 2987} 2988 2989/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2990 * Any commits to this file should be reviewed with security in mind. * 2991 * Changes to this file can potentially create security vulnerabilities. * 2992 * An approval from 2 Core members with history of modifying * 2993 * this file is required. * 2994 * * 2995 * Does the change somehow allow for arbitrary javascript to be executed? * 2996 * Or allows for someone to change the prototype of built-in objects? * 2997 * Or gives undesired access to variables likes document or window? * 2998 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2999 3000/* global 3001 JQLitePrototype: true, 3002 BOOLEAN_ATTR: true, 3003 ALIASED_ATTR: true 3004*/ 3005 3006////////////////////////////////// 3007//JQLite 3008////////////////////////////////// 3009 3010/** 3011 * @ngdoc function 3012 * @name angular.element 3013 * @module ng 3014 * @kind function 3015 * 3016 * @description 3017 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 3018 * 3019 * If jQuery is available, `angular.element` is an alias for the 3020 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 3021 * delegates to AngularJS's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 3022 * 3023 * jqLite is a tiny, API-compatible subset of jQuery that allows 3024 * AngularJS to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 3025 * commonly needed functionality with the goal of having a very small footprint. 3026 * 3027 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 3028 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 3029 * specific version of jQuery if multiple versions exist on the page. 3030 * 3031 * <div class="alert alert-info">**Note:** All element references in AngularJS are always wrapped with jQuery or 3032 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 3033 * 3034 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 3035 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 3036 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 3037 * 3038 * ## AngularJS's jqLite 3039 * jqLite provides only the following jQuery methods: 3040 * 3041 * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument 3042 * - [`after()`](http://api.jquery.com/after/) 3043 * - [`append()`](http://api.jquery.com/append/) - Contrary to jQuery, this doesn't clone elements 3044 * so will not work correctly when invoked on a jqLite object containing more than one DOM node 3045 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 3046 * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData 3047 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 3048 * - [`clone()`](http://api.jquery.com/clone/) 3049 * - [`contents()`](http://api.jquery.com/contents/) 3050 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 3051 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 3052 * - [`data()`](http://api.jquery.com/data/) 3053 * - [`detach()`](http://api.jquery.com/detach/) 3054 * - [`empty()`](http://api.jquery.com/empty/) 3055 * - [`eq()`](http://api.jquery.com/eq/) 3056 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 3057 * - [`hasClass()`](http://api.jquery.com/hasClass/) 3058 * - [`html()`](http://api.jquery.com/html/) 3059 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 3060 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 3061 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 3062 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 3063 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 3064 * - [`prepend()`](http://api.jquery.com/prepend/) 3065 * - [`prop()`](http://api.jquery.com/prop/) 3066 * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`) 3067 * - [`remove()`](http://api.jquery.com/remove/) 3068 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes 3069 * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument 3070 * - [`removeData()`](http://api.jquery.com/removeData/) 3071 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 3072 * - [`text()`](http://api.jquery.com/text/) 3073 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument 3074 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers 3075 * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter 3076 * - [`val()`](http://api.jquery.com/val/) 3077 * - [`wrap()`](http://api.jquery.com/wrap/) 3078 * 3079 * jqLite also provides a method restoring pre-1.8 insecure treatment of XHTML-like tags. 3080 * This legacy behavior turns input like `<div /><span />` to `<div></div><span></span>` 3081 * instead of `<div><span></span></div>` like version 1.8 & newer do. To restore it, invoke: 3082 * ```js 3083 * angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement(); 3084 * ``` 3085 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the 3086 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details 3087 * about the workarounds. 3088 * 3089 * ## jQuery/jqLite Extras 3090 * AngularJS also provides the following additional methods and events to both jQuery and jqLite: 3091 * 3092 * ### Events 3093 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 3094 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 3095 * element before it is removed. 3096 * 3097 * ### Methods 3098 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 3099 * retrieves controller associated with the `ngController` directive. If `name` is provided as 3100 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 3101 * `'ngModel'`). 3102 * - `injector()` - retrieves the injector of the current element or its parent. 3103 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 3104 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 3105 * be enabled. 3106 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 3107 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 3108 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 3109 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 3110 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 3111 * parent element is reached. 3112 * 3113 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See 3114 * https://github.com/angular/angular.js/issues/14251 for more information. 3115 * 3116 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 3117 * @returns {Object} jQuery object. 3118 */ 3119 3120JQLite.expando = 'ng339'; 3121 3122var jqCache = JQLite.cache = {}, 3123 jqId = 1; 3124 3125/* 3126 * !!! This is an undocumented "private" function !!! 3127 */ 3128JQLite._data = function(node) { 3129 //jQuery always returns an object on cache miss 3130 return this.cache[node[this.expando]] || {}; 3131}; 3132 3133function jqNextId() { return ++jqId; } 3134 3135 3136var DASH_LOWERCASE_REGEXP = /-([a-z])/g; 3137var MS_HACK_REGEXP = /^-ms-/; 3138var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; 3139var jqLiteMinErr = minErr('jqLite'); 3140 3141/** 3142 * Converts kebab-case to camelCase. 3143 * There is also a special case for the ms prefix starting with a lowercase letter. 3144 * @param name Name to normalize 3145 */ 3146function cssKebabToCamel(name) { 3147 return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-')); 3148} 3149 3150function fnCamelCaseReplace(all, letter) { 3151 return letter.toUpperCase(); 3152} 3153 3154/** 3155 * Converts kebab-case to camelCase. 3156 * @param name Name to normalize 3157 */ 3158function kebabToCamel(name) { 3159 return name 3160 .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace); 3161} 3162 3163var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 3164var HTML_REGEXP = /<|&#?\w+;/; 3165var TAG_NAME_REGEXP = /<([\w:-]+)/; 3166var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 3167 3168// Table parts need to be wrapped with `<table>` or they're 3169// stripped to their contents when put in a div. 3170// XHTML parsers do not magically insert elements in the 3171// same way that tag soup parsers do, so we cannot shorten 3172// this by omitting <tbody> or other required elements. 3173var wrapMap = { 3174 thead: ['table'], 3175 col: ['colgroup', 'table'], 3176 tr: ['tbody', 'table'], 3177 td: ['tr', 'tbody', 'table'] 3178}; 3179 3180wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 3181wrapMap.th = wrapMap.td; 3182 3183// Support: IE <10 only 3184// IE 9 requires an option wrapper & it needs to have the whole table structure 3185// set up in advance; assigning `"<td></td>"` to `tr.innerHTML` doesn't work, etc. 3186var wrapMapIE9 = { 3187 option: [1, '<select multiple="multiple">', '</select>'], 3188 _default: [0, '', ''] 3189}; 3190 3191for (var key in wrapMap) { 3192 var wrapMapValueClosing = wrapMap[key]; 3193 var wrapMapValue = wrapMapValueClosing.slice().reverse(); 3194 wrapMapIE9[key] = [wrapMapValue.length, '<' + wrapMapValue.join('><') + '>', '</' + wrapMapValueClosing.join('></') + '>']; 3195} 3196 3197wrapMapIE9.optgroup = wrapMapIE9.option; 3198 3199function jqLiteIsTextNode(html) { 3200 return !HTML_REGEXP.test(html); 3201} 3202 3203function jqLiteAcceptsData(node) { 3204 // The window object can accept data but has no nodeType 3205 // Otherwise we are only interested in elements (1) and documents (9) 3206 var nodeType = node.nodeType; 3207 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 3208} 3209 3210function jqLiteHasData(node) { 3211 for (var key in jqCache[node.ng339]) { 3212 return true; 3213 } 3214 return false; 3215} 3216 3217function jqLiteBuildFragment(html, context) { 3218 var tmp, tag, wrap, finalHtml, 3219 fragment = context.createDocumentFragment(), 3220 nodes = [], i; 3221 3222 if (jqLiteIsTextNode(html)) { 3223 // Convert non-html into a text node 3224 nodes.push(context.createTextNode(html)); 3225 } else { 3226 // Convert html into DOM nodes 3227 tmp = fragment.appendChild(context.createElement('div')); 3228 tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase(); 3229 finalHtml = JQLite.legacyXHTMLReplacement ? 3230 html.replace(XHTML_TAG_REGEXP, '<$1></$2>') : 3231 html; 3232 3233 if (msie < 10) { 3234 wrap = wrapMapIE9[tag] || wrapMapIE9._default; 3235 tmp.innerHTML = wrap[1] + finalHtml + wrap[2]; 3236 3237 // Descend through wrappers to the right content 3238 i = wrap[0]; 3239 while (i--) { 3240 tmp = tmp.firstChild; 3241 } 3242 } else { 3243 wrap = wrapMap[tag] || []; 3244 3245 // Create wrappers & descend into them 3246 i = wrap.length; 3247 while (--i > -1) { 3248 tmp.appendChild(window.document.createElement(wrap[i])); 3249 tmp = tmp.firstChild; 3250 } 3251 3252 tmp.innerHTML = finalHtml; 3253 } 3254 3255 nodes = concat(nodes, tmp.childNodes); 3256 3257 tmp = fragment.firstChild; 3258 tmp.textContent = ''; 3259 } 3260 3261 // Remove wrapper from fragment 3262 fragment.textContent = ''; 3263 fragment.innerHTML = ''; // Clear inner HTML 3264 forEach(nodes, function(node) { 3265 fragment.appendChild(node); 3266 }); 3267 3268 return fragment; 3269} 3270 3271function jqLiteParseHTML(html, context) { 3272 context = context || window.document; 3273 var parsed; 3274 3275 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 3276 return [context.createElement(parsed[1])]; 3277 } 3278 3279 if ((parsed = jqLiteBuildFragment(html, context))) { 3280 return parsed.childNodes; 3281 } 3282 3283 return []; 3284} 3285 3286function jqLiteWrapNode(node, wrapper) { 3287 var parent = node.parentNode; 3288 3289 if (parent) { 3290 parent.replaceChild(wrapper, node); 3291 } 3292 3293 wrapper.appendChild(node); 3294} 3295 3296 3297// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 3298var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) { 3299 // eslint-disable-next-line no-bitwise 3300 return !!(this.compareDocumentPosition(arg) & 16); 3301}; 3302 3303///////////////////////////////////////////// 3304function JQLite(element) { 3305 if (element instanceof JQLite) { 3306 return element; 3307 } 3308 3309 var argIsString; 3310 3311 if (isString(element)) { 3312 element = trim(element); 3313 argIsString = true; 3314 } 3315 if (!(this instanceof JQLite)) { 3316 if (argIsString && element.charAt(0) !== '<') { 3317 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 3318 } 3319 return new JQLite(element); 3320 } 3321 3322 if (argIsString) { 3323 jqLiteAddNodes(this, jqLiteParseHTML(element)); 3324 } else if (isFunction(element)) { 3325 jqLiteReady(element); 3326 } else { 3327 jqLiteAddNodes(this, element); 3328 } 3329} 3330 3331function jqLiteClone(element) { 3332 return element.cloneNode(true); 3333} 3334 3335function jqLiteDealoc(element, onlyDescendants) { 3336 if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]); 3337 3338 if (element.querySelectorAll) { 3339 jqLite.cleanData(element.querySelectorAll('*')); 3340 } 3341} 3342 3343function isEmptyObject(obj) { 3344 var name; 3345 3346 for (name in obj) { 3347 return false; 3348 } 3349 return true; 3350} 3351 3352function removeIfEmptyData(element) { 3353 var expandoId = element.ng339; 3354 var expandoStore = expandoId && jqCache[expandoId]; 3355 3356 var events = expandoStore && expandoStore.events; 3357 var data = expandoStore && expandoStore.data; 3358 3359 if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) { 3360 delete jqCache[expandoId]; 3361 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 3362 } 3363} 3364 3365function jqLiteOff(element, type, fn, unsupported) { 3366 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 3367 3368 var expandoStore = jqLiteExpandoStore(element); 3369 var events = expandoStore && expandoStore.events; 3370 var handle = expandoStore && expandoStore.handle; 3371 3372 if (!handle) return; //no listeners registered 3373 3374 if (!type) { 3375 for (type in events) { 3376 if (type !== '$destroy') { 3377 element.removeEventListener(type, handle); 3378 } 3379 delete events[type]; 3380 } 3381 } else { 3382 3383 var removeHandler = function(type) { 3384 var listenerFns = events[type]; 3385 if (isDefined(fn)) { 3386 arrayRemove(listenerFns || [], fn); 3387 } 3388 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 3389 element.removeEventListener(type, handle); 3390 delete events[type]; 3391 } 3392 }; 3393 3394 forEach(type.split(' '), function(type) { 3395 removeHandler(type); 3396 if (MOUSE_EVENT_MAP[type]) { 3397 removeHandler(MOUSE_EVENT_MAP[type]); 3398 } 3399 }); 3400 } 3401 3402 removeIfEmptyData(element); 3403} 3404 3405function jqLiteRemoveData(element, name) { 3406 var expandoId = element.ng339; 3407 var expandoStore = expandoId && jqCache[expandoId]; 3408 3409 if (expandoStore) { 3410 if (name) { 3411 delete expandoStore.data[name]; 3412 } else { 3413 expandoStore.data = {}; 3414 } 3415 3416 removeIfEmptyData(element); 3417 } 3418} 3419 3420 3421function jqLiteExpandoStore(element, createIfNecessary) { 3422 var expandoId = element.ng339, 3423 expandoStore = expandoId && jqCache[expandoId]; 3424 3425 if (createIfNecessary && !expandoStore) { 3426 element.ng339 = expandoId = jqNextId(); 3427 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 3428 } 3429 3430 return expandoStore; 3431} 3432 3433 3434function jqLiteData(element, key, value) { 3435 if (jqLiteAcceptsData(element)) { 3436 var prop; 3437 3438 var isSimpleSetter = isDefined(value); 3439 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 3440 var massGetter = !key; 3441 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 3442 var data = expandoStore && expandoStore.data; 3443 3444 if (isSimpleSetter) { // data('key', value) 3445 data[kebabToCamel(key)] = value; 3446 } else { 3447 if (massGetter) { // data() 3448 return data; 3449 } else { 3450 if (isSimpleGetter) { // data('key') 3451 // don't force creation of expandoStore if it doesn't exist yet 3452 return data && data[kebabToCamel(key)]; 3453 } else { // mass-setter: data({key1: val1, key2: val2}) 3454 for (prop in key) { 3455 data[kebabToCamel(prop)] = key[prop]; 3456 } 3457 } 3458 } 3459 } 3460 } 3461} 3462 3463function jqLiteHasClass(element, selector) { 3464 if (!element.getAttribute) return false; 3465 return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' '). 3466 indexOf(' ' + selector + ' ') > -1); 3467} 3468 3469function jqLiteRemoveClass(element, cssClasses) { 3470 if (cssClasses && element.setAttribute) { 3471 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3472 .replace(/[\n\t]/g, ' '); 3473 var newClasses = existingClasses; 3474 3475 forEach(cssClasses.split(' '), function(cssClass) { 3476 cssClass = trim(cssClass); 3477 newClasses = newClasses.replace(' ' + cssClass + ' ', ' '); 3478 }); 3479 3480 if (newClasses !== existingClasses) { 3481 element.setAttribute('class', trim(newClasses)); 3482 } 3483 } 3484} 3485 3486function jqLiteAddClass(element, cssClasses) { 3487 if (cssClasses && element.setAttribute) { 3488 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3489 .replace(/[\n\t]/g, ' '); 3490 var newClasses = existingClasses; 3491 3492 forEach(cssClasses.split(' '), function(cssClass) { 3493 cssClass = trim(cssClass); 3494 if (newClasses.indexOf(' ' + cssClass + ' ') === -1) { 3495 newClasses += cssClass + ' '; 3496 } 3497 }); 3498 3499 if (newClasses !== existingClasses) { 3500 element.setAttribute('class', trim(newClasses)); 3501 } 3502 } 3503} 3504 3505 3506function jqLiteAddNodes(root, elements) { 3507 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 3508 3509 if (elements) { 3510 3511 // if a Node (the most common case) 3512 if (elements.nodeType) { 3513 root[root.length++] = elements; 3514 } else { 3515 var length = elements.length; 3516 3517 // if an Array or NodeList and not a Window 3518 if (typeof length === 'number' && elements.window !== elements) { 3519 if (length) { 3520 for (var i = 0; i < length; i++) { 3521 root[root.length++] = elements[i]; 3522 } 3523 } 3524 } else { 3525 root[root.length++] = elements; 3526 } 3527 } 3528 } 3529} 3530 3531 3532function jqLiteController(element, name) { 3533 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 3534} 3535 3536function jqLiteInheritedData(element, name, value) { 3537 // if element is the document object work with the html element instead 3538 // this makes $(document).scope() possible 3539 if (element.nodeType === NODE_TYPE_DOCUMENT) { 3540 element = element.documentElement; 3541 } 3542 var names = isArray(name) ? name : [name]; 3543 3544 while (element) { 3545 for (var i = 0, ii = names.length; i < ii; i++) { 3546 if (isDefined(value = jqLite.data(element, names[i]))) return value; 3547 } 3548 3549 // If dealing with a document fragment node with a host element, and no parent, use the host 3550 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 3551 // to lookup parent controllers. 3552 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 3553 } 3554} 3555 3556function jqLiteEmpty(element) { 3557 jqLiteDealoc(element, true); 3558 while (element.firstChild) { 3559 element.removeChild(element.firstChild); 3560 } 3561} 3562 3563function jqLiteRemove(element, keepData) { 3564 if (!keepData) jqLiteDealoc(element); 3565 var parent = element.parentNode; 3566 if (parent) parent.removeChild(element); 3567} 3568 3569 3570function jqLiteDocumentLoaded(action, win) { 3571 win = win || window; 3572 if (win.document.readyState === 'complete') { 3573 // Force the action to be run async for consistent behavior 3574 // from the action's point of view 3575 // i.e. it will definitely not be in a $apply 3576 win.setTimeout(action); 3577 } else { 3578 // No need to unbind this handler as load is only ever called once 3579 jqLite(win).on('load', action); 3580 } 3581} 3582 3583function jqLiteReady(fn) { 3584 function trigger() { 3585 window.document.removeEventListener('DOMContentLoaded', trigger); 3586 window.removeEventListener('load', trigger); 3587 fn(); 3588 } 3589 3590 // check if document is already loaded 3591 if (window.document.readyState === 'complete') { 3592 window.setTimeout(fn); 3593 } else { 3594 // We can not use jqLite since we are not done loading and jQuery could be loaded later. 3595 3596 // Works for modern browsers and IE9 3597 window.document.addEventListener('DOMContentLoaded', trigger); 3598 3599 // Fallback to window.onload for others 3600 window.addEventListener('load', trigger); 3601 } 3602} 3603 3604////////////////////////////////////////// 3605// Functions which are declared directly. 3606////////////////////////////////////////// 3607var JQLitePrototype = JQLite.prototype = { 3608 ready: jqLiteReady, 3609 toString: function() { 3610 var value = []; 3611 forEach(this, function(e) { value.push('' + e);}); 3612 return '[' + value.join(', ') + ']'; 3613 }, 3614 3615 eq: function(index) { 3616 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3617 }, 3618 3619 length: 0, 3620 push: push, 3621 sort: [].sort, 3622 splice: [].splice 3623}; 3624 3625////////////////////////////////////////// 3626// Functions iterating getter/setters. 3627// these functions return self on setter and 3628// value on get. 3629////////////////////////////////////////// 3630var BOOLEAN_ATTR = {}; 3631forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3632 BOOLEAN_ATTR[lowercase(value)] = value; 3633}); 3634var BOOLEAN_ELEMENTS = {}; 3635forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3636 BOOLEAN_ELEMENTS[value] = true; 3637}); 3638var ALIASED_ATTR = { 3639 'ngMinlength': 'minlength', 3640 'ngMaxlength': 'maxlength', 3641 'ngMin': 'min', 3642 'ngMax': 'max', 3643 'ngPattern': 'pattern', 3644 'ngStep': 'step' 3645}; 3646 3647function getBooleanAttrName(element, name) { 3648 // check dom last since we will most likely fail on name 3649 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3650 3651 // booleanAttr is here twice to minimize DOM access 3652 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3653} 3654 3655function getAliasedAttrName(name) { 3656 return ALIASED_ATTR[name]; 3657} 3658 3659forEach({ 3660 data: jqLiteData, 3661 removeData: jqLiteRemoveData, 3662 hasData: jqLiteHasData, 3663 cleanData: function jqLiteCleanData(nodes) { 3664 for (var i = 0, ii = nodes.length; i < ii; i++) { 3665 jqLiteRemoveData(nodes[i]); 3666 jqLiteOff(nodes[i]); 3667 } 3668 } 3669}, function(fn, name) { 3670 JQLite[name] = fn; 3671}); 3672 3673forEach({ 3674 data: jqLiteData, 3675 inheritedData: jqLiteInheritedData, 3676 3677 scope: function(element) { 3678 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3679 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3680 }, 3681 3682 isolateScope: function(element) { 3683 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3684 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3685 }, 3686 3687 controller: jqLiteController, 3688 3689 injector: function(element) { 3690 return jqLiteInheritedData(element, '$injector'); 3691 }, 3692 3693 removeAttr: function(element, name) { 3694 element.removeAttribute(name); 3695 }, 3696 3697 hasClass: jqLiteHasClass, 3698 3699 css: function(element, name, value) { 3700 name = cssKebabToCamel(name); 3701 3702 if (isDefined(value)) { 3703 element.style[name] = value; 3704 } else { 3705 return element.style[name]; 3706 } 3707 }, 3708 3709 attr: function(element, name, value) { 3710 var ret; 3711 var nodeType = element.nodeType; 3712 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT || 3713 !element.getAttribute) { 3714 return; 3715 } 3716 3717 var lowercasedName = lowercase(name); 3718 var isBooleanAttr = BOOLEAN_ATTR[lowercasedName]; 3719 3720 if (isDefined(value)) { 3721 // setter 3722 3723 if (value === null || (value === false && isBooleanAttr)) { 3724 element.removeAttribute(name); 3725 } else { 3726 element.setAttribute(name, isBooleanAttr ? lowercasedName : value); 3727 } 3728 } else { 3729 // getter 3730 3731 ret = element.getAttribute(name); 3732 3733 if (isBooleanAttr && ret !== null) { 3734 ret = lowercasedName; 3735 } 3736 // Normalize non-existing attributes to undefined (as jQuery). 3737 return ret === null ? undefined : ret; 3738 } 3739 }, 3740 3741 prop: function(element, name, value) { 3742 if (isDefined(value)) { 3743 element[name] = value; 3744 } else { 3745 return element[name]; 3746 } 3747 }, 3748 3749 text: (function() { 3750 getText.$dv = ''; 3751 return getText; 3752 3753 function getText(element, value) { 3754 if (isUndefined(value)) { 3755 var nodeType = element.nodeType; 3756 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3757 } 3758 element.textContent = value; 3759 } 3760 })(), 3761 3762 val: function(element, value) { 3763 if (isUndefined(value)) { 3764 if (element.multiple && nodeName_(element) === 'select') { 3765 var result = []; 3766 forEach(element.options, function(option) { 3767 if (option.selected) { 3768 result.push(option.value || option.text); 3769 } 3770 }); 3771 return result; 3772 } 3773 return element.value; 3774 } 3775 element.value = value; 3776 }, 3777 3778 html: function(element, value) { 3779 if (isUndefined(value)) { 3780 return element.innerHTML; 3781 } 3782 jqLiteDealoc(element, true); 3783 element.innerHTML = value; 3784 }, 3785 3786 empty: jqLiteEmpty 3787}, function(fn, name) { 3788 /** 3789 * Properties: writes return selection, reads return first value 3790 */ 3791 JQLite.prototype[name] = function(arg1, arg2) { 3792 var i, key; 3793 var nodeCount = this.length; 3794 3795 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3796 // in a way that survives minification. 3797 // jqLiteEmpty takes no arguments but is a setter. 3798 if (fn !== jqLiteEmpty && 3799 (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3800 if (isObject(arg1)) { 3801 3802 // we are a write, but the object properties are the key/values 3803 for (i = 0; i < nodeCount; i++) { 3804 if (fn === jqLiteData) { 3805 // data() takes the whole object in jQuery 3806 fn(this[i], arg1); 3807 } else { 3808 for (key in arg1) { 3809 fn(this[i], key, arg1[key]); 3810 } 3811 } 3812 } 3813 // return self for chaining 3814 return this; 3815 } else { 3816 // we are a read, so read the first child. 3817 // TODO: do we still need this? 3818 var value = fn.$dv; 3819 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3820 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3821 for (var j = 0; j < jj; j++) { 3822 var nodeValue = fn(this[j], arg1, arg2); 3823 value = value ? value + nodeValue : nodeValue; 3824 } 3825 return value; 3826 } 3827 } else { 3828 // we are a write, so apply to all children 3829 for (i = 0; i < nodeCount; i++) { 3830 fn(this[i], arg1, arg2); 3831 } 3832 // return self for chaining 3833 return this; 3834 } 3835 }; 3836}); 3837 3838function createEventHandler(element, events) { 3839 var eventHandler = function(event, type) { 3840 // jQuery specific api 3841 event.isDefaultPrevented = function() { 3842 return event.defaultPrevented; 3843 }; 3844 3845 var eventFns = events[type || event.type]; 3846 var eventFnsLength = eventFns ? eventFns.length : 0; 3847 3848 if (!eventFnsLength) return; 3849 3850 if (isUndefined(event.immediatePropagationStopped)) { 3851 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3852 event.stopImmediatePropagation = function() { 3853 event.immediatePropagationStopped = true; 3854 3855 if (event.stopPropagation) { 3856 event.stopPropagation(); 3857 } 3858 3859 if (originalStopImmediatePropagation) { 3860 originalStopImmediatePropagation.call(event); 3861 } 3862 }; 3863 } 3864 3865 event.isImmediatePropagationStopped = function() { 3866 return event.immediatePropagationStopped === true; 3867 }; 3868 3869 // Some events have special handlers that wrap the real handler 3870 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 3871 3872 // Copy event handlers in case event handlers array is modified during execution. 3873 if ((eventFnsLength > 1)) { 3874 eventFns = shallowCopy(eventFns); 3875 } 3876 3877 for (var i = 0; i < eventFnsLength; i++) { 3878 if (!event.isImmediatePropagationStopped()) { 3879 handlerWrapper(element, event, eventFns[i]); 3880 } 3881 } 3882 }; 3883 3884 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3885 // events on `element` 3886 eventHandler.elem = element; 3887 return eventHandler; 3888} 3889 3890function defaultHandlerWrapper(element, event, handler) { 3891 handler.call(element, event); 3892} 3893 3894function specialMouseHandlerWrapper(target, event, handler) { 3895 // Refer to jQuery's implementation of mouseenter & mouseleave 3896 // Read about mouseenter and mouseleave: 3897 // http://www.quirksmode.org/js/events_mouse.html#link8 3898 var related = event.relatedTarget; 3899 // For mousenter/leave call the handler if related is outside the target. 3900 // NB: No relatedTarget if the mouse left/entered the browser window 3901 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 3902 handler.call(target, event); 3903 } 3904} 3905 3906////////////////////////////////////////// 3907// Functions iterating traversal. 3908// These functions chain results into a single 3909// selector. 3910////////////////////////////////////////// 3911forEach({ 3912 removeData: jqLiteRemoveData, 3913 3914 on: function jqLiteOn(element, type, fn, unsupported) { 3915 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3916 3917 // Do not add event handlers to non-elements because they will not be cleaned up. 3918 if (!jqLiteAcceptsData(element)) { 3919 return; 3920 } 3921 3922 var expandoStore = jqLiteExpandoStore(element, true); 3923 var events = expandoStore.events; 3924 var handle = expandoStore.handle; 3925 3926 if (!handle) { 3927 handle = expandoStore.handle = createEventHandler(element, events); 3928 } 3929 3930 // http://jsperf.com/string-indexof-vs-split 3931 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3932 var i = types.length; 3933 3934 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 3935 var eventFns = events[type]; 3936 3937 if (!eventFns) { 3938 eventFns = events[type] = []; 3939 eventFns.specialHandlerWrapper = specialHandlerWrapper; 3940 if (type !== '$destroy' && !noEventListener) { 3941 element.addEventListener(type, handle); 3942 } 3943 } 3944 3945 eventFns.push(fn); 3946 }; 3947 3948 while (i--) { 3949 type = types[i]; 3950 if (MOUSE_EVENT_MAP[type]) { 3951 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 3952 addHandler(type, undefined, true); 3953 } else { 3954 addHandler(type); 3955 } 3956 } 3957 }, 3958 3959 off: jqLiteOff, 3960 3961 one: function(element, type, fn) { 3962 element = jqLite(element); 3963 3964 //add the listener twice so that when it is called 3965 //you can remove the original function and still be 3966 //able to call element.off(ev, fn) normally 3967 element.on(type, function onFn() { 3968 element.off(type, fn); 3969 element.off(type, onFn); 3970 }); 3971 element.on(type, fn); 3972 }, 3973 3974 replaceWith: function(element, replaceNode) { 3975 var index, parent = element.parentNode; 3976 jqLiteDealoc(element); 3977 forEach(new JQLite(replaceNode), function(node) { 3978 if (index) { 3979 parent.insertBefore(node, index.nextSibling); 3980 } else { 3981 parent.replaceChild(node, element); 3982 } 3983 index = node; 3984 }); 3985 }, 3986 3987 children: function(element) { 3988 var children = []; 3989 forEach(element.childNodes, function(element) { 3990 if (element.nodeType === NODE_TYPE_ELEMENT) { 3991 children.push(element); 3992 } 3993 }); 3994 return children; 3995 }, 3996 3997 contents: function(element) { 3998 return element.contentDocument || element.childNodes || []; 3999 }, 4000 4001 append: function(element, node) { 4002 var nodeType = element.nodeType; 4003 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 4004 4005 node = new JQLite(node); 4006 4007 for (var i = 0, ii = node.length; i < ii; i++) { 4008 var child = node[i]; 4009 element.appendChild(child); 4010 } 4011 }, 4012 4013 prepend: function(element, node) { 4014 if (element.nodeType === NODE_TYPE_ELEMENT) { 4015 var index = element.firstChild; 4016 forEach(new JQLite(node), function(child) { 4017 element.insertBefore(child, index); 4018 }); 4019 } 4020 }, 4021 4022 wrap: function(element, wrapNode) { 4023 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); 4024 }, 4025 4026 remove: jqLiteRemove, 4027 4028 detach: function(element) { 4029 jqLiteRemove(element, true); 4030 }, 4031 4032 after: function(element, newElement) { 4033 var index = element, parent = element.parentNode; 4034 4035 if (parent) { 4036 newElement = new JQLite(newElement); 4037 4038 for (var i = 0, ii = newElement.length; i < ii; i++) { 4039 var node = newElement[i]; 4040 parent.insertBefore(node, index.nextSibling); 4041 index = node; 4042 } 4043 } 4044 }, 4045 4046 addClass: jqLiteAddClass, 4047 removeClass: jqLiteRemoveClass, 4048 4049 toggleClass: function(element, selector, condition) { 4050 if (selector) { 4051 forEach(selector.split(' '), function(className) { 4052 var classCondition = condition; 4053 if (isUndefined(classCondition)) { 4054 classCondition = !jqLiteHasClass(element, className); 4055 } 4056 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 4057 }); 4058 } 4059 }, 4060 4061 parent: function(element) { 4062 var parent = element.parentNode; 4063 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 4064 }, 4065 4066 next: function(element) { 4067 return element.nextElementSibling; 4068 }, 4069 4070 find: function(element, selector) { 4071 if (element.getElementsByTagName) { 4072 return element.getElementsByTagName(selector); 4073 } else { 4074 return []; 4075 } 4076 }, 4077 4078 clone: jqLiteClone, 4079 4080 triggerHandler: function(element, event, extraParameters) { 4081 4082 var dummyEvent, eventFnsCopy, handlerArgs; 4083 var eventName = event.type || event; 4084 var expandoStore = jqLiteExpandoStore(element); 4085 var events = expandoStore && expandoStore.events; 4086 var eventFns = events && events[eventName]; 4087 4088 if (eventFns) { 4089 // Create a dummy event to pass to the handlers 4090 dummyEvent = { 4091 preventDefault: function() { this.defaultPrevented = true; }, 4092 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 4093 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 4094 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 4095 stopPropagation: noop, 4096 type: eventName, 4097 target: element 4098 }; 4099 4100 // If a custom event was provided then extend our dummy event with it 4101 if (event.type) { 4102 dummyEvent = extend(dummyEvent, event); 4103 } 4104 4105 // Copy event handlers in case event handlers array is modified during execution. 4106 eventFnsCopy = shallowCopy(eventFns); 4107 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 4108 4109 forEach(eventFnsCopy, function(fn) { 4110 if (!dummyEvent.isImmediatePropagationStopped()) { 4111 fn.apply(element, handlerArgs); 4112 } 4113 }); 4114 } 4115 } 4116}, function(fn, name) { 4117 /** 4118 * chaining functions 4119 */ 4120 JQLite.prototype[name] = function(arg1, arg2, arg3) { 4121 var value; 4122 4123 for (var i = 0, ii = this.length; i < ii; i++) { 4124 if (isUndefined(value)) { 4125 value = fn(this[i], arg1, arg2, arg3); 4126 if (isDefined(value)) { 4127 // any function which returns a value needs to be wrapped 4128 value = jqLite(value); 4129 } 4130 } else { 4131 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 4132 } 4133 } 4134 return isDefined(value) ? value : this; 4135 }; 4136}); 4137 4138// bind legacy bind/unbind to on/off 4139JQLite.prototype.bind = JQLite.prototype.on; 4140JQLite.prototype.unbind = JQLite.prototype.off; 4141 4142 4143// Provider for private $$jqLite service 4144/** @this */ 4145function $$jqLiteProvider() { 4146 this.$get = function $$jqLite() { 4147 return extend(JQLite, { 4148 hasClass: function(node, classes) { 4149 if (node.attr) node = node[0]; 4150 return jqLiteHasClass(node, classes); 4151 }, 4152 addClass: function(node, classes) { 4153 if (node.attr) node = node[0]; 4154 return jqLiteAddClass(node, classes); 4155 }, 4156 removeClass: function(node, classes) { 4157 if (node.attr) node = node[0]; 4158 return jqLiteRemoveClass(node, classes); 4159 } 4160 }); 4161 }; 4162} 4163 4164/** 4165 * Computes a hash of an 'obj'. 4166 * Hash of a: 4167 * string is string 4168 * number is number as string 4169 * object is either result of calling $$hashKey function on the object or uniquely generated id, 4170 * that is also assigned to the $$hashKey property of the object. 4171 * 4172 * @param obj 4173 * @returns {string} hash string such that the same input will have the same hash string. 4174 * The resulting string key is in 'type:hashKey' format. 4175 */ 4176function hashKey(obj, nextUidFn) { 4177 var key = obj && obj.$$hashKey; 4178 4179 if (key) { 4180 if (typeof key === 'function') { 4181 key = obj.$$hashKey(); 4182 } 4183 return key; 4184 } 4185 4186 var objType = typeof obj; 4187 if (objType === 'function' || (objType === 'object' && obj !== null)) { 4188 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 4189 } else { 4190 key = objType + ':' + obj; 4191 } 4192 4193 return key; 4194} 4195 4196// A minimal ES2015 Map implementation. 4197// Should be bug/feature equivalent to the native implementations of supported browsers 4198// (for the features required in Angular). 4199// See https://kangax.github.io/compat-table/es6/#test-Map 4200var nanKey = Object.create(null); 4201function NgMapShim() { 4202 this._keys = []; 4203 this._values = []; 4204 this._lastKey = NaN; 4205 this._lastIndex = -1; 4206} 4207NgMapShim.prototype = { 4208 _idx: function(key) { 4209 if (key !== this._lastKey) { 4210 this._lastKey = key; 4211 this._lastIndex = this._keys.indexOf(key); 4212 } 4213 return this._lastIndex; 4214 }, 4215 _transformKey: function(key) { 4216 return isNumberNaN(key) ? nanKey : key; 4217 }, 4218 get: function(key) { 4219 key = this._transformKey(key); 4220 var idx = this._idx(key); 4221 if (idx !== -1) { 4222 return this._values[idx]; 4223 } 4224 }, 4225 has: function(key) { 4226 key = this._transformKey(key); 4227 var idx = this._idx(key); 4228 return idx !== -1; 4229 }, 4230 set: function(key, value) { 4231 key = this._transformKey(key); 4232 var idx = this._idx(key); 4233 if (idx === -1) { 4234 idx = this._lastIndex = this._keys.length; 4235 } 4236 this._keys[idx] = key; 4237 this._values[idx] = value; 4238 4239 // Support: IE11 4240 // Do not `return this` to simulate the partial IE11 implementation 4241 }, 4242 delete: function(key) { 4243 key = this._transformKey(key); 4244 var idx = this._idx(key); 4245 if (idx === -1) { 4246 return false; 4247 } 4248 this._keys.splice(idx, 1); 4249 this._values.splice(idx, 1); 4250 this._lastKey = NaN; 4251 this._lastIndex = -1; 4252 return true; 4253 } 4254}; 4255 4256// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations 4257// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map` 4258// implementations get more stable, we can reconsider switching to `window.Map` (when available). 4259var NgMap = NgMapShim; 4260 4261var $$MapProvider = [/** @this */function() { 4262 this.$get = [function() { 4263 return NgMap; 4264 }]; 4265}]; 4266 4267/** 4268 * @ngdoc function 4269 * @module ng 4270 * @name angular.injector 4271 * @kind function 4272 * 4273 * @description 4274 * Creates an injector object that can be used for retrieving services as well as for 4275 * dependency injection (see {@link guide/di dependency injection}). 4276 * 4277 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 4278 * {@link angular.module}. The `ng` module must be explicitly added. 4279 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 4280 * disallows argument name annotation inference. 4281 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 4282 * 4283 * @example 4284 * Typical usage 4285 * ```js 4286 * // create an injector 4287 * var $injector = angular.injector(['ng']); 4288 * 4289 * // use the injector to kick off your application 4290 * // use the type inference to auto inject arguments, or use implicit injection 4291 * $injector.invoke(function($rootScope, $compile, $document) { 4292 * $compile($document)($rootScope); 4293 * $rootScope.$digest(); 4294 * }); 4295 * ``` 4296 * 4297 * Sometimes you want to get access to the injector of a currently running AngularJS app 4298 * from outside AngularJS. Perhaps, you want to inject and compile some markup after the 4299 * application has been bootstrapped. You can do this using the extra `injector()` added 4300 * to JQuery/jqLite elements. See {@link angular.element}. 4301 * 4302 * *This is fairly rare but could be the case if a third party library is injecting the 4303 * markup.* 4304 * 4305 * In the following example a new block of HTML containing a `ng-controller` 4306 * directive is added to the end of the document body by JQuery. We then compile and link 4307 * it into the current AngularJS scope. 4308 * 4309 * ```js 4310 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 4311 * $(document.body).append($div); 4312 * 4313 * angular.element(document).injector().invoke(function($compile) { 4314 * var scope = angular.element($div).scope(); 4315 * $compile($div)(scope); 4316 * }); 4317 * ``` 4318 */ 4319 4320 4321/** 4322 * @ngdoc module 4323 * @name auto 4324 * @installation 4325 * @description 4326 * 4327 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 4328 */ 4329 4330var ARROW_ARG = /^([^(]+?)=>/; 4331var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m; 4332var FN_ARG_SPLIT = /,/; 4333var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 4334var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 4335var $injectorMinErr = minErr('$injector'); 4336 4337function stringifyFn(fn) { 4338 return Function.prototype.toString.call(fn); 4339} 4340 4341function extractArgs(fn) { 4342 var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''), 4343 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 4344 return args; 4345} 4346 4347function anonFn(fn) { 4348 // For anonymous functions, showing at the very least the function signature can help in 4349 // debugging. 4350 var args = extractArgs(fn); 4351 if (args) { 4352 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 4353 } 4354 return 'fn'; 4355} 4356 4357function annotate(fn, strictDi, name) { 4358 var $inject, 4359 argDecl, 4360 last; 4361 4362 if (typeof fn === 'function') { 4363 if (!($inject = fn.$inject)) { 4364 $inject = []; 4365 if (fn.length) { 4366 if (strictDi) { 4367 if (!isString(name) || !name) { 4368 name = fn.name || anonFn(fn); 4369 } 4370 throw $injectorMinErr('strictdi', 4371 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 4372 } 4373 argDecl = extractArgs(fn); 4374 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 4375 arg.replace(FN_ARG, function(all, underscore, name) { 4376 $inject.push(name); 4377 }); 4378 }); 4379 } 4380 fn.$inject = $inject; 4381 } 4382 } else if (isArray(fn)) { 4383 last = fn.length - 1; 4384 assertArgFn(fn[last], 'fn'); 4385 $inject = fn.slice(0, last); 4386 } else { 4387 assertArgFn(fn, 'fn', true); 4388 } 4389 return $inject; 4390} 4391 4392/////////////////////////////////////// 4393 4394/** 4395 * @ngdoc service 4396 * @name $injector 4397 * 4398 * @description 4399 * 4400 * `$injector` is used to retrieve object instances as defined by 4401 * {@link auto.$provide provider}, instantiate types, invoke methods, 4402 * and load modules. 4403 * 4404 * The following always holds true: 4405 * 4406 * ```js 4407 * var $injector = angular.injector(); 4408 * expect($injector.get('$injector')).toBe($injector); 4409 * expect($injector.invoke(function($injector) { 4410 * return $injector; 4411 * })).toBe($injector); 4412 * ``` 4413 * 4414 * ## Injection Function Annotation 4415 * 4416 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 4417 * following are all valid ways of annotating function with injection arguments and are equivalent. 4418 * 4419 * ```js 4420 * // inferred (only works if code not minified/obfuscated) 4421 * $injector.invoke(function(serviceA){}); 4422 * 4423 * // annotated 4424 * function explicit(serviceA) {}; 4425 * explicit.$inject = ['serviceA']; 4426 * $injector.invoke(explicit); 4427 * 4428 * // inline 4429 * $injector.invoke(['serviceA', function(serviceA){}]); 4430 * ``` 4431 * 4432 * ### Inference 4433 * 4434 * In JavaScript calling `toString()` on a function returns the function definition. The definition 4435 * can then be parsed and the function arguments can be extracted. This method of discovering 4436 * annotations is disallowed when the injector is in strict mode. 4437 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 4438 * argument names. 4439 * 4440 * ### `$inject` Annotation 4441 * By adding an `$inject` property onto a function the injection parameters can be specified. 4442 * 4443 * ### Inline 4444 * As an array of injection names, where the last item in the array is the function to call. 4445 */ 4446 4447/** 4448 * @ngdoc property 4449 * @name $injector#modules 4450 * @type {Object} 4451 * @description 4452 * A hash containing all the modules that have been loaded into the 4453 * $injector. 4454 * 4455 * You can use this property to find out information about a module via the 4456 * {@link angular.Module#info `myModule.info(...)`} method. 4457 * 4458 * For example: 4459 * 4460 * ``` 4461 * var info = $injector.modules['ngAnimate'].info(); 4462 * ``` 4463 * 4464 * **Do not use this property to attempt to modify the modules after the application 4465 * has been bootstrapped.** 4466 */ 4467 4468 4469/** 4470 * @ngdoc method 4471 * @name $injector#get 4472 * 4473 * @description 4474 * Return an instance of the service. 4475 * 4476 * @param {string} name The name of the instance to retrieve. 4477 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 4478 * @return {*} The instance. 4479 */ 4480 4481/** 4482 * @ngdoc method 4483 * @name $injector#invoke 4484 * 4485 * @description 4486 * Invoke the method and supply the method arguments from the `$injector`. 4487 * 4488 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 4489 * injected according to the {@link guide/di $inject Annotation} rules. 4490 * @param {Object=} self The `this` for the invoked method. 4491 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4492 * object first, before the `$injector` is consulted. 4493 * @returns {*} the value returned by the invoked `fn` function. 4494 */ 4495 4496/** 4497 * @ngdoc method 4498 * @name $injector#has 4499 * 4500 * @description 4501 * Allows the user to query if the particular service exists. 4502 * 4503 * @param {string} name Name of the service to query. 4504 * @returns {boolean} `true` if injector has given service. 4505 */ 4506 4507/** 4508 * @ngdoc method 4509 * @name $injector#instantiate 4510 * @description 4511 * Create a new instance of JS type. The method takes a constructor function, invokes the new 4512 * operator, and supplies all of the arguments to the constructor function as specified by the 4513 * constructor annotation. 4514 * 4515 * @param {Function} Type Annotated constructor function. 4516 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4517 * object first, before the `$injector` is consulted. 4518 * @returns {Object} new instance of `Type`. 4519 */ 4520 4521/** 4522 * @ngdoc method 4523 * @name $injector#annotate 4524 * 4525 * @description 4526 * Returns an array of service names which the function is requesting for injection. This API is 4527 * used by the injector to determine which services need to be injected into the function when the 4528 * function is invoked. There are three ways in which the function can be annotated with the needed 4529 * dependencies. 4530 * 4531 * #### Argument names 4532 * 4533 * The simplest form is to extract the dependencies from the arguments of the function. This is done 4534 * by converting the function into a string using `toString()` method and extracting the argument 4535 * names. 4536 * ```js 4537 * // Given 4538 * function MyController($scope, $route) { 4539 * // ... 4540 * } 4541 * 4542 * // Then 4543 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4544 * ``` 4545 * 4546 * You can disallow this method by using strict injection mode. 4547 * 4548 * This method does not work with code minification / obfuscation. For this reason the following 4549 * annotation strategies are supported. 4550 * 4551 * #### The `$inject` property 4552 * 4553 * If a function has an `$inject` property and its value is an array of strings, then the strings 4554 * represent names of services to be injected into the function. 4555 * ```js 4556 * // Given 4557 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 4558 * // ... 4559 * } 4560 * // Define function dependencies 4561 * MyController['$inject'] = ['$scope', '$route']; 4562 * 4563 * // Then 4564 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4565 * ``` 4566 * 4567 * #### The array notation 4568 * 4569 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 4570 * is very inconvenient. In these situations using the array notation to specify the dependencies in 4571 * a way that survives minification is a better choice: 4572 * 4573 * ```js 4574 * // We wish to write this (not minification / obfuscation safe) 4575 * injector.invoke(function($compile, $rootScope) { 4576 * // ... 4577 * }); 4578 * 4579 * // We are forced to write break inlining 4580 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 4581 * // ... 4582 * }; 4583 * tmpFn.$inject = ['$compile', '$rootScope']; 4584 * injector.invoke(tmpFn); 4585 * 4586 * // To better support inline function the inline annotation is supported 4587 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 4588 * // ... 4589 * }]); 4590 * 4591 * // Therefore 4592 * expect(injector.annotate( 4593 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 4594 * ).toEqual(['$compile', '$rootScope']); 4595 * ``` 4596 * 4597 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 4598 * be retrieved as described above. 4599 * 4600 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 4601 * 4602 * @returns {Array.<string>} The names of the services which the function requires. 4603 */ 4604/** 4605 * @ngdoc method 4606 * @name $injector#loadNewModules 4607 * 4608 * @description 4609 * 4610 * **This is a dangerous API, which you use at your own risk!** 4611 * 4612 * Add the specified modules to the current injector. 4613 * 4614 * This method will add each of the injectables to the injector and execute all of the config and run 4615 * blocks for each module passed to the method. 4616 * 4617 * If a module has already been loaded into the injector then it will not be loaded again. 4618 * 4619 * * The application developer is responsible for loading the code containing the modules; and for 4620 * ensuring that lazy scripts are not downloaded and executed more often that desired. 4621 * * Previously compiled HTML will not be affected by newly loaded directives, filters and components. 4622 * * Modules cannot be unloaded. 4623 * 4624 * You can use {@link $injector#modules `$injector.modules`} to check whether a module has been loaded 4625 * into the injector, which may indicate whether the script has been executed already. 4626 * 4627 * @example 4628 * Here is an example of loading a bundle of modules, with a utility method called `getScript`: 4629 * 4630 * ```javascript 4631 * app.factory('loadModule', function($injector) { 4632 * return function loadModule(moduleName, bundleUrl) { 4633 * return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); }); 4634 * }; 4635 * }) 4636 * ``` 4637 * 4638 * @param {Array<String|Function|Array>=} mods an array of modules to load into the application. 4639 * Each item in the array should be the name of a predefined module or a (DI annotated) 4640 * function that will be invoked by the injector as a `config` block. 4641 * See: {@link angular.module modules} 4642 */ 4643 4644 4645/** 4646 * @ngdoc service 4647 * @name $provide 4648 * 4649 * @description 4650 * 4651 * The {@link auto.$provide $provide} service has a number of methods for registering components 4652 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 4653 * {@link angular.Module}. 4654 * 4655 * An AngularJS **service** is a singleton object created by a **service factory**. These **service 4656 * factories** are functions which, in turn, are created by a **service provider**. 4657 * The **service providers** are constructor functions. When instantiated they must contain a 4658 * property called `$get`, which holds the **service factory** function. 4659 * 4660 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 4661 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 4662 * function to get the instance of the **service**. 4663 * 4664 * Often services have no configuration options and there is no need to add methods to the service 4665 * provider. The provider will be no more than a constructor function with a `$get` property. For 4666 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 4667 * services without specifying a provider. 4668 * 4669 * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the 4670 * {@link auto.$injector $injector} 4671 * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by 4672 * providers and services. 4673 * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by 4674 * services, not providers. 4675 * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function** 4676 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 4677 * given factory function. 4678 * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function** 4679 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 4680 * a new object using the given constructor function. 4681 * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that 4682 * will be able to modify or replace the implementation of another service. 4683 * 4684 * See the individual methods for more information and examples. 4685 */ 4686 4687/** 4688 * @ngdoc method 4689 * @name $provide#provider 4690 * @description 4691 * 4692 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4693 * are constructor functions, whose instances are responsible for "providing" a factory for a 4694 * service. 4695 * 4696 * Service provider names start with the name of the service they provide followed by `Provider`. 4697 * For example, the {@link ng.$log $log} service has a provider called 4698 * {@link ng.$logProvider $logProvider}. 4699 * 4700 * Service provider objects can have additional methods which allow configuration of the provider 4701 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4702 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4703 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4704 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4705 * console or not. 4706 * 4707 * It is possible to inject other providers into the provider function, 4708 * but the injected provider must have been defined before the one that requires it. 4709 * 4710 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4711 'Provider'` key. 4712 * @param {(Object|function())} provider If the provider is: 4713 * 4714 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4715 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4716 * - `Constructor`: a new instance of the provider will be created using 4717 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4718 * 4719 * @returns {Object} registered provider instance 4720 4721 * @example 4722 * 4723 * The following example shows how to create a simple event tracking service and register it using 4724 * {@link auto.$provide#provider $provide.provider()}. 4725 * 4726 * ```js 4727 * // Define the eventTracker provider 4728 * function EventTrackerProvider() { 4729 * var trackingUrl = '/track'; 4730 * 4731 * // A provider method for configuring where the tracked events should been saved 4732 * this.setTrackingUrl = function(url) { 4733 * trackingUrl = url; 4734 * }; 4735 * 4736 * // The service factory function 4737 * this.$get = ['$http', function($http) { 4738 * var trackedEvents = {}; 4739 * return { 4740 * // Call this to track an event 4741 * event: function(event) { 4742 * var count = trackedEvents[event] || 0; 4743 * count += 1; 4744 * trackedEvents[event] = count; 4745 * return count; 4746 * }, 4747 * // Call this to save the tracked events to the trackingUrl 4748 * save: function() { 4749 * $http.post(trackingUrl, trackedEvents); 4750 * } 4751 * }; 4752 * }]; 4753 * } 4754 * 4755 * describe('eventTracker', function() { 4756 * var postSpy; 4757 * 4758 * beforeEach(module(function($provide) { 4759 * // Register the eventTracker provider 4760 * $provide.provider('eventTracker', EventTrackerProvider); 4761 * })); 4762 * 4763 * beforeEach(module(function(eventTrackerProvider) { 4764 * // Configure eventTracker provider 4765 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4766 * })); 4767 * 4768 * it('tracks events', inject(function(eventTracker) { 4769 * expect(eventTracker.event('login')).toEqual(1); 4770 * expect(eventTracker.event('login')).toEqual(2); 4771 * })); 4772 * 4773 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4774 * postSpy = spyOn($http, 'post'); 4775 * eventTracker.event('login'); 4776 * eventTracker.save(); 4777 * expect(postSpy).toHaveBeenCalled(); 4778 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4779 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4780 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4781 * })); 4782 * }); 4783 * ``` 4784 */ 4785 4786/** 4787 * @ngdoc method 4788 * @name $provide#factory 4789 * @description 4790 * 4791 * Register a **service factory**, which will be called to return the service instance. 4792 * This is short for registering a service where its provider consists of only a `$get` property, 4793 * which is the given service factory function. 4794 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4795 * configure your service in a provider. 4796 * 4797 * @param {string} name The name of the instance. 4798 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4799 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4800 * @returns {Object} registered provider instance 4801 * 4802 * @example 4803 * Here is an example of registering a service 4804 * ```js 4805 * $provide.factory('ping', ['$http', function($http) { 4806 * return function ping() { 4807 * return $http.send('/ping'); 4808 * }; 4809 * }]); 4810 * ``` 4811 * You would then inject and use this service like this: 4812 * ```js 4813 * someModule.controller('Ctrl', ['ping', function(ping) { 4814 * ping(); 4815 * }]); 4816 * ``` 4817 */ 4818 4819 4820/** 4821 * @ngdoc method 4822 * @name $provide#service 4823 * @description 4824 * 4825 * Register a **service constructor**, which will be invoked with `new` to create the service 4826 * instance. 4827 * This is short for registering a service where its provider's `$get` property is a factory 4828 * function that returns an instance instantiated by the injector from the service constructor 4829 * function. 4830 * 4831 * Internally it looks a bit like this: 4832 * 4833 * ``` 4834 * { 4835 * $get: function() { 4836 * return $injector.instantiate(constructor); 4837 * } 4838 * } 4839 * ``` 4840 * 4841 * 4842 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4843 * as a type/class. 4844 * 4845 * @param {string} name The name of the instance. 4846 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4847 * that will be instantiated. 4848 * @returns {Object} registered provider instance 4849 * 4850 * @example 4851 * Here is an example of registering a service using 4852 * {@link auto.$provide#service $provide.service(class)}. 4853 * ```js 4854 * var Ping = function($http) { 4855 * this.$http = $http; 4856 * }; 4857 * 4858 * Ping.$inject = ['$http']; 4859 * 4860 * Ping.prototype.send = function() { 4861 * return this.$http.get('/ping'); 4862 * }; 4863 * $provide.service('ping', Ping); 4864 * ``` 4865 * You would then inject and use this service like this: 4866 * ```js 4867 * someModule.controller('Ctrl', ['ping', function(ping) { 4868 * ping.send(); 4869 * }]); 4870 * ``` 4871 */ 4872 4873 4874/** 4875 * @ngdoc method 4876 * @name $provide#value 4877 * @description 4878 * 4879 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4880 * number, an array, an object or a function. This is short for registering a service where its 4881 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4882 * service**. That also means it is not possible to inject other services into a value service. 4883 * 4884 * Value services are similar to constant services, except that they cannot be injected into a 4885 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4886 * an AngularJS {@link auto.$provide#decorator decorator}. 4887 * 4888 * @param {string} name The name of the instance. 4889 * @param {*} value The value. 4890 * @returns {Object} registered provider instance 4891 * 4892 * @example 4893 * Here are some examples of creating value services. 4894 * ```js 4895 * $provide.value('ADMIN_USER', 'admin'); 4896 * 4897 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4898 * 4899 * $provide.value('halfOf', function(value) { 4900 * return value / 2; 4901 * }); 4902 * ``` 4903 */ 4904 4905 4906/** 4907 * @ngdoc method 4908 * @name $provide#constant 4909 * @description 4910 * 4911 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, 4912 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not 4913 * possible to inject other services into a constant. 4914 * 4915 * But unlike {@link auto.$provide#value value}, a constant can be 4916 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4917 * be overridden by an AngularJS {@link auto.$provide#decorator decorator}. 4918 * 4919 * @param {string} name The name of the constant. 4920 * @param {*} value The constant value. 4921 * @returns {Object} registered instance 4922 * 4923 * @example 4924 * Here a some examples of creating constants: 4925 * ```js 4926 * $provide.constant('SHARD_HEIGHT', 306); 4927 * 4928 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4929 * 4930 * $provide.constant('double', function(value) { 4931 * return value * 2; 4932 * }); 4933 * ``` 4934 */ 4935 4936 4937/** 4938 * @ngdoc method 4939 * @name $provide#decorator 4940 * @description 4941 * 4942 * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function 4943 * intercepts the creation of a service, allowing it to override or modify the behavior of the 4944 * service. The return value of the decorator function may be the original service, or a new service 4945 * that replaces (or wraps and delegates to) the original service. 4946 * 4947 * You can find out more about using decorators in the {@link guide/decorators} guide. 4948 * 4949 * @param {string} name The name of the service to decorate. 4950 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4951 * provided and should return the decorated service instance. The function is called using 4952 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4953 * Local injection arguments: 4954 * 4955 * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured, 4956 * decorated or delegated to. 4957 * 4958 * @example 4959 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4960 * calls to {@link ng.$log#error $log.warn()}. 4961 * ```js 4962 * $provide.decorator('$log', ['$delegate', function($delegate) { 4963 * $delegate.warn = $delegate.error; 4964 * return $delegate; 4965 * }]); 4966 * ``` 4967 */ 4968 4969 4970function createInjector(modulesToLoad, strictDi) { 4971 strictDi = (strictDi === true); 4972 var INSTANTIATING = {}, 4973 providerSuffix = 'Provider', 4974 path = [], 4975 loadedModules = new NgMap(), 4976 providerCache = { 4977 $provide: { 4978 provider: supportObject(provider), 4979 factory: supportObject(factory), 4980 service: supportObject(service), 4981 value: supportObject(value), 4982 constant: supportObject(constant), 4983 decorator: decorator 4984 } 4985 }, 4986 providerInjector = (providerCache.$injector = 4987 createInternalInjector(providerCache, function(serviceName, caller) { 4988 if (angular.isString(caller)) { 4989 path.push(caller); 4990 } 4991 throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- ')); 4992 })), 4993 instanceCache = {}, 4994 protoInstanceInjector = 4995 createInternalInjector(instanceCache, function(serviceName, caller) { 4996 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4997 return instanceInjector.invoke( 4998 provider.$get, provider, undefined, serviceName); 4999 }), 5000 instanceInjector = protoInstanceInjector; 5001 5002 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 5003 instanceInjector.modules = providerInjector.modules = createMap(); 5004 var runBlocks = loadModules(modulesToLoad); 5005 instanceInjector = protoInstanceInjector.get('$injector'); 5006 instanceInjector.strictDi = strictDi; 5007 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 5008 5009 instanceInjector.loadNewModules = function(mods) { 5010 forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); }); 5011 }; 5012 5013 5014 return instanceInjector; 5015 5016 //////////////////////////////////// 5017 // $provider 5018 //////////////////////////////////// 5019 5020 function supportObject(delegate) { 5021 return function(key, value) { 5022 if (isObject(key)) { 5023 forEach(key, reverseParams(delegate)); 5024 } else { 5025 return delegate(key, value); 5026 } 5027 }; 5028 } 5029 5030 function provider(name, provider_) { 5031 assertNotHasOwnProperty(name, 'service'); 5032 if (isFunction(provider_) || isArray(provider_)) { 5033 provider_ = providerInjector.instantiate(provider_); 5034 } 5035 if (!provider_.$get) { 5036 throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name); 5037 } 5038 return (providerCache[name + providerSuffix] = provider_); 5039 } 5040 5041 function enforceReturnValue(name, factory) { 5042 return /** @this */ function enforcedReturnValue() { 5043 var result = instanceInjector.invoke(factory, this); 5044 if (isUndefined(result)) { 5045 throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name); 5046 } 5047 return result; 5048 }; 5049 } 5050 5051 function factory(name, factoryFn, enforce) { 5052 return provider(name, { 5053 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 5054 }); 5055 } 5056 5057 function service(name, constructor) { 5058 return factory(name, ['$injector', function($injector) { 5059 return $injector.instantiate(constructor); 5060 }]); 5061 } 5062 5063 function value(name, val) { return factory(name, valueFn(val), false); } 5064 5065 function constant(name, value) { 5066 assertNotHasOwnProperty(name, 'constant'); 5067 providerCache[name] = value; 5068 instanceCache[name] = value; 5069 } 5070 5071 function decorator(serviceName, decorFn) { 5072 var origProvider = providerInjector.get(serviceName + providerSuffix), 5073 orig$get = origProvider.$get; 5074 5075 origProvider.$get = function() { 5076 var origInstance = instanceInjector.invoke(orig$get, origProvider); 5077 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 5078 }; 5079 } 5080 5081 //////////////////////////////////// 5082 // Module Loading 5083 //////////////////////////////////// 5084 function loadModules(modulesToLoad) { 5085 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 5086 var runBlocks = [], moduleFn; 5087 forEach(modulesToLoad, function(module) { 5088 if (loadedModules.get(module)) return; 5089 loadedModules.set(module, true); 5090 5091 function runInvokeQueue(queue) { 5092 var i, ii; 5093 for (i = 0, ii = queue.length; i < ii; i++) { 5094 var invokeArgs = queue[i], 5095 provider = providerInjector.get(invokeArgs[0]); 5096 5097 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 5098 } 5099 } 5100 5101 try { 5102 if (isString(module)) { 5103 moduleFn = angularModule(module); 5104 instanceInjector.modules[module] = moduleFn; 5105 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 5106 runInvokeQueue(moduleFn._invokeQueue); 5107 runInvokeQueue(moduleFn._configBlocks); 5108 } else if (isFunction(module)) { 5109 runBlocks.push(providerInjector.invoke(module)); 5110 } else if (isArray(module)) { 5111 runBlocks.push(providerInjector.invoke(module)); 5112 } else { 5113 assertArgFn(module, 'module'); 5114 } 5115 } catch (e) { 5116 if (isArray(module)) { 5117 module = module[module.length - 1]; 5118 } 5119 if (e.message && e.stack && e.stack.indexOf(e.message) === -1) { 5120 // Safari & FF's stack traces don't contain error.message content 5121 // unlike those of Chrome and IE 5122 // So if stack doesn't contain message, we create a new string that contains both. 5123 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 5124 // eslint-disable-next-line no-ex-assign 5125 e = e.message + '\n' + e.stack; 5126 } 5127 throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', 5128 module, e.stack || e.message || e); 5129 } 5130 }); 5131 return runBlocks; 5132 } 5133 5134 //////////////////////////////////// 5135 // internal Injector 5136 //////////////////////////////////// 5137 5138 function createInternalInjector(cache, factory) { 5139 5140 function getService(serviceName, caller) { 5141 if (cache.hasOwnProperty(serviceName)) { 5142 if (cache[serviceName] === INSTANTIATING) { 5143 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 5144 serviceName + ' <- ' + path.join(' <- ')); 5145 } 5146 return cache[serviceName]; 5147 } else { 5148 try { 5149 path.unshift(serviceName); 5150 cache[serviceName] = INSTANTIATING; 5151 cache[serviceName] = factory(serviceName, caller); 5152 return cache[serviceName]; 5153 } catch (err) { 5154 if (cache[serviceName] === INSTANTIATING) { 5155 delete cache[serviceName]; 5156 } 5157 throw err; 5158 } finally { 5159 path.shift(); 5160 } 5161 } 5162 } 5163 5164 5165 function injectionArgs(fn, locals, serviceName) { 5166 var args = [], 5167 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 5168 5169 for (var i = 0, length = $inject.length; i < length; i++) { 5170 var key = $inject[i]; 5171 if (typeof key !== 'string') { 5172 throw $injectorMinErr('itkn', 5173 'Incorrect injection token! Expected service name as string, got {0}', key); 5174 } 5175 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 5176 getService(key, serviceName)); 5177 } 5178 return args; 5179 } 5180 5181 function isClass(func) { 5182 // Support: IE 9-11 only 5183 // IE 9-11 do not support classes and IE9 leaks with the code below. 5184 if (msie || typeof func !== 'function') { 5185 return false; 5186 } 5187 var result = func.$$ngIsClass; 5188 if (!isBoolean(result)) { 5189 result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func)); 5190 } 5191 return result; 5192 } 5193 5194 function invoke(fn, self, locals, serviceName) { 5195 if (typeof locals === 'string') { 5196 serviceName = locals; 5197 locals = null; 5198 } 5199 5200 var args = injectionArgs(fn, locals, serviceName); 5201 if (isArray(fn)) { 5202 fn = fn[fn.length - 1]; 5203 } 5204 5205 if (!isClass(fn)) { 5206 // http://jsperf.com/angularjs-invoke-apply-vs-switch 5207 // #5388 5208 return fn.apply(self, args); 5209 } else { 5210 args.unshift(null); 5211 return new (Function.prototype.bind.apply(fn, args))(); 5212 } 5213 } 5214 5215 5216 function instantiate(Type, locals, serviceName) { 5217 // Check if Type is annotated and use just the given function at n-1 as parameter 5218 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 5219 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 5220 var args = injectionArgs(Type, locals, serviceName); 5221 // Empty object at position 0 is ignored for invocation with `new`, but required. 5222 args.unshift(null); 5223 return new (Function.prototype.bind.apply(ctor, args))(); 5224 } 5225 5226 5227 return { 5228 invoke: invoke, 5229 instantiate: instantiate, 5230 get: getService, 5231 annotate: createInjector.$$annotate, 5232 has: function(name) { 5233 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 5234 } 5235 }; 5236 } 5237} 5238 5239createInjector.$$annotate = annotate; 5240 5241/** 5242 * @ngdoc provider 5243 * @name $anchorScrollProvider 5244 * @this 5245 * 5246 * @description 5247 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 5248 * {@link ng.$location#hash $location.hash()} changes. 5249 */ 5250function $AnchorScrollProvider() { 5251 5252 var autoScrollingEnabled = true; 5253 5254 /** 5255 * @ngdoc method 5256 * @name $anchorScrollProvider#disableAutoScrolling 5257 * 5258 * @description 5259 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 5260 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 5261 * Use this method to disable automatic scrolling. 5262 * 5263 * If automatic scrolling is disabled, one must explicitly call 5264 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 5265 * current hash. 5266 */ 5267 this.disableAutoScrolling = function() { 5268 autoScrollingEnabled = false; 5269 }; 5270 5271 /** 5272 * @ngdoc service 5273 * @name $anchorScroll 5274 * @kind function 5275 * @requires $window 5276 * @requires $location 5277 * @requires $rootScope 5278 * 5279 * @description 5280 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 5281 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 5282 * in the 5283 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document). 5284 * 5285 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 5286 * match any anchor whenever it changes. This can be disabled by calling 5287 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 5288 * 5289 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 5290 * vertical scroll-offset (either fixed or dynamic). 5291 * 5292 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 5293 * {@link ng.$location#hash $location.hash()} will be used. 5294 * 5295 * @property {(number|function|jqLite)} yOffset 5296 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 5297 * positioned elements at the top of the page, such as navbars, headers etc. 5298 * 5299 * `yOffset` can be specified in various ways: 5300 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 5301 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 5302 * a number representing the offset (in pixels).<br /><br /> 5303 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 5304 * the top of the page to the element's bottom will be used as offset.<br /> 5305 * **Note**: The element will be taken into account only as long as its `position` is set to 5306 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 5307 * their height and/or positioning according to the viewport's size. 5308 * 5309 * <br /> 5310 * <div class="alert alert-warning"> 5311 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 5312 * not some child element. 5313 * </div> 5314 * 5315 * @example 5316 <example module="anchorScrollExample" name="anchor-scroll"> 5317 <file name="index.html"> 5318 <div id="scrollArea" ng-controller="ScrollController"> 5319 <a ng-click="gotoBottom()">Go to bottom</a> 5320 <a id="bottom"></a> You're at the bottom! 5321 </div> 5322 </file> 5323 <file name="script.js"> 5324 angular.module('anchorScrollExample', []) 5325 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 5326 function($scope, $location, $anchorScroll) { 5327 $scope.gotoBottom = function() { 5328 // set the location.hash to the id of 5329 // the element you wish to scroll to. 5330 $location.hash('bottom'); 5331 5332 // call $anchorScroll() 5333 $anchorScroll(); 5334 }; 5335 }]); 5336 </file> 5337 <file name="style.css"> 5338 #scrollArea { 5339 height: 280px; 5340 overflow: auto; 5341 } 5342 5343 #bottom { 5344 display: block; 5345 margin-top: 2000px; 5346 } 5347 </file> 5348 </example> 5349 * 5350 * <hr /> 5351 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 5352 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 5353 * 5354 * @example 5355 <example module="anchorScrollOffsetExample" name="anchor-scroll-offset"> 5356 <file name="index.html"> 5357 <div class="fixed-header" ng-controller="headerCtrl"> 5358 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 5359 Go to anchor {{x}} 5360 </a> 5361 </div> 5362 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 5363 Anchor {{x}} of 5 5364 </div> 5365 </file> 5366 <file name="script.js"> 5367 angular.module('anchorScrollOffsetExample', []) 5368 .run(['$anchorScroll', function($anchorScroll) { 5369 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 5370 }]) 5371 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 5372 function($anchorScroll, $location, $scope) { 5373 $scope.gotoAnchor = function(x) { 5374 var newHash = 'anchor' + x; 5375 if ($location.hash() !== newHash) { 5376 // set the $location.hash to `newHash` and 5377 // $anchorScroll will automatically scroll to it 5378 $location.hash('anchor' + x); 5379 } else { 5380 // call $anchorScroll() explicitly, 5381 // since $location.hash hasn't changed 5382 $anchorScroll(); 5383 } 5384 }; 5385 } 5386 ]); 5387 </file> 5388 <file name="style.css"> 5389 body { 5390 padding-top: 50px; 5391 } 5392 5393 .anchor { 5394 border: 2px dashed DarkOrchid; 5395 padding: 10px 10px 200px 10px; 5396 } 5397 5398 .fixed-header { 5399 background-color: rgba(0, 0, 0, 0.2); 5400 height: 50px; 5401 position: fixed; 5402 top: 0; left: 0; right: 0; 5403 } 5404 5405 .fixed-header > a { 5406 display: inline-block; 5407 margin: 5px 15px; 5408 } 5409 </file> 5410 </example> 5411 */ 5412 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 5413 var document = $window.document; 5414 5415 // Helper function to get first anchor from a NodeList 5416 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 5417 // and working in all supported browsers.) 5418 function getFirstAnchor(list) { 5419 var result = null; 5420 Array.prototype.some.call(list, function(element) { 5421 if (nodeName_(element) === 'a') { 5422 result = element; 5423 return true; 5424 } 5425 }); 5426 return result; 5427 } 5428 5429 function getYOffset() { 5430 5431 var offset = scroll.yOffset; 5432 5433 if (isFunction(offset)) { 5434 offset = offset(); 5435 } else if (isElement(offset)) { 5436 var elem = offset[0]; 5437 var style = $window.getComputedStyle(elem); 5438 if (style.position !== 'fixed') { 5439 offset = 0; 5440 } else { 5441 offset = elem.getBoundingClientRect().bottom; 5442 } 5443 } else if (!isNumber(offset)) { 5444 offset = 0; 5445 } 5446 5447 return offset; 5448 } 5449 5450 function scrollTo(elem) { 5451 if (elem) { 5452 elem.scrollIntoView(); 5453 5454 var offset = getYOffset(); 5455 5456 if (offset) { 5457 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 5458 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 5459 // top of the viewport. 5460 // 5461 // IF the number of pixels from the top of `elem` to the end of the page's content is less 5462 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 5463 // way down the page. 5464 // 5465 // This is often the case for elements near the bottom of the page. 5466 // 5467 // In such cases we do not need to scroll the whole `offset` up, just the difference between 5468 // the top of the element and the offset, which is enough to align the top of `elem` at the 5469 // desired position. 5470 var elemTop = elem.getBoundingClientRect().top; 5471 $window.scrollBy(0, elemTop - offset); 5472 } 5473 } else { 5474 $window.scrollTo(0, 0); 5475 } 5476 } 5477 5478 function scroll(hash) { 5479 // Allow numeric hashes 5480 hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash(); 5481 var elm; 5482 5483 // empty hash, scroll to the top of the page 5484 if (!hash) scrollTo(null); 5485 5486 // element with given id 5487 else if ((elm = document.getElementById(hash))) scrollTo(elm); 5488 5489 // first anchor with given name :-D 5490 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 5491 5492 // no element and hash === 'top', scroll to the top of the page 5493 else if (hash === 'top') scrollTo(null); 5494 } 5495 5496 // does not scroll when user clicks on anchor link that is currently on 5497 // (no url change, no $location.hash() change), browser native does scroll 5498 if (autoScrollingEnabled) { 5499 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 5500 function autoScrollWatchAction(newVal, oldVal) { 5501 // skip the initial scroll if $location.hash is empty 5502 if (newVal === oldVal && newVal === '') return; 5503 5504 jqLiteDocumentLoaded(function() { 5505 $rootScope.$evalAsync(scroll); 5506 }); 5507 }); 5508 } 5509 5510 return scroll; 5511 }]; 5512} 5513 5514var $animateMinErr = minErr('$animate'); 5515var ELEMENT_NODE = 1; 5516var NG_ANIMATE_CLASSNAME = 'ng-animate'; 5517 5518function mergeClasses(a,b) { 5519 if (!a && !b) return ''; 5520 if (!a) return b; 5521 if (!b) return a; 5522 if (isArray(a)) a = a.join(' '); 5523 if (isArray(b)) b = b.join(' '); 5524 return a + ' ' + b; 5525} 5526 5527function extractElementNode(element) { 5528 for (var i = 0; i < element.length; i++) { 5529 var elm = element[i]; 5530 if (elm.nodeType === ELEMENT_NODE) { 5531 return elm; 5532 } 5533 } 5534} 5535 5536function splitClasses(classes) { 5537 if (isString(classes)) { 5538 classes = classes.split(' '); 5539 } 5540 5541 // Use createMap() to prevent class assumptions involving property names in 5542 // Object.prototype 5543 var obj = createMap(); 5544 forEach(classes, function(klass) { 5545 // sometimes the split leaves empty string values 5546 // incase extra spaces were applied to the options 5547 if (klass.length) { 5548 obj[klass] = true; 5549 } 5550 }); 5551 return obj; 5552} 5553 5554// if any other type of options value besides an Object value is 5555// passed into the $animate.method() animation then this helper code 5556// will be run which will ignore it. While this patch is not the 5557// greatest solution to this, a lot of existing plugins depend on 5558// $animate to either call the callback (< 1.2) or return a promise 5559// that can be changed. This helper function ensures that the options 5560// are wiped clean incase a callback function is provided. 5561function prepareAnimateOptions(options) { 5562 return isObject(options) 5563 ? options 5564 : {}; 5565} 5566 5567var $$CoreAnimateJsProvider = /** @this */ function() { 5568 this.$get = noop; 5569}; 5570 5571// this is prefixed with Core since it conflicts with 5572// the animateQueueProvider defined in ngAnimate/animateQueue.js 5573var $$CoreAnimateQueueProvider = /** @this */ function() { 5574 var postDigestQueue = new NgMap(); 5575 var postDigestElements = []; 5576 5577 this.$get = ['$$AnimateRunner', '$rootScope', 5578 function($$AnimateRunner, $rootScope) { 5579 return { 5580 enabled: noop, 5581 on: noop, 5582 off: noop, 5583 pin: noop, 5584 5585 push: function(element, event, options, domOperation) { 5586 if (domOperation) { 5587 domOperation(); 5588 } 5589 5590 options = options || {}; 5591 if (options.from) { 5592 element.css(options.from); 5593 } 5594 if (options.to) { 5595 element.css(options.to); 5596 } 5597 5598 if (options.addClass || options.removeClass) { 5599 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 5600 } 5601 5602 var runner = new $$AnimateRunner(); 5603 5604 // since there are no animations to run the runner needs to be 5605 // notified that the animation call is complete. 5606 runner.complete(); 5607 return runner; 5608 } 5609 }; 5610 5611 5612 function updateData(data, classes, value) { 5613 var changed = false; 5614 if (classes) { 5615 classes = isString(classes) ? classes.split(' ') : 5616 isArray(classes) ? classes : []; 5617 forEach(classes, function(className) { 5618 if (className) { 5619 changed = true; 5620 data[className] = value; 5621 } 5622 }); 5623 } 5624 return changed; 5625 } 5626 5627 function handleCSSClassChanges() { 5628 forEach(postDigestElements, function(element) { 5629 var data = postDigestQueue.get(element); 5630 if (data) { 5631 var existing = splitClasses(element.attr('class')); 5632 var toAdd = ''; 5633 var toRemove = ''; 5634 forEach(data, function(status, className) { 5635 var hasClass = !!existing[className]; 5636 if (status !== hasClass) { 5637 if (status) { 5638 toAdd += (toAdd.length ? ' ' : '') + className; 5639 } else { 5640 toRemove += (toRemove.length ? ' ' : '') + className; 5641 } 5642 } 5643 }); 5644 5645 forEach(element, function(elm) { 5646 if (toAdd) { 5647 jqLiteAddClass(elm, toAdd); 5648 } 5649 if (toRemove) { 5650 jqLiteRemoveClass(elm, toRemove); 5651 } 5652 }); 5653 postDigestQueue.delete(element); 5654 } 5655 }); 5656 postDigestElements.length = 0; 5657 } 5658 5659 5660 function addRemoveClassesPostDigest(element, add, remove) { 5661 var data = postDigestQueue.get(element) || {}; 5662 5663 var classesAdded = updateData(data, add, true); 5664 var classesRemoved = updateData(data, remove, false); 5665 5666 if (classesAdded || classesRemoved) { 5667 5668 postDigestQueue.set(element, data); 5669 postDigestElements.push(element); 5670 5671 if (postDigestElements.length === 1) { 5672 $rootScope.$$postDigest(handleCSSClassChanges); 5673 } 5674 } 5675 } 5676 }]; 5677}; 5678 5679/** 5680 * @ngdoc provider 5681 * @name $animateProvider 5682 * 5683 * @description 5684 * Default implementation of $animate that doesn't perform any animations, instead just 5685 * synchronously performs DOM updates and resolves the returned runner promise. 5686 * 5687 * In order to enable animations the `ngAnimate` module has to be loaded. 5688 * 5689 * To see the functional implementation check out `src/ngAnimate/animate.js`. 5690 */ 5691var $AnimateProvider = ['$provide', /** @this */ function($provide) { 5692 var provider = this; 5693 var classNameFilter = null; 5694 var customFilter = null; 5695 5696 this.$$registeredAnimations = Object.create(null); 5697 5698 /** 5699 * @ngdoc method 5700 * @name $animateProvider#register 5701 * 5702 * @description 5703 * Registers a new injectable animation factory function. The factory function produces the 5704 * animation object which contains callback functions for each event that is expected to be 5705 * animated. 5706 * 5707 * * `eventFn`: `function(element, ... , doneFunction, options)` 5708 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 5709 * on the type of animation additional arguments will be injected into the animation function. The 5710 * list below explains the function signatures for the different animation methods: 5711 * 5712 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 5713 * - addClass: function(element, addedClasses, doneFunction, options) 5714 * - removeClass: function(element, removedClasses, doneFunction, options) 5715 * - enter, leave, move: function(element, doneFunction, options) 5716 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 5717 * 5718 * Make sure to trigger the `doneFunction` once the animation is fully complete. 5719 * 5720 * ```js 5721 * return { 5722 * //enter, leave, move signature 5723 * eventFn : function(element, done, options) { 5724 * //code to run the animation 5725 * //once complete, then run done() 5726 * return function endFunction(wasCancelled) { 5727 * //code to cancel the animation 5728 * } 5729 * } 5730 * } 5731 * ``` 5732 * 5733 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 5734 * @param {Function} factory The factory function that will be executed to return the animation 5735 * object. 5736 */ 5737 this.register = function(name, factory) { 5738 if (name && name.charAt(0) !== '.') { 5739 throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name); 5740 } 5741 5742 var key = name + '-animation'; 5743 provider.$$registeredAnimations[name.substr(1)] = key; 5744 $provide.factory(key, factory); 5745 }; 5746 5747 /** 5748 * @ngdoc method 5749 * @name $animateProvider#customFilter 5750 * 5751 * @description 5752 * Sets and/or returns the custom filter function that is used to "filter" animations, i.e. 5753 * determine if an animation is allowed or not. When no filter is specified (the default), no 5754 * animation will be blocked. Setting the `customFilter` value will only allow animations for 5755 * which the filter function's return value is truthy. 5756 * 5757 * This allows to easily create arbitrarily complex rules for filtering animations, such as 5758 * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc. 5759 * Filtering animations can also boost performance for low-powered devices, as well as 5760 * applications containing a lot of structural operations. 5761 * 5762 * <div class="alert alert-success"> 5763 * **Best Practice:** 5764 * Keep the filtering function as lean as possible, because it will be called for each DOM 5765 * action (e.g. insertion, removal, class change) performed by "animation-aware" directives. 5766 * See {@link guide/animations#which-directives-support-animations- here} for a list of built-in 5767 * directives that support animations. 5768 * Performing computationally expensive or time-consuming operations on each call of the 5769 * filtering function can make your animations sluggish. 5770 * </div> 5771 * 5772 * **Note:** If present, `customFilter` will be checked before 5773 * {@link $animateProvider#classNameFilter classNameFilter}. 5774 * 5775 * @param {Function=} filterFn - The filter function which will be used to filter all animations. 5776 * If a falsy value is returned, no animation will be performed. The function will be called 5777 * with the following arguments: 5778 * - **node** `{DOMElement}` - The DOM element to be animated. 5779 * - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass` 5780 * etc). 5781 * - **options** `{Object}` - A collection of options/styles used for the animation. 5782 * @return {Function} The current filter function or `null` if there is none set. 5783 */ 5784 this.customFilter = function(filterFn) { 5785 if (arguments.length === 1) { 5786 customFilter = isFunction(filterFn) ? filterFn : null; 5787 } 5788 5789 return customFilter; 5790 }; 5791 5792 /** 5793 * @ngdoc method 5794 * @name $animateProvider#classNameFilter 5795 * 5796 * @description 5797 * Sets and/or returns the CSS class regular expression that is checked when performing 5798 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5799 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5800 * When setting the `classNameFilter` value, animations will only be performed on elements 5801 * that successfully match the filter expression. This in turn can boost performance 5802 * for low-powered devices as well as applications containing a lot of structural operations. 5803 * 5804 * **Note:** If present, `classNameFilter` will be checked after 5805 * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns 5806 * false, `classNameFilter` will not be checked. 5807 * 5808 * @param {RegExp=} expression The className expression which will be checked against all animations 5809 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5810 */ 5811 this.classNameFilter = function(expression) { 5812 if (arguments.length === 1) { 5813 classNameFilter = (expression instanceof RegExp) ? expression : null; 5814 if (classNameFilter) { 5815 var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]'); 5816 if (reservedRegex.test(classNameFilter.toString())) { 5817 classNameFilter = null; 5818 throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5819 } 5820 } 5821 } 5822 return classNameFilter; 5823 }; 5824 5825 this.$get = ['$$animateQueue', function($$animateQueue) { 5826 function domInsert(element, parentElement, afterElement) { 5827 // if for some reason the previous element was removed 5828 // from the dom sometime before this code runs then let's 5829 // just stick to using the parent element as the anchor 5830 if (afterElement) { 5831 var afterNode = extractElementNode(afterElement); 5832 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5833 afterElement = null; 5834 } 5835 } 5836 if (afterElement) { 5837 afterElement.after(element); 5838 } else { 5839 parentElement.prepend(element); 5840 } 5841 } 5842 5843 /** 5844 * @ngdoc service 5845 * @name $animate 5846 * @description The $animate service exposes a series of DOM utility methods that provide support 5847 * for animation hooks. The default behavior is the application of DOM operations, however, 5848 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5849 * to ensure that animation runs with the triggered DOM operation. 5850 * 5851 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 5852 * included and only when it is active then the animation hooks that `$animate` triggers will be 5853 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5854 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5855 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5856 * 5857 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5858 * 5859 * To learn more about enabling animation support, click here to visit the 5860 * {@link ngAnimate ngAnimate module page}. 5861 */ 5862 return { 5863 // we don't call it directly since non-existant arguments may 5864 // be interpreted as null within the sub enabled function 5865 5866 /** 5867 * 5868 * @ngdoc method 5869 * @name $animate#on 5870 * @kind function 5871 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5872 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5873 * is fired with the following params: 5874 * 5875 * ```js 5876 * $animate.on('enter', container, 5877 * function callback(element, phase) { 5878 * // cool we detected an enter animation within the container 5879 * } 5880 * ); 5881 * ``` 5882 * 5883 * <div class="alert alert-warning"> 5884 * **Note**: Generally, the events that are fired correspond 1:1 to `$animate` method names, 5885 * e.g. {@link ng.$animate#addClass addClass()} will fire `addClass`, and {@link ng.ngClass} 5886 * will fire `addClass` if classes are added, and `removeClass` if classes are removed. 5887 * However, there are two exceptions: 5888 * 5889 * <ul> 5890 * <li>if both an {@link ng.$animate#addClass addClass()} and a 5891 * {@link ng.$animate#removeClass removeClass()} action are performed during the same 5892 * animation, the event fired will be `setClass`. This is true even for `ngClass`.</li> 5893 * <li>an {@link ng.$animate#animate animate()} call that adds and removes classes will fire 5894 * the `setClass` event, but if it either removes or adds classes, 5895 * it will fire `animate` instead.</li> 5896 * </ul> 5897 * 5898 * </div> 5899 * 5900 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5901 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5902 * as well as among its children 5903 * @param {Function} callback the callback function that will be fired when the listener is triggered. 5904 * 5905 * The arguments present in the callback function are: 5906 * * `element` - The captured DOM element that the animation was fired on. 5907 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5908 * * `data` - an object with these properties: 5909 * * addClass - `{string|null}` - space-separated CSS classes to add to the element 5910 * * removeClass - `{string|null}` - space-separated CSS classes to remove from the element 5911 * * from - `{Object|null}` - CSS properties & values at the beginning of the animation 5912 * * to - `{Object|null}` - CSS properties & values at the end of the animation 5913 * 5914 * Note that the callback does not trigger a scope digest. Wrap your call into a 5915 * {@link $rootScope.Scope#$apply scope.$apply} to propagate changes to the scope. 5916 */ 5917 on: $$animateQueue.on, 5918 5919 /** 5920 * 5921 * @ngdoc method 5922 * @name $animate#off 5923 * @kind function 5924 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5925 * can be used in three different ways depending on the arguments: 5926 * 5927 * ```js 5928 * // remove all the animation event listeners listening for `enter` 5929 * $animate.off('enter'); 5930 * 5931 * // remove listeners for all animation events from the container element 5932 * $animate.off(container); 5933 * 5934 * // remove all the animation event listeners listening for `enter` on the given element and its children 5935 * $animate.off('enter', container); 5936 * 5937 * // remove the event listener function provided by `callback` that is set 5938 * // to listen for `enter` on the given `container` as well as its children 5939 * $animate.off('enter', container, callback); 5940 * ``` 5941 * 5942 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, 5943 * addClass, removeClass, etc...), or the container element. If it is the element, all other 5944 * arguments are ignored. 5945 * @param {DOMElement=} container the container element the event listener was placed on 5946 * @param {Function=} callback the callback function that was registered as the listener 5947 */ 5948 off: $$animateQueue.off, 5949 5950 /** 5951 * @ngdoc method 5952 * @name $animate#pin 5953 * @kind function 5954 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5955 * outside of the DOM structure of the AngularJS application. By doing so, any animation triggered via `$animate` can be issued on the 5956 * element despite being outside the realm of the application or within another application. Say for example if the application 5957 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5958 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5959 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5960 * 5961 * Note that this feature is only active when the `ngAnimate` module is used. 5962 * 5963 * @param {DOMElement} element the external element that will be pinned 5964 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5965 */ 5966 pin: $$animateQueue.pin, 5967 5968 /** 5969 * 5970 * @ngdoc method 5971 * @name $animate#enabled 5972 * @kind function 5973 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5974 * function can be called in four ways: 5975 * 5976 * ```js 5977 * // returns true or false 5978 * $animate.enabled(); 5979 * 5980 * // changes the enabled state for all animations 5981 * $animate.enabled(false); 5982 * $animate.enabled(true); 5983 * 5984 * // returns true or false if animations are enabled for an element 5985 * $animate.enabled(element); 5986 * 5987 * // changes the enabled state for an element and its children 5988 * $animate.enabled(element, true); 5989 * $animate.enabled(element, false); 5990 * ``` 5991 * 5992 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5993 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5994 * 5995 * @return {boolean} whether or not animations are enabled 5996 */ 5997 enabled: $$animateQueue.enabled, 5998 5999 /** 6000 * @ngdoc method 6001 * @name $animate#cancel 6002 * @kind function 6003 * @description Cancels the provided animation and applies the end state of the animation. 6004 * Note that this does not cancel the underlying operation, e.g. the setting of classes or 6005 * adding the element to the DOM. 6006 * 6007 * @param {animationRunner} animationRunner An animation runner returned by an $animate function. 6008 * 6009 * @example 6010 <example module="animationExample" deps="angular-animate.js" animations="true" name="animate-cancel"> 6011 <file name="app.js"> 6012 angular.module('animationExample', ['ngAnimate']).component('cancelExample', { 6013 templateUrl: 'template.html', 6014 controller: function($element, $animate) { 6015 this.runner = null; 6016 6017 this.addClass = function() { 6018 this.runner = $animate.addClass($element.find('div'), 'red'); 6019 var ctrl = this; 6020 this.runner.finally(function() { 6021 ctrl.runner = null; 6022 }); 6023 }; 6024 6025 this.removeClass = function() { 6026 this.runner = $animate.removeClass($element.find('div'), 'red'); 6027 var ctrl = this; 6028 this.runner.finally(function() { 6029 ctrl.runner = null; 6030 }); 6031 }; 6032 6033 this.cancel = function() { 6034 $animate.cancel(this.runner); 6035 }; 6036 } 6037 }); 6038 </file> 6039 <file name="template.html"> 6040 <p> 6041 <button id="add" ng-click="$ctrl.addClass()">Add</button> 6042 <button ng-click="$ctrl.removeClass()">Remove</button> 6043 <br> 6044 <button id="cancel" ng-click="$ctrl.cancel()" ng-disabled="!$ctrl.runner">Cancel</button> 6045 <br> 6046 <div id="target">CSS-Animated Text</div> 6047 </p> 6048 </file> 6049 <file name="index.html"> 6050 <cancel-example></cancel-example> 6051 </file> 6052 <file name="style.css"> 6053 .red-add, .red-remove { 6054 transition: all 4s cubic-bezier(0.250, 0.460, 0.450, 0.940); 6055 } 6056 6057 .red, 6058 .red-add.red-add-active { 6059 color: #FF0000; 6060 font-size: 40px; 6061 } 6062 6063 .red-remove.red-remove-active { 6064 font-size: 10px; 6065 color: black; 6066 } 6067 6068 </file> 6069 </example> 6070 */ 6071 cancel: function(runner) { 6072 if (runner.cancel) { 6073 runner.cancel(); 6074 } 6075 }, 6076 6077 /** 6078 * 6079 * @ngdoc method 6080 * @name $animate#enter 6081 * @kind function 6082 * @description Inserts the element into the DOM either after the `after` element (if provided) or 6083 * as the first child within the `parent` element and then triggers an animation. 6084 * A promise is returned that will be resolved during the next digest once the animation 6085 * has completed. 6086 * 6087 * @param {DOMElement} element the element which will be inserted into the DOM 6088 * @param {DOMElement} parent the parent element which will append the element as 6089 * a child (so long as the after element is not present) 6090 * @param {DOMElement=} after the sibling element after which the element will be appended 6091 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6092 * The object can have the following properties: 6093 * 6094 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6095 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6096 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6097 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6098 * 6099 * @return {Runner} the animation runner 6100 */ 6101 enter: function(element, parent, after, options) { 6102 parent = parent && jqLite(parent); 6103 after = after && jqLite(after); 6104 parent = parent || after.parent(); 6105 domInsert(element, parent, after); 6106 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 6107 }, 6108 6109 /** 6110 * 6111 * @ngdoc method 6112 * @name $animate#move 6113 * @kind function 6114 * @description Inserts (moves) the element into its new position in the DOM either after 6115 * the `after` element (if provided) or as the first child within the `parent` element 6116 * and then triggers an animation. A promise is returned that will be resolved 6117 * during the next digest once the animation has completed. 6118 * 6119 * @param {DOMElement} element the element which will be moved into the new DOM position 6120 * @param {DOMElement} parent the parent element which will append the element as 6121 * a child (so long as the after element is not present) 6122 * @param {DOMElement=} after the sibling element after which the element will be appended 6123 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6124 * The object can have the following properties: 6125 * 6126 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6127 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6128 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6129 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6130 * 6131 * @return {Runner} the animation runner 6132 */ 6133 move: function(element, parent, after, options) { 6134 parent = parent && jqLite(parent); 6135 after = after && jqLite(after); 6136 parent = parent || after.parent(); 6137 domInsert(element, parent, after); 6138 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 6139 }, 6140 6141 /** 6142 * @ngdoc method 6143 * @name $animate#leave 6144 * @kind function 6145 * @description Triggers an animation and then removes the element from the DOM. 6146 * When the function is called a promise is returned that will be resolved during the next 6147 * digest once the animation has completed. 6148 * 6149 * @param {DOMElement} element the element which will be removed from the DOM 6150 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6151 * The object can have the following properties: 6152 * 6153 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6154 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6155 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6156 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6157 * 6158 * @return {Runner} the animation runner 6159 */ 6160 leave: function(element, options) { 6161 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 6162 element.remove(); 6163 }); 6164 }, 6165 6166 /** 6167 * @ngdoc method 6168 * @name $animate#addClass 6169 * @kind function 6170 * 6171 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 6172 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 6173 * animation if element already contains the CSS class or if the class is removed at a later step. 6174 * Note that class-based animations are treated differently compared to structural animations 6175 * (like enter, move and leave) since the CSS classes may be added/removed at different points 6176 * depending if CSS or JavaScript animations are used. 6177 * 6178 * @param {DOMElement} element the element which the CSS classes will be applied to 6179 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 6180 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6181 * The object can have the following properties: 6182 * 6183 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6184 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6185 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6186 * 6187 * @return {Runner} animationRunner the animation runner 6188 */ 6189 addClass: function(element, className, options) { 6190 options = prepareAnimateOptions(options); 6191 options.addClass = mergeClasses(options.addclass, className); 6192 return $$animateQueue.push(element, 'addClass', options); 6193 }, 6194 6195 /** 6196 * @ngdoc method 6197 * @name $animate#removeClass 6198 * @kind function 6199 * 6200 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 6201 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 6202 * animation if element does not contain the CSS class or if the class is added at a later step. 6203 * Note that class-based animations are treated differently compared to structural animations 6204 * (like enter, move and leave) since the CSS classes may be added/removed at different points 6205 * depending if CSS or JavaScript animations are used. 6206 * 6207 * @param {DOMElement} element the element which the CSS classes will be applied to 6208 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 6209 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6210 * The object can have the following properties: 6211 * 6212 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6213 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6214 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6215 * 6216 * @return {Runner} the animation runner 6217 */ 6218 removeClass: function(element, className, options) { 6219 options = prepareAnimateOptions(options); 6220 options.removeClass = mergeClasses(options.removeClass, className); 6221 return $$animateQueue.push(element, 'removeClass', options); 6222 }, 6223 6224 /** 6225 * @ngdoc method 6226 * @name $animate#setClass 6227 * @kind function 6228 * 6229 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 6230 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 6231 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 6232 * passed. Note that class-based animations are treated differently compared to structural animations 6233 * (like enter, move and leave) since the CSS classes may be added/removed at different points 6234 * depending if CSS or JavaScript animations are used. 6235 * 6236 * @param {DOMElement} element the element which the CSS classes will be applied to 6237 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 6238 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 6239 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6240 * The object can have the following properties: 6241 * 6242 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6243 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6244 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6245 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6246 * 6247 * @return {Runner} the animation runner 6248 */ 6249 setClass: function(element, add, remove, options) { 6250 options = prepareAnimateOptions(options); 6251 options.addClass = mergeClasses(options.addClass, add); 6252 options.removeClass = mergeClasses(options.removeClass, remove); 6253 return $$animateQueue.push(element, 'setClass', options); 6254 }, 6255 6256 /** 6257 * @ngdoc method 6258 * @name $animate#animate 6259 * @kind function 6260 * 6261 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 6262 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 6263 * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and 6264 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 6265 * style in `to`, the style in `from` is applied immediately, and no animation is run. 6266 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 6267 * method (or as part of the `options` parameter): 6268 * 6269 * ```js 6270 * ngModule.animation('.my-inline-animation', function() { 6271 * return { 6272 * animate : function(element, from, to, done, options) { 6273 * //animation 6274 * done(); 6275 * } 6276 * } 6277 * }); 6278 * ``` 6279 * 6280 * @param {DOMElement} element the element which the CSS styles will be applied to 6281 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 6282 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 6283 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 6284 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 6285 * (Note that if no animation is detected then this value will not be applied to the element.) 6286 * @param {object=} options an optional collection of options/styles that will be applied to the element. 6287 * The object can have the following properties: 6288 * 6289 * - **addClass** - `{string}` - space-separated CSS classes to add to element 6290 * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` 6291 * - **removeClass** - `{string}` - space-separated CSS classes to remove from element 6292 * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` 6293 * 6294 * @return {Runner} the animation runner 6295 */ 6296 animate: function(element, from, to, className, options) { 6297 options = prepareAnimateOptions(options); 6298 options.from = options.from ? extend(options.from, from) : from; 6299 options.to = options.to ? extend(options.to, to) : to; 6300 6301 className = className || 'ng-inline-animate'; 6302 options.tempClasses = mergeClasses(options.tempClasses, className); 6303 return $$animateQueue.push(element, 'animate', options); 6304 } 6305 }; 6306 }]; 6307}]; 6308 6309var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { 6310 this.$get = ['$$rAF', function($$rAF) { 6311 var waitQueue = []; 6312 6313 function waitForTick(fn) { 6314 waitQueue.push(fn); 6315 if (waitQueue.length > 1) return; 6316 $$rAF(function() { 6317 for (var i = 0; i < waitQueue.length; i++) { 6318 waitQueue[i](); 6319 } 6320 waitQueue = []; 6321 }); 6322 } 6323 6324 return function() { 6325 var passed = false; 6326 waitForTick(function() { 6327 passed = true; 6328 }); 6329 return function(callback) { 6330 if (passed) { 6331 callback(); 6332 } else { 6333 waitForTick(callback); 6334 } 6335 }; 6336 }; 6337 }]; 6338}; 6339 6340var $$AnimateRunnerFactoryProvider = /** @this */ function() { 6341 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout', 6342 function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) { 6343 6344 var INITIAL_STATE = 0; 6345 var DONE_PENDING_STATE = 1; 6346 var DONE_COMPLETE_STATE = 2; 6347 6348 AnimateRunner.chain = function(chain, callback) { 6349 var index = 0; 6350 6351 next(); 6352 function next() { 6353 if (index === chain.length) { 6354 callback(true); 6355 return; 6356 } 6357 6358 chain[index](function(response) { 6359 if (response === false) { 6360 callback(false); 6361 return; 6362 } 6363 index++; 6364 next(); 6365 }); 6366 } 6367 }; 6368 6369 AnimateRunner.all = function(runners, callback) { 6370 var count = 0; 6371 var status = true; 6372 forEach(runners, function(runner) { 6373 runner.done(onProgress); 6374 }); 6375 6376 function onProgress(response) { 6377 status = status && response; 6378 if (++count === runners.length) { 6379 callback(status); 6380 } 6381 } 6382 }; 6383 6384 function AnimateRunner(host) { 6385 this.setHost(host); 6386 6387 var rafTick = $$animateAsyncRun(); 6388 var timeoutTick = function(fn) { 6389 $timeout(fn, 0, false); 6390 }; 6391 6392 this._doneCallbacks = []; 6393 this._tick = function(fn) { 6394 if ($$isDocumentHidden()) { 6395 timeoutTick(fn); 6396 } else { 6397 rafTick(fn); 6398 } 6399 }; 6400 this._state = 0; 6401 } 6402 6403 AnimateRunner.prototype = { 6404 setHost: function(host) { 6405 this.host = host || {}; 6406 }, 6407 6408 done: function(fn) { 6409 if (this._state === DONE_COMPLETE_STATE) { 6410 fn(); 6411 } else { 6412 this._doneCallbacks.push(fn); 6413 } 6414 }, 6415 6416 progress: noop, 6417 6418 getPromise: function() { 6419 if (!this.promise) { 6420 var self = this; 6421 this.promise = $q(function(resolve, reject) { 6422 self.done(function(status) { 6423 if (status === false) { 6424 reject(); 6425 } else { 6426 resolve(); 6427 } 6428 }); 6429 }); 6430 } 6431 return this.promise; 6432 }, 6433 6434 then: function(resolveHandler, rejectHandler) { 6435 return this.getPromise().then(resolveHandler, rejectHandler); 6436 }, 6437 6438 'catch': function(handler) { 6439 return this.getPromise()['catch'](handler); 6440 }, 6441 6442 'finally': function(handler) { 6443 return this.getPromise()['finally'](handler); 6444 }, 6445 6446 pause: function() { 6447 if (this.host.pause) { 6448 this.host.pause(); 6449 } 6450 }, 6451 6452 resume: function() { 6453 if (this.host.resume) { 6454 this.host.resume(); 6455 } 6456 }, 6457 6458 end: function() { 6459 if (this.host.end) { 6460 this.host.end(); 6461 } 6462 this._resolve(true); 6463 }, 6464 6465 cancel: function() { 6466 if (this.host.cancel) { 6467 this.host.cancel(); 6468 } 6469 this._resolve(false); 6470 }, 6471 6472 complete: function(response) { 6473 var self = this; 6474 if (self._state === INITIAL_STATE) { 6475 self._state = DONE_PENDING_STATE; 6476 self._tick(function() { 6477 self._resolve(response); 6478 }); 6479 } 6480 }, 6481 6482 _resolve: function(response) { 6483 if (this._state !== DONE_COMPLETE_STATE) { 6484 forEach(this._doneCallbacks, function(fn) { 6485 fn(response); 6486 }); 6487 this._doneCallbacks.length = 0; 6488 this._state = DONE_COMPLETE_STATE; 6489 } 6490 } 6491 }; 6492 6493 return AnimateRunner; 6494 }]; 6495}; 6496 6497/* exported $CoreAnimateCssProvider */ 6498 6499/** 6500 * @ngdoc service 6501 * @name $animateCss 6502 * @kind object 6503 * @this 6504 * 6505 * @description 6506 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 6507 * then the `$animateCss` service will actually perform animations. 6508 * 6509 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 6510 */ 6511var $CoreAnimateCssProvider = function() { 6512 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 6513 6514 return function(element, initialOptions) { 6515 // all of the animation functions should create 6516 // a copy of the options data, however, if a 6517 // parent service has already created a copy then 6518 // we should stick to using that 6519 var options = initialOptions || {}; 6520 if (!options.$$prepared) { 6521 options = copy(options); 6522 } 6523 6524 // there is no point in applying the styles since 6525 // there is no animation that goes on at all in 6526 // this version of $animateCss. 6527 if (options.cleanupStyles) { 6528 options.from = options.to = null; 6529 } 6530 6531 if (options.from) { 6532 element.css(options.from); 6533 options.from = null; 6534 } 6535 6536 var closed, runner = new $$AnimateRunner(); 6537 return { 6538 start: run, 6539 end: run 6540 }; 6541 6542 function run() { 6543 $$rAF(function() { 6544 applyAnimationContents(); 6545 if (!closed) { 6546 runner.complete(); 6547 } 6548 closed = true; 6549 }); 6550 return runner; 6551 } 6552 6553 function applyAnimationContents() { 6554 if (options.addClass) { 6555 element.addClass(options.addClass); 6556 options.addClass = null; 6557 } 6558 if (options.removeClass) { 6559 element.removeClass(options.removeClass); 6560 options.removeClass = null; 6561 } 6562 if (options.to) { 6563 element.css(options.to); 6564 options.to = null; 6565 } 6566 } 6567 }; 6568 }]; 6569}; 6570 6571/* global getHash: true, stripHash: false */ 6572 6573function getHash(url) { 6574 var index = url.indexOf('#'); 6575 return index === -1 ? '' : url.substr(index); 6576} 6577 6578function trimEmptyHash(url) { 6579 return url.replace(/#$/, ''); 6580} 6581 6582/** 6583 * ! This is a private undocumented service ! 6584 * 6585 * @name $browser 6586 * @requires $log 6587 * @description 6588 * This object has two goals: 6589 * 6590 * - hide all the global state in the browser caused by the window object 6591 * - abstract away all the browser specific features and inconsistencies 6592 * 6593 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 6594 * service, which can be used for convenient testing of the application without the interaction with 6595 * the real browser apis. 6596 */ 6597/** 6598 * @param {object} window The global window object. 6599 * @param {object} document jQuery wrapped document. 6600 * @param {object} $log window.console or an object with the same interface. 6601 * @param {object} $sniffer $sniffer service 6602 */ 6603function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) { 6604 var self = this, 6605 location = window.location, 6606 history = window.history, 6607 setTimeout = window.setTimeout, 6608 clearTimeout = window.clearTimeout, 6609 pendingDeferIds = {}, 6610 taskTracker = $$taskTrackerFactory($log); 6611 6612 self.isMock = false; 6613 6614 ////////////////////////////////////////////////////////////// 6615 // Task-tracking API 6616 ////////////////////////////////////////////////////////////// 6617 6618 // TODO(vojta): remove this temporary api 6619 self.$$completeOutstandingRequest = taskTracker.completeTask; 6620 self.$$incOutstandingRequestCount = taskTracker.incTaskCount; 6621 6622 // TODO(vojta): prefix this method with $$ ? 6623 self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks; 6624 6625 ////////////////////////////////////////////////////////////// 6626 // URL API 6627 ////////////////////////////////////////////////////////////// 6628 6629 var cachedState, lastHistoryState, 6630 lastBrowserUrl = location.href, 6631 baseElement = document.find('base'), 6632 pendingLocation = null, 6633 getCurrentState = !$sniffer.history ? noop : function getCurrentState() { 6634 try { 6635 return history.state; 6636 } catch (e) { 6637 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 6638 } 6639 }; 6640 6641 cacheState(); 6642 6643 /** 6644 * @name $browser#url 6645 * 6646 * @description 6647 * GETTER: 6648 * Without any argument, this method just returns current value of `location.href` (with a 6649 * trailing `#` stripped of if the hash is empty). 6650 * 6651 * SETTER: 6652 * With at least one argument, this method sets url to new value. 6653 * If html5 history api supported, `pushState`/`replaceState` is used, otherwise 6654 * `location.href`/`location.replace` is used. 6655 * Returns its own instance to allow chaining. 6656 * 6657 * NOTE: this api is intended for use only by the `$location` service. Please use the 6658 * {@link ng.$location $location service} to change url. 6659 * 6660 * @param {string} url New url (when used as setter) 6661 * @param {boolean=} replace Should new url replace current history record? 6662 * @param {object=} state State object to use with `pushState`/`replaceState` 6663 */ 6664 self.url = function(url, replace, state) { 6665 // In modern browsers `history.state` is `null` by default; treating it separately 6666 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 6667 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 6668 if (isUndefined(state)) { 6669 state = null; 6670 } 6671 6672 // Android Browser BFCache causes location, history reference to become stale. 6673 if (location !== window.location) location = window.location; 6674 if (history !== window.history) history = window.history; 6675 6676 // setter 6677 if (url) { 6678 var sameState = lastHistoryState === state; 6679 6680 // Normalize the inputted URL 6681 url = urlResolve(url).href; 6682 6683 // Don't change anything if previous and current URLs and states match. This also prevents 6684 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 6685 // See https://github.com/angular/angular.js/commit/ffb2701 6686 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 6687 return self; 6688 } 6689 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 6690 lastBrowserUrl = url; 6691 lastHistoryState = state; 6692 // Don't use history API if only the hash changed 6693 // due to a bug in IE10/IE11 which leads 6694 // to not firing a `hashchange` nor `popstate` event 6695 // in some cases (see #9143). 6696 if ($sniffer.history && (!sameBase || !sameState)) { 6697 history[replace ? 'replaceState' : 'pushState'](state, '', url); 6698 cacheState(); 6699 } else { 6700 if (!sameBase) { 6701 pendingLocation = url; 6702 } 6703 if (replace) { 6704 location.replace(url); 6705 } else if (!sameBase) { 6706 location.href = url; 6707 } else { 6708 location.hash = getHash(url); 6709 } 6710 if (location.href !== url) { 6711 pendingLocation = url; 6712 } 6713 } 6714 if (pendingLocation) { 6715 pendingLocation = url; 6716 } 6717 return self; 6718 // getter 6719 } else { 6720 // - pendingLocation is needed as browsers don't allow to read out 6721 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 6722 // https://openradar.appspot.com/22186109). 6723 return trimEmptyHash(pendingLocation || location.href); 6724 } 6725 }; 6726 6727 /** 6728 * @name $browser#state 6729 * 6730 * @description 6731 * This method is a getter. 6732 * 6733 * Return history.state or null if history.state is undefined. 6734 * 6735 * @returns {object} state 6736 */ 6737 self.state = function() { 6738 return cachedState; 6739 }; 6740 6741 var urlChangeListeners = [], 6742 urlChangeInit = false; 6743 6744 function cacheStateAndFireUrlChange() { 6745 pendingLocation = null; 6746 fireStateOrUrlChange(); 6747 } 6748 6749 // This variable should be used *only* inside the cacheState function. 6750 var lastCachedState = null; 6751 function cacheState() { 6752 // This should be the only place in $browser where `history.state` is read. 6753 cachedState = getCurrentState(); 6754 cachedState = isUndefined(cachedState) ? null : cachedState; 6755 6756 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 6757 if (equals(cachedState, lastCachedState)) { 6758 cachedState = lastCachedState; 6759 } 6760 6761 lastCachedState = cachedState; 6762 lastHistoryState = cachedState; 6763 } 6764 6765 function fireStateOrUrlChange() { 6766 var prevLastHistoryState = lastHistoryState; 6767 cacheState(); 6768 6769 if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) { 6770 return; 6771 } 6772 6773 lastBrowserUrl = self.url(); 6774 lastHistoryState = cachedState; 6775 forEach(urlChangeListeners, function(listener) { 6776 listener(self.url(), cachedState); 6777 }); 6778 } 6779 6780 /** 6781 * @name $browser#onUrlChange 6782 * 6783 * @description 6784 * Register callback function that will be called, when url changes. 6785 * 6786 * It's only called when the url is changed from outside of AngularJS: 6787 * - user types different url into address bar 6788 * - user clicks on history (forward/back) button 6789 * - user clicks on a link 6790 * 6791 * It's not called when url is changed by $browser.url() method 6792 * 6793 * The listener gets called with new url as parameter. 6794 * 6795 * NOTE: this api is intended for use only by the $location service. Please use the 6796 * {@link ng.$location $location service} to monitor url changes in AngularJS apps. 6797 * 6798 * @param {function(string)} listener Listener function to be called when url changes. 6799 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 6800 */ 6801 self.onUrlChange = function(callback) { 6802 // TODO(vojta): refactor to use node's syntax for events 6803 if (!urlChangeInit) { 6804 // We listen on both (hashchange/popstate) when available, as some browsers don't 6805 // fire popstate when user changes the address bar and don't fire hashchange when url 6806 // changed by push/replaceState 6807 6808 // html5 history api - popstate event 6809 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 6810 // hashchange event 6811 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 6812 6813 urlChangeInit = true; 6814 } 6815 6816 urlChangeListeners.push(callback); 6817 return callback; 6818 }; 6819 6820 /** 6821 * @private 6822 * Remove popstate and hashchange handler from window. 6823 * 6824 * NOTE: this api is intended for use only by $rootScope. 6825 */ 6826 self.$$applicationDestroyed = function() { 6827 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 6828 }; 6829 6830 /** 6831 * Checks whether the url has changed outside of AngularJS. 6832 * Needs to be exported to be able to check for changes that have been done in sync, 6833 * as hashchange/popstate events fire in async. 6834 */ 6835 self.$$checkUrlChange = fireStateOrUrlChange; 6836 6837 ////////////////////////////////////////////////////////////// 6838 // Misc API 6839 ////////////////////////////////////////////////////////////// 6840 6841 /** 6842 * @name $browser#baseHref 6843 * 6844 * @description 6845 * Returns current <base href> 6846 * (always relative - without domain) 6847 * 6848 * @returns {string} The current base href 6849 */ 6850 self.baseHref = function() { 6851 var href = baseElement.attr('href'); 6852 return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : ''; 6853 }; 6854 6855 /** 6856 * @name $browser#defer 6857 * @param {function()} fn A function, who's execution should be deferred. 6858 * @param {number=} [delay=0] Number of milliseconds to defer the function execution. 6859 * @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred. 6860 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 6861 * 6862 * @description 6863 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 6864 * 6865 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 6866 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 6867 * via `$browser.defer.flush()`. 6868 * 6869 */ 6870 self.defer = function(fn, delay, taskType) { 6871 var timeoutId; 6872 6873 delay = delay || 0; 6874 taskType = taskType || taskTracker.DEFAULT_TASK_TYPE; 6875 6876 taskTracker.incTaskCount(taskType); 6877 timeoutId = setTimeout(function() { 6878 delete pendingDeferIds[timeoutId]; 6879 taskTracker.completeTask(fn, taskType); 6880 }, delay); 6881 pendingDeferIds[timeoutId] = taskType; 6882 6883 return timeoutId; 6884 }; 6885 6886 6887 /** 6888 * @name $browser#defer.cancel 6889 * 6890 * @description 6891 * Cancels a deferred task identified with `deferId`. 6892 * 6893 * @param {*} deferId Token returned by the `$browser.defer` function. 6894 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 6895 * canceled. 6896 */ 6897 self.defer.cancel = function(deferId) { 6898 if (pendingDeferIds.hasOwnProperty(deferId)) { 6899 var taskType = pendingDeferIds[deferId]; 6900 delete pendingDeferIds[deferId]; 6901 clearTimeout(deferId); 6902 taskTracker.completeTask(noop, taskType); 6903 return true; 6904 } 6905 return false; 6906 }; 6907 6908} 6909 6910/** @this */ 6911function $BrowserProvider() { 6912 this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory', 6913 function($window, $log, $sniffer, $document, $$taskTrackerFactory) { 6914 return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory); 6915 }]; 6916} 6917 6918/** 6919 * @ngdoc service 6920 * @name $cacheFactory 6921 * @this 6922 * 6923 * @description 6924 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 6925 * them. 6926 * 6927 * ```js 6928 * 6929 * var cache = $cacheFactory('cacheId'); 6930 * expect($cacheFactory.get('cacheId')).toBe(cache); 6931 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 6932 * 6933 * cache.put("key", "value"); 6934 * cache.put("another key", "another value"); 6935 * 6936 * // We've specified no options on creation 6937 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 6938 * 6939 * ``` 6940 * 6941 * 6942 * @param {string} cacheId Name or id of the newly created cache. 6943 * @param {object=} options Options object that specifies the cache behavior. Properties: 6944 * 6945 * - `{number=}` `capacity` — turns the cache into LRU cache. 6946 * 6947 * @returns {object} Newly created cache object with the following set of methods: 6948 * 6949 * - `{object}` `info()` — Returns id, size, and options of cache. 6950 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns 6951 * it. 6952 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. 6953 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. 6954 * - `{void}` `removeAll()` — Removes all cached values. 6955 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. 6956 * 6957 * @example 6958 <example module="cacheExampleApp" name="cache-factory"> 6959 <file name="index.html"> 6960 <div ng-controller="CacheController"> 6961 <input ng-model="newCacheKey" placeholder="Key"> 6962 <input ng-model="newCacheValue" placeholder="Value"> 6963 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 6964 6965 <p ng-if="keys.length">Cached Values</p> 6966 <div ng-repeat="key in keys"> 6967 <span ng-bind="key"></span> 6968 <span>: </span> 6969 <b ng-bind="cache.get(key)"></b> 6970 </div> 6971 6972 <p>Cache Info</p> 6973 <div ng-repeat="(key, value) in cache.info()"> 6974 <span ng-bind="key"></span> 6975 <span>: </span> 6976 <b ng-bind="value"></b> 6977 </div> 6978 </div> 6979 </file> 6980 <file name="script.js"> 6981 angular.module('cacheExampleApp', []). 6982 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 6983 $scope.keys = []; 6984 $scope.cache = $cacheFactory('cacheId'); 6985 $scope.put = function(key, value) { 6986 if (angular.isUndefined($scope.cache.get(key))) { 6987 $scope.keys.push(key); 6988 } 6989 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 6990 }; 6991 }]); 6992 </file> 6993 <file name="style.css"> 6994 p { 6995 margin: 10px 0 3px; 6996 } 6997 </file> 6998 </example> 6999 */ 7000function $CacheFactoryProvider() { 7001 7002 this.$get = function() { 7003 var caches = {}; 7004 7005 function cacheFactory(cacheId, options) { 7006 if (cacheId in caches) { 7007 throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId); 7008 } 7009 7010 var size = 0, 7011 stats = extend({}, options, {id: cacheId}), 7012 data = createMap(), 7013 capacity = (options && options.capacity) || Number.MAX_VALUE, 7014 lruHash = createMap(), 7015 freshEnd = null, 7016 staleEnd = null; 7017 7018 /** 7019 * @ngdoc type 7020 * @name $cacheFactory.Cache 7021 * 7022 * @description 7023 * A cache object used to store and retrieve data, primarily used by 7024 * {@link $templateRequest $templateRequest} and the {@link ng.directive:script script} 7025 * directive to cache templates and other data. 7026 * 7027 * ```js 7028 * angular.module('superCache') 7029 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 7030 * return $cacheFactory('super-cache'); 7031 * }]); 7032 * ``` 7033 * 7034 * Example test: 7035 * 7036 * ```js 7037 * it('should behave like a cache', inject(function(superCache) { 7038 * superCache.put('key', 'value'); 7039 * superCache.put('another key', 'another value'); 7040 * 7041 * expect(superCache.info()).toEqual({ 7042 * id: 'super-cache', 7043 * size: 2 7044 * }); 7045 * 7046 * superCache.remove('another key'); 7047 * expect(superCache.get('another key')).toBeUndefined(); 7048 * 7049 * superCache.removeAll(); 7050 * expect(superCache.info()).toEqual({ 7051 * id: 'super-cache', 7052 * size: 0 7053 * }); 7054 * })); 7055 * ``` 7056 */ 7057 return (caches[cacheId] = { 7058 7059 /** 7060 * @ngdoc method 7061 * @name $cacheFactory.Cache#put 7062 * @kind function 7063 * 7064 * @description 7065 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 7066 * retrieved later, and incrementing the size of the cache if the key was not already 7067 * present in the cache. If behaving like an LRU cache, it will also remove stale 7068 * entries from the set. 7069 * 7070 * It will not insert undefined values into the cache. 7071 * 7072 * @param {string} key the key under which the cached data is stored. 7073 * @param {*} value the value to store alongside the key. If it is undefined, the key 7074 * will not be stored. 7075 * @returns {*} the value stored. 7076 */ 7077 put: function(key, value) { 7078 if (isUndefined(value)) return; 7079 if (capacity < Number.MAX_VALUE) { 7080 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 7081 7082 refresh(lruEntry); 7083 } 7084 7085 if (!(key in data)) size++; 7086 data[key] = value; 7087 7088 if (size > capacity) { 7089 this.remove(staleEnd.key); 7090 } 7091 7092 return value; 7093 }, 7094 7095 /** 7096 * @ngdoc method 7097 * @name $cacheFactory.Cache#get 7098 * @kind function 7099 * 7100 * @description 7101 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 7102 * 7103 * @param {string} key the key of the data to be retrieved 7104 * @returns {*} the value stored. 7105 */ 7106 get: function(key) { 7107 if (capacity < Number.MAX_VALUE) { 7108 var lruEntry = lruHash[key]; 7109 7110 if (!lruEntry) return; 7111 7112 refresh(lruEntry); 7113 } 7114 7115 return data[key]; 7116 }, 7117 7118 7119 /** 7120 * @ngdoc method 7121 * @name $cacheFactory.Cache#remove 7122 * @kind function 7123 * 7124 * @description 7125 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 7126 * 7127 * @param {string} key the key of the entry to be removed 7128 */ 7129 remove: function(key) { 7130 if (capacity < Number.MAX_VALUE) { 7131 var lruEntry = lruHash[key]; 7132 7133 if (!lruEntry) return; 7134 7135 if (lruEntry === freshEnd) freshEnd = lruEntry.p; 7136 if (lruEntry === staleEnd) staleEnd = lruEntry.n; 7137 link(lruEntry.n,lruEntry.p); 7138 7139 delete lruHash[key]; 7140 } 7141 7142 if (!(key in data)) return; 7143 7144 delete data[key]; 7145 size--; 7146 }, 7147 7148 7149 /** 7150 * @ngdoc method 7151 * @name $cacheFactory.Cache#removeAll 7152 * @kind function 7153 * 7154 * @description 7155 * Clears the cache object of any entries. 7156 */ 7157 removeAll: function() { 7158 data = createMap(); 7159 size = 0; 7160 lruHash = createMap(); 7161 freshEnd = staleEnd = null; 7162 }, 7163 7164 7165 /** 7166 * @ngdoc method 7167 * @name $cacheFactory.Cache#destroy 7168 * @kind function 7169 * 7170 * @description 7171 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 7172 * removing it from the {@link $cacheFactory $cacheFactory} set. 7173 */ 7174 destroy: function() { 7175 data = null; 7176 stats = null; 7177 lruHash = null; 7178 delete caches[cacheId]; 7179 }, 7180 7181 7182 /** 7183 * @ngdoc method 7184 * @name $cacheFactory.Cache#info 7185 * @kind function 7186 * 7187 * @description 7188 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 7189 * 7190 * @returns {object} an object with the following properties: 7191 * <ul> 7192 * <li>**id**: the id of the cache instance</li> 7193 * <li>**size**: the number of entries kept in the cache instance</li> 7194 * <li>**...**: any additional properties from the options object when creating the 7195 * cache.</li> 7196 * </ul> 7197 */ 7198 info: function() { 7199 return extend({}, stats, {size: size}); 7200 } 7201 }); 7202 7203 7204 /** 7205 * makes the `entry` the freshEnd of the LRU linked list 7206 */ 7207 function refresh(entry) { 7208 if (entry !== freshEnd) { 7209 if (!staleEnd) { 7210 staleEnd = entry; 7211 } else if (staleEnd === entry) { 7212 staleEnd = entry.n; 7213 } 7214 7215 link(entry.n, entry.p); 7216 link(entry, freshEnd); 7217 freshEnd = entry; 7218 freshEnd.n = null; 7219 } 7220 } 7221 7222 7223 /** 7224 * bidirectionally links two entries of the LRU linked list 7225 */ 7226 function link(nextEntry, prevEntry) { 7227 if (nextEntry !== prevEntry) { 7228 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 7229 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 7230 } 7231 } 7232 } 7233 7234 7235 /** 7236 * @ngdoc method 7237 * @name $cacheFactory#info 7238 * 7239 * @description 7240 * Get information about all the caches that have been created 7241 * 7242 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 7243 */ 7244 cacheFactory.info = function() { 7245 var info = {}; 7246 forEach(caches, function(cache, cacheId) { 7247 info[cacheId] = cache.info(); 7248 }); 7249 return info; 7250 }; 7251 7252 7253 /** 7254 * @ngdoc method 7255 * @name $cacheFactory#get 7256 * 7257 * @description 7258 * Get access to a cache object by the `cacheId` used when it was created. 7259 * 7260 * @param {string} cacheId Name or id of a cache to access. 7261 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 7262 */ 7263 cacheFactory.get = function(cacheId) { 7264 return caches[cacheId]; 7265 }; 7266 7267 7268 return cacheFactory; 7269 }; 7270} 7271 7272/** 7273 * @ngdoc service 7274 * @name $templateCache 7275 * @this 7276 * 7277 * @description 7278 * `$templateCache` is a {@link $cacheFactory.Cache Cache object} created by the 7279 * {@link ng.$cacheFactory $cacheFactory}. 7280 * 7281 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 7282 * can load templates directly into the cache in a `script` tag, by using {@link $templateRequest}, 7283 * or by consuming the `$templateCache` service directly. 7284 * 7285 * Adding via the `script` tag: 7286 * 7287 * ```html 7288 * <script type="text/ng-template" id="templateId.html"> 7289 * <p>This is the content of the template</p> 7290 * </script> 7291 * ``` 7292 * 7293 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 7294 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (e.g. 7295 * element with {@link ngApp} attribute), otherwise the template will be ignored. 7296 * 7297 * Adding via the `$templateCache` service: 7298 * 7299 * ```js 7300 * var myApp = angular.module('myApp', []); 7301 * myApp.run(function($templateCache) { 7302 * $templateCache.put('templateId.html', 'This is the content of the template'); 7303 * }); 7304 * ``` 7305 * 7306 * To retrieve the template later, simply use it in your component: 7307 * ```js 7308 * myApp.component('myComponent', { 7309 * templateUrl: 'templateId.html' 7310 * }); 7311 * ``` 7312 * 7313 * or get it via the `$templateCache` service: 7314 * ```js 7315 * $templateCache.get('templateId.html') 7316 * ``` 7317 * 7318 */ 7319function $TemplateCacheProvider() { 7320 this.$get = ['$cacheFactory', function($cacheFactory) { 7321 return $cacheFactory('templates'); 7322 }]; 7323} 7324 7325/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 7326 * Any commits to this file should be reviewed with security in mind. * 7327 * Changes to this file can potentially create security vulnerabilities. * 7328 * An approval from 2 Core members with history of modifying * 7329 * this file is required. * 7330 * * 7331 * Does the change somehow allow for arbitrary javascript to be executed? * 7332 * Or allows for someone to change the prototype of built-in objects? * 7333 * Or gives undesired access to variables like document or window? * 7334 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 7335 7336/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 7337 * 7338 * DOM-related variables: 7339 * 7340 * - "node" - DOM Node 7341 * - "element" - DOM Element or Node 7342 * - "$node" or "$element" - jqLite-wrapped node or element 7343 * 7344 * 7345 * Compiler related stuff: 7346 * 7347 * - "linkFn" - linking fn of a single directive 7348 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 7349 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 7350 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 7351 */ 7352 7353 7354/** 7355 * @ngdoc service 7356 * @name $compile 7357 * @kind function 7358 * 7359 * @description 7360 * Compiles an HTML string or DOM into a template and produces a template function, which 7361 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 7362 * 7363 * The compilation is a process of walking the DOM tree and matching DOM elements to 7364 * {@link ng.$compileProvider#directive directives}. 7365 * 7366 * <div class="alert alert-warning"> 7367 * **Note:** This document is an in-depth reference of all directive options. 7368 * For a gentle introduction to directives with examples of common use cases, 7369 * see the {@link guide/directive directive guide}. 7370 * </div> 7371 * 7372 * ## Comprehensive Directive API 7373 * 7374 * There are many different options for a directive. 7375 * 7376 * The difference resides in the return value of the factory function. 7377 * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)} 7378 * that defines the directive properties, or just the `postLink` function (all other properties will have 7379 * the default values). 7380 * 7381 * <div class="alert alert-success"> 7382 * **Best Practice:** It's recommended to use the "directive definition object" form. 7383 * </div> 7384 * 7385 * Here's an example directive declared with a Directive Definition Object: 7386 * 7387 * ```js 7388 * var myModule = angular.module(...); 7389 * 7390 * myModule.directive('directiveName', function factory(injectables) { 7391 * var directiveDefinitionObject = { 7392 * {@link $compile#-priority- priority}: 0, 7393 * {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... }, 7394 * // or 7395 * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... }, 7396 * {@link $compile#-transclude- transclude}: false, 7397 * {@link $compile#-restrict- restrict}: 'A', 7398 * {@link $compile#-templatenamespace- templateNamespace}: 'html', 7399 * {@link $compile#-scope- scope}: false, 7400 * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 7401 * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier', 7402 * {@link $compile#-bindtocontroller- bindToController}: false, 7403 * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 7404 * {@link $compile#-multielement- multiElement}: false, 7405 * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) { 7406 * return { 7407 * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, 7408 * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } 7409 * } 7410 * // or 7411 * // return function postLink( ... ) { ... } 7412 * }, 7413 * // or 7414 * // {@link $compile#-link- link}: { 7415 * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, 7416 * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } 7417 * // } 7418 * // or 7419 * // {@link $compile#-link- link}: function postLink( ... ) { ... } 7420 * }; 7421 * return directiveDefinitionObject; 7422 * }); 7423 * ``` 7424 * 7425 * <div class="alert alert-warning"> 7426 * **Note:** Any unspecified options will use the default value. You can see the default values below. 7427 * </div> 7428 * 7429 * Therefore the above can be simplified as: 7430 * 7431 * ```js 7432 * var myModule = angular.module(...); 7433 * 7434 * myModule.directive('directiveName', function factory(injectables) { 7435 * var directiveDefinitionObject = { 7436 * link: function postLink(scope, iElement, iAttrs) { ... } 7437 * }; 7438 * return directiveDefinitionObject; 7439 * // or 7440 * // return function postLink(scope, iElement, iAttrs) { ... } 7441 * }); 7442 * ``` 7443 * 7444 * ### Life-cycle hooks 7445 * Directive controllers can provide the following methods that are called by AngularJS at points in the life-cycle of the 7446 * directive: 7447 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and 7448 * had their bindings initialized (and before the pre & post linking functions for the directives on 7449 * this element). This is a good place to put initialization code for your controller. 7450 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The 7451 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an 7452 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a 7453 * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will 7454 * also be called when your bindings are initialized. 7455 * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on 7456 * changes. Any actions that you wish to take in response to the changes that you detect must be 7457 * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook 7458 * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not 7459 * be detected by AngularJS's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments; 7460 * if detecting changes, you must store the previous value(s) for comparison to the current values. 7461 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing 7462 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in 7463 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent 7464 * components will have their `$onDestroy()` hook called before child components. 7465 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link 7466 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. 7467 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since 7468 * they are waiting for their template to load asynchronously and their own compilation and linking has been 7469 * suspended until that occurs. 7470 * 7471 * #### Comparison with life-cycle hooks in the new Angular 7472 * The new Angular also uses life-cycle hooks for its components. While the AngularJS life-cycle hooks are similar there are 7473 * some differences that you should be aware of, especially when it comes to moving your code from AngularJS to Angular: 7474 * 7475 * * AngularJS hooks are prefixed with `$`, such as `$onInit`. Angular hooks are prefixed with `ng`, such as `ngOnInit`. 7476 * * AngularJS hooks can be defined on the controller prototype or added to the controller inside its constructor. 7477 * In Angular you can only define hooks on the prototype of the Component class. 7478 * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in AngularJS than you would to 7479 * `ngDoCheck` in Angular. 7480 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be 7481 * propagated throughout the application. 7482 * Angular does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an 7483 * error or do nothing depending upon the state of `enableProdMode()`. 7484 * 7485 * #### Life-cycle hook examples 7486 * 7487 * This example shows how you can check for mutations to a Date object even though the identity of the object 7488 * has not changed. 7489 * 7490 * <example name="doCheckDateExample" module="do-check-module"> 7491 * <file name="app.js"> 7492 * angular.module('do-check-module', []) 7493 * .component('app', { 7494 * template: 7495 * 'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' + 7496 * 'Date: {{ $ctrl.date }}' + 7497 * '<test date="$ctrl.date"></test>', 7498 * controller: function() { 7499 * this.date = new Date(); 7500 * this.month = this.date.getMonth(); 7501 * this.updateDate = function() { 7502 * this.date.setMonth(this.month); 7503 * }; 7504 * } 7505 * }) 7506 * .component('test', { 7507 * bindings: { date: '<' }, 7508 * template: 7509 * '<pre>{{ $ctrl.log | json }}</pre>', 7510 * controller: function() { 7511 * var previousValue; 7512 * this.log = []; 7513 * this.$doCheck = function() { 7514 * var currentValue = this.date && this.date.valueOf(); 7515 * if (previousValue !== currentValue) { 7516 * this.log.push('doCheck: date mutated: ' + this.date); 7517 * previousValue = currentValue; 7518 * } 7519 * }; 7520 * } 7521 * }); 7522 * </file> 7523 * <file name="index.html"> 7524 * <app></app> 7525 * </file> 7526 * </example> 7527 * 7528 * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the 7529 * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large 7530 * arrays or objects can have a negative impact on your application performance.) 7531 * 7532 * <example name="doCheckArrayExample" module="do-check-module"> 7533 * <file name="index.html"> 7534 * <div ng-init="items = []"> 7535 * <button ng-click="items.push(items.length)">Add Item</button> 7536 * <button ng-click="items = []">Reset Items</button> 7537 * <pre>{{ items }}</pre> 7538 * <test items="items"></test> 7539 * </div> 7540 * </file> 7541 * <file name="app.js"> 7542 * angular.module('do-check-module', []) 7543 * .component('test', { 7544 * bindings: { items: '<' }, 7545 * template: 7546 * '<pre>{{ $ctrl.log | json }}</pre>', 7547 * controller: function() { 7548 * this.log = []; 7549 * 7550 * this.$doCheck = function() { 7551 * if (this.items_ref !== this.items) { 7552 * this.log.push('doCheck: items changed'); 7553 * this.items_ref = this.items; 7554 * } 7555 * if (!angular.equals(this.items_clone, this.items)) { 7556 * this.log.push('doCheck: items mutated'); 7557 * this.items_clone = angular.copy(this.items); 7558 * } 7559 * }; 7560 * } 7561 * }); 7562 * </file> 7563 * </example> 7564 * 7565 * 7566 * ### Directive Definition Object 7567 * 7568 * The directive definition object provides instructions to the {@link ng.$compile 7569 * compiler}. The attributes are: 7570 * 7571 * #### `multiElement` 7572 * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between 7573 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 7574 * together as the directive elements. It is recommended that this feature be used on directives 7575 * which are not strictly behavioral (such as {@link ngClick}), and which 7576 * do not manipulate or replace child nodes (such as {@link ngInclude}). 7577 * 7578 * #### `priority` 7579 * When there are multiple directives defined on a single DOM element, sometimes it 7580 * is necessary to specify the order in which the directives are applied. The `priority` is used 7581 * to sort the directives before their `compile` functions get called. Priority is defined as a 7582 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 7583 * are also run in priority order, but post-link functions are run in reverse order. The order 7584 * of directives with the same priority is undefined. The default priority is `0`. 7585 * 7586 * #### `terminal` 7587 * If set to true then the current `priority` will be the last set of directives 7588 * which will execute (any directives at the current priority will still execute 7589 * as the order of execution on same `priority` is undefined). Note that expressions 7590 * and other directives used in the directive's template will also be excluded from execution. 7591 * 7592 * #### `scope` 7593 * The scope property can be `false`, `true`, or an object: 7594 * 7595 * * **`false` (default):** No scope will be created for the directive. The directive will use its 7596 * parent's scope. 7597 * 7598 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 7599 * the directive's element. If multiple directives on the same element request a new scope, 7600 * only one new scope is created. 7601 * 7602 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template. 7603 * The 'isolate' scope differs from normal scope in that it does not prototypically 7604 * inherit from its parent scope. This is useful when creating reusable components, which should not 7605 * accidentally read or modify data in the parent scope. Note that an isolate scope 7606 * directive without a `template` or `templateUrl` will not apply the isolate scope 7607 * to its children elements. 7608 * 7609 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 7610 * directive's element. These local properties are useful for aliasing values for templates. The keys in 7611 * the object hash map to the name of the property on the isolate scope; the values define how the property 7612 * is bound to the parent scope, via matching attributes on the directive's element: 7613 * 7614 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 7615 * always a string since DOM attributes are strings. If no `attr` name is specified then the 7616 * attribute name is assumed to be the same as the local name. Given `<my-component 7617 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`, 7618 * the directive's scope property `localName` will reflect the interpolated value of `hello 7619 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's 7620 * scope. The `name` is read from the parent scope (not the directive's scope). 7621 * 7622 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression 7623 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. 7624 * If no `attr` name is specified then the attribute name is assumed to be the same as the local 7625 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { 7626 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the 7627 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in 7628 * `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute 7629 * isn't optional and doesn't exist, an exception 7630 * ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes 7631 * to the local value, since it will be impossible to sync them back to the parent scope. 7632 * 7633 * By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 7634 * method is used for tracking changes, and the equality check is based on object identity. 7635 * However, if an object literal or an array literal is passed as the binding expression, the 7636 * equality check is done by value (using the {@link angular.equals} function). It's also possible 7637 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection 7638 * `$watchCollection`}: use `=*` or `=*attr` 7639 * 7640 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an 7641 * expression passed via the attribute `attr`. The expression is evaluated in the context of the 7642 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the 7643 * local name. 7644 * 7645 * For example, given `<my-component my-attr="parentModel">` and directive definition of 7646 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the 7647 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 7648 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however 7649 * two caveats: 7650 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply 7651 * sets the same value. That means if your bound value is an object, changes to its properties 7652 * in the isolated scope will be reflected in the parent scope (because both reference the same object). 7653 * 2. one-way binding watches changes to the **identity** of the parent value. That means the 7654 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference 7655 * to the value has changed. In most cases, this should not be of concern, but can be important 7656 * to know if you one-way bind to an object, and then replace that object in the isolated scope. 7657 * If you now change a property of the object in your parent scope, the change will not be 7658 * propagated to the isolated scope, because the identity of the object on the parent scope 7659 * has not changed. Instead you must assign a new object. 7660 * 7661 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings 7662 * back to the parent. However, it does not make this completely impossible. 7663 * 7664 * By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 7665 * method is used for tracking changes, and the equality check is based on object identity. 7666 * It's also possible to watch the evaluated value shallowly with 7667 * {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr` 7668 * 7669 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If 7670 * no `attr` name is specified then the attribute name is assumed to be the same as the local name. 7671 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: { 7672 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for 7673 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope 7674 * via an expression to the parent scope. This can be done by passing a map of local variable names 7675 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` 7676 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. 7677 * 7678 * All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression. 7679 * The marker must come after the mode and before the attribute name. 7680 * See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples. 7681 * This is useful to refine the interface directives provide. 7682 * One subtle difference between optional and non-optional happens **when the binding attribute is not 7683 * set**: 7684 * - the binding is optional: the property will not be defined 7685 * - the binding is not optional: the property is defined 7686 * 7687 * ```js 7688 *app.directive('testDir', function() { 7689 return { 7690 scope: { 7691 notoptional: '=', 7692 optional: '=?', 7693 }, 7694 bindToController: true, 7695 controller: function() { 7696 this.$onInit = function() { 7697 console.log(this.hasOwnProperty('notoptional')) // true 7698 console.log(this.hasOwnProperty('optional')) // false 7699 } 7700 } 7701 } 7702 }) 7703 *``` 7704 * 7705 * 7706 * ##### Combining directives with different scope defintions 7707 * 7708 * In general it's possible to apply more than one directive to one element, but there might be limitations 7709 * depending on the type of scope required by the directives. The following points will help explain these limitations. 7710 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 7711 * 7712 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 7713 * * **child scope** + **no scope** => Both directives will share one single child scope 7714 * * **child scope** + **child scope** => Both directives will share one single child scope 7715 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 7716 * its parent's scope 7717 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 7718 * be applied to the same element. 7719 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 7720 * cannot be applied to the same element. 7721 * 7722 * 7723 * #### `bindToController` 7724 * This property is used to bind scope properties directly to the controller. It can be either 7725 * `true` or an object hash with the same format as the `scope` property. 7726 * 7727 * When an isolate scope is used for a directive (see above), `bindToController: true` will 7728 * allow a component to have its properties bound to the controller, rather than to scope. 7729 * 7730 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller 7731 * properties. You can access these bindings once they have been initialized by providing a controller method called 7732 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings 7733 * initialized. 7734 * 7735 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 7736 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 7737 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 7738 * scope (useful for component directives). 7739 * 7740 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 7741 * 7742 * 7743 * #### `controller` 7744 * Controller constructor function. The controller is instantiated before the 7745 * pre-linking phase and can be accessed by other directives (see 7746 * `require` attribute). This allows the directives to communicate with each other and augment 7747 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 7748 * 7749 * * `$scope` - Current scope associated with the element 7750 * * `$element` - Current element 7751 * * `$attrs` - Current attributes object for the element 7752 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 7753 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 7754 * * `scope`: (optional) override the scope. 7755 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 7756 * * `futureParentElement` (optional): 7757 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 7758 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 7759 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 7760 * and when the `cloneLinkingFn` is passed, 7761 * as those elements need to created and cloned in a special way when they are defined outside their 7762 * usual containers (e.g. like `<svg>`). 7763 * * See also the `directive.templateNamespace` property. 7764 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 7765 * then the default transclusion is provided. 7766 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 7767 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 7768 * 7769 * #### `require` 7770 * Require another directive and inject its controller as the fourth argument to the linking function. The 7771 * `require` property can be a string, an array or an object: 7772 * * a **string** containing the name of the directive to pass to the linking function 7773 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the 7774 * linking function will be an array of controllers in the same order as the names in the `require` property 7775 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument 7776 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding 7777 * controllers. 7778 * 7779 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are 7780 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers 7781 * have been constructed but before `$onInit` is called. 7782 * If the name of the required controller is the same as the local name (the key), the name can be 7783 * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`. 7784 * See the {@link $compileProvider#component} helper for an example of how this can be used. 7785 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is 7786 * raised (unless no link function is specified and the required controllers are not being bound to the directive 7787 * controller, in which case error checking is skipped). The name can be prefixed with: 7788 * 7789 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 7790 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 7791 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 7792 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 7793 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 7794 * `null` to the `link` fn if not found. 7795 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 7796 * `null` to the `link` fn if not found. 7797 * 7798 * 7799 * #### `controllerAs` 7800 * Identifier name for a reference to the controller in the directive's scope. 7801 * This allows the controller to be referenced from the directive template. This is especially 7802 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 7803 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 7804 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 7805 * 7806 * 7807 * #### `restrict` 7808 * String of subset of `EACM` which restricts the directive to a specific directive 7809 * declaration style. If omitted, the defaults (elements and attributes) are used. 7810 * 7811 * * `E` - Element name (default): `<my-directive></my-directive>` 7812 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 7813 * * `C` - Class: `<div class="my-directive: exp;"></div>` 7814 * * `M` - Comment: `<!-- directive: my-directive exp -->` 7815 * 7816 * 7817 * #### `templateNamespace` 7818 * String representing the document type used by the markup in the template. 7819 * AngularJS needs this information as those elements need to be created and cloned 7820 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 7821 * 7822 * * `html` - All root nodes in the template are HTML. Root nodes may also be 7823 * top-level elements such as `<svg>` or `<math>`. 7824 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 7825 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 7826 * 7827 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 7828 * 7829 * #### `template` 7830 * HTML markup that may: 7831 * * Replace the contents of the directive's element (default). 7832 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 7833 * * Wrap the contents of the directive's element (if `transclude` is true). 7834 * 7835 * Value may be: 7836 * 7837 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 7838 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 7839 * function api below) and returns a string value. 7840 * 7841 * 7842 * #### `templateUrl` 7843 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 7844 * 7845 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 7846 * for later when the template has been resolved. In the meantime it will continue to compile and link 7847 * sibling and parent elements as though this element had not contained any directives. 7848 * 7849 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 7850 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 7851 * case when only one deeply nested directive has `templateUrl`. 7852 * 7853 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}. 7854 * 7855 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 7856 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 7857 * a string value representing the url. In either case, the template URL is passed through {@link 7858 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 7859 * 7860 * 7861 * #### `replace` 7862 * <div class="alert alert-danger"> 7863 * **Note:** `replace` is deprecated in AngularJS and has been removed in the new Angular (v2+). 7864 * </div> 7865 * 7866 * Specifies what the template should replace. Defaults to `false`. 7867 * 7868 * * `true` - the template will replace the directive's element. 7869 * * `false` - the template will replace the contents of the directive's element. 7870 * 7871 * The replacement process migrates all of the attributes / classes from the old element to the new 7872 * one. See the {@link guide/directive#template-expanding-directive 7873 * Directives Guide} for an example. 7874 * 7875 * There are very few scenarios where element replacement is required for the application function, 7876 * the main one being reusable custom components that are used within SVG contexts 7877 * (because SVG doesn't work with custom elements in the DOM tree). 7878 * 7879 * #### `transclude` 7880 * Extract the contents of the element where the directive appears and make it available to the directive. 7881 * The contents are compiled and provided to the directive as a **transclusion function**. See the 7882 * {@link $compile#transclusion Transclusion} section below. 7883 * 7884 * 7885 * #### `compile` 7886 * 7887 * ```js 7888 * function compile(tElement, tAttrs, transclude) { ... } 7889 * ``` 7890 * 7891 * The compile function deals with transforming the template DOM. Since most directives do not do 7892 * template transformation, it is not used often. The compile function takes the following arguments: 7893 * 7894 * * `tElement` - template element - The element where the directive has been declared. It is 7895 * safe to do template transformation on the element and child elements only. 7896 * 7897 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 7898 * between all directive compile functions. 7899 * 7900 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 7901 * 7902 * <div class="alert alert-warning"> 7903 * **Note:** The template instance and the link instance may be different objects if the template has 7904 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 7905 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 7906 * should be done in a linking function rather than in a compile function. 7907 * </div> 7908 7909 * <div class="alert alert-warning"> 7910 * **Note:** The compile function cannot handle directives that recursively use themselves in their 7911 * own templates or compile functions. Compiling these directives results in an infinite loop and 7912 * stack overflow errors. 7913 * 7914 * This can be avoided by manually using `$compile` in the postLink function to imperatively compile 7915 * a directive's template instead of relying on automatic template compilation via `template` or 7916 * `templateUrl` declaration or manual compilation inside the compile function. 7917 * </div> 7918 * 7919 * <div class="alert alert-danger"> 7920 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 7921 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 7922 * to the link function instead. 7923 * </div> 7924 7925 * A compile function can have a return value which can be either a function or an object. 7926 * 7927 * * returning a (post-link) function - is equivalent to registering the linking function via the 7928 * `link` property of the config object when the compile function is empty. 7929 * 7930 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 7931 * control when a linking function should be called during the linking phase. See info about 7932 * pre-linking and post-linking functions below. 7933 * 7934 * 7935 * #### `link` 7936 * This property is used only if the `compile` property is not defined. 7937 * 7938 * ```js 7939 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 7940 * ``` 7941 * 7942 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 7943 * executed after the template has been cloned. This is where most of the directive logic will be 7944 * put. 7945 * 7946 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 7947 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 7948 * 7949 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 7950 * manipulate the children of the element only in `postLink` function since the children have 7951 * already been linked. 7952 * 7953 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 7954 * between all directive linking functions. 7955 * 7956 * * `controller` - the directive's required controller instance(s) - Instances are shared 7957 * among all directives, which allows the directives to use the controllers as a communication 7958 * channel. The exact value depends on the directive's `require` property: 7959 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 7960 * * `string`: the controller instance 7961 * * `array`: array of controller instances 7962 * 7963 * If a required controller cannot be found, and it is optional, the instance is `null`, 7964 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 7965 * 7966 * Note that you can also require the directive's own controller - it will be made available like 7967 * any other controller. 7968 * 7969 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 7970 * This is the same as the `$transclude` parameter of directive controllers, 7971 * see {@link ng.$compile#-controller- the controller section for details}. 7972 * `function([scope], cloneLinkingFn, futureParentElement)`. 7973 * 7974 * #### Pre-linking function 7975 * 7976 * Executed before the child elements are linked. Not safe to do DOM transformation since the 7977 * compiler linking function will fail to locate the correct elements for linking. 7978 * 7979 * #### Post-linking function 7980 * 7981 * Executed after the child elements are linked. 7982 * 7983 * Note that child elements that contain `templateUrl` directives will not have been compiled 7984 * and linked since they are waiting for their template to load asynchronously and their own 7985 * compilation and linking has been suspended until that occurs. 7986 * 7987 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 7988 * for their async templates to be resolved. 7989 * 7990 * 7991 * ### Transclusion 7992 * 7993 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 7994 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 7995 * scope from where they were taken. 7996 * 7997 * Transclusion is used (often with {@link ngTransclude}) to insert the 7998 * original contents of a directive's element into a specified place in the template of the directive. 7999 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 8000 * content has access to the properties on the scope from which it was taken, even if the directive 8001 * has isolated scope. 8002 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 8003 * 8004 * This makes it possible for the widget to have private state for its template, while the transcluded 8005 * content has access to its originating scope. 8006 * 8007 * <div class="alert alert-warning"> 8008 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 8009 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 8010 * Testing Transclusion Directives}. 8011 * </div> 8012 * 8013 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 8014 * directive's element, the entire element or multiple parts of the element contents: 8015 * 8016 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 8017 * * `'element'` - transclude the whole of the directive's element including any directives on this 8018 * element that are defined at a lower priority than this directive. When used, the `template` 8019 * property is ignored. 8020 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 8021 * 8022 * **Multi-slot transclusion** is declared by providing an object for the `transclude` property. 8023 * 8024 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 8025 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 8026 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 8027 * 8028 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}. 8029 * 8030 * If the element selector is prefixed with a `?` then that slot is optional. 8031 * 8032 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 8033 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 8034 * 8035 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 8036 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 8037 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 8038 * injectable into the directive's controller. 8039 * 8040 * 8041 * #### Transclusion Functions 8042 * 8043 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 8044 * function** to the directive's `link` function and `controller`. This transclusion function is a special 8045 * **linking function** that will return the compiled contents linked to a new transclusion scope. 8046 * 8047 * <div class="alert alert-info"> 8048 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 8049 * ngTransclude will deal with it for us. 8050 * </div> 8051 * 8052 * If you want to manually control the insertion and removal of the transcluded content in your directive 8053 * then you must use this transclude function. When you call a transclude function it returns a jqLite/JQuery 8054 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 8055 * 8056 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 8057 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 8058 * content and the `scope` is the newly created transclusion scope, which the clone will be linked to. 8059 * 8060 * <div class="alert alert-info"> 8061 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 8062 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 8063 * </div> 8064 * 8065 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 8066 * attach function**: 8067 * 8068 * ```js 8069 * var transcludedContent, transclusionScope; 8070 * 8071 * $transclude(function(clone, scope) { 8072 * element.append(clone); 8073 * transcludedContent = clone; 8074 * transclusionScope = scope; 8075 * }); 8076 * ``` 8077 * 8078 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 8079 * associated transclusion scope: 8080 * 8081 * ```js 8082 * transcludedContent.remove(); 8083 * transclusionScope.$destroy(); 8084 * ``` 8085 * 8086 * <div class="alert alert-info"> 8087 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 8088 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 8089 * then you are also responsible for calling `$destroy` on the transclusion scope. 8090 * </div> 8091 * 8092 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 8093 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 8094 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 8095 * 8096 * 8097 * #### Transclusion Scopes 8098 * 8099 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 8100 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 8101 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 8102 * was taken. 8103 * 8104 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 8105 * like this: 8106 * 8107 * ```html 8108 * <div ng-app> 8109 * <div isolate> 8110 * <div transclusion> 8111 * </div> 8112 * </div> 8113 * </div> 8114 * ``` 8115 * 8116 * The `$parent` scope hierarchy will look like this: 8117 * 8118 ``` 8119 - $rootScope 8120 - isolate 8121 - transclusion 8122 ``` 8123 * 8124 * but the scopes will inherit prototypically from different scopes to their `$parent`. 8125 * 8126 ``` 8127 - $rootScope 8128 - transclusion 8129 - isolate 8130 ``` 8131 * 8132 * 8133 * ### Attributes 8134 * 8135 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 8136 * `link()` or `compile()` functions. It has a variety of uses. 8137 * 8138 * * *Accessing normalized attribute names:* Directives like `ngBind` can be expressed in many ways: 8139 * `ng:bind`, `data-ng-bind`, or `x-ng-bind`. The attributes object allows for normalized access 8140 * to the attributes. 8141 * 8142 * * *Directive inter-communication:* All directives share the same instance of the attributes 8143 * object which allows the directives to use the attributes object as inter directive 8144 * communication. 8145 * 8146 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 8147 * allowing other directives to read the interpolated value. 8148 * 8149 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 8150 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 8151 * the only way to easily get the actual value because during the linking phase the interpolation 8152 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 8153 * 8154 * ```js 8155 * function linkingFn(scope, elm, attrs, ctrl) { 8156 * // get the attribute value 8157 * console.log(attrs.ngModel); 8158 * 8159 * // change the attribute 8160 * attrs.$set('ngModel', 'new value'); 8161 * 8162 * // observe changes to interpolated attribute 8163 * attrs.$observe('ngModel', function(value) { 8164 * console.log('ngModel has changed value to ' + value); 8165 * }); 8166 * } 8167 * ``` 8168 * 8169 * ## Example 8170 * 8171 * <div class="alert alert-warning"> 8172 * **Note**: Typically directives are registered with `module.directive`. The example below is 8173 * to illustrate how `$compile` works. 8174 * </div> 8175 * 8176 <example module="compileExample" name="compile"> 8177 <file name="index.html"> 8178 <script> 8179 angular.module('compileExample', [], function($compileProvider) { 8180 // Configure new 'compile' directive by passing a directive 8181 // factory function. The factory function injects '$compile'. 8182 $compileProvider.directive('compile', function($compile) { 8183 // The directive factory creates a link function. 8184 return function(scope, element, attrs) { 8185 scope.$watch( 8186 function(scope) { 8187 // Watch the 'compile' expression for changes. 8188 return scope.$eval(attrs.compile); 8189 }, 8190 function(value) { 8191 // When the 'compile' expression changes 8192 // assign it into the current DOM. 8193 element.html(value); 8194 8195 // Compile the new DOM and link it to the current scope. 8196 // NOTE: we only compile '.childNodes' so that we 8197 // don't get into an infinite loop compiling ourselves. 8198 $compile(element.contents())(scope); 8199 } 8200 ); 8201 }; 8202 }); 8203 }) 8204 .controller('GreeterController', ['$scope', function($scope) { 8205 $scope.name = 'AngularJS'; 8206 $scope.html = 'Hello {{name}}'; 8207 }]); 8208 </script> 8209 <div ng-controller="GreeterController"> 8210 <input ng-model="name"> <br/> 8211 <textarea ng-model="html"></textarea> <br/> 8212 <div compile="html"></div> 8213 </div> 8214 </file> 8215 <file name="protractor.js" type="protractor"> 8216 it('should auto compile', function() { 8217 var textarea = $('textarea'); 8218 var output = $('div[compile]'); 8219 // The initial state reads 'Hello AngularJS'. 8220 expect(output.getText()).toBe('Hello AngularJS'); 8221 textarea.clear(); 8222 textarea.sendKeys('{{name}}!'); 8223 expect(output.getText()).toBe('AngularJS!'); 8224 }); 8225 </file> 8226 </example> 8227 8228 * 8229 * 8230 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 8231 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 8232 * 8233 * <div class="alert alert-danger"> 8234 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 8235 * e.g. will not use the right outer scope. Please pass the transclude function as a 8236 * `parentBoundTranscludeFn` to the link function instead. 8237 * </div> 8238 * 8239 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 8240 * root element(s), not their children) 8241 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 8242 * (a DOM element/tree) to a scope. Where: 8243 * 8244 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 8245 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 8246 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 8247 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 8248 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 8249 * 8250 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 8251 * * `scope` - is the current scope with which the linking function is working with. 8252 * 8253 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 8254 * keys may be used to control linking behavior: 8255 * 8256 * * `parentBoundTranscludeFn` - the transclude function made available to 8257 * directives; if given, it will be passed through to the link functions of 8258 * directives found in `element` during compilation. 8259 * * `transcludeControllers` - an object hash with keys that map controller names 8260 * to a hash with the key `instance`, which maps to the controller instance; 8261 * if given, it will make the controllers available to directives on the compileNode: 8262 * ``` 8263 * { 8264 * parent: { 8265 * instance: parentControllerInstance 8266 * } 8267 * } 8268 * ``` 8269 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 8270 * the cloned elements; only needed for transcludes that are allowed to contain non HTML 8271 * elements (e.g. SVG elements). See also the `directive.controller` property. 8272 * 8273 * Calling the linking function returns the element of the template. It is either the original 8274 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 8275 * 8276 * After linking the view is not updated until after a call to `$digest`, which typically is done by 8277 * AngularJS automatically. 8278 * 8279 * If you need access to the bound view, there are two ways to do it: 8280 * 8281 * - If you are not asking the linking function to clone the template, create the DOM element(s) 8282 * before you send them to the compiler and keep this reference around. 8283 * ```js 8284 * var element = angular.element('<p>{{total}}</p>'); 8285 * $compile(element)(scope); 8286 * ``` 8287 * 8288 * - if on the other hand, you need the element to be cloned, the view reference from the original 8289 * example would not point to the clone, but rather to the original template that was cloned. In 8290 * this case, you can access the clone either via the `cloneAttachFn` or the value returned by the 8291 * linking function: 8292 * ```js 8293 * var templateElement = angular.element('<p>{{total}}</p>'); 8294 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 8295 * // Attach the clone to DOM document at the right place. 8296 * }); 8297 * 8298 * // Now we have reference to the cloned DOM via `clonedElement`. 8299 * // NOTE: The `clonedElement` returned by the linking function is the same as the 8300 * // `clonedElement` passed to `cloneAttachFn`. 8301 * ``` 8302 * 8303 * 8304 * For information on how the compiler works, see the 8305 * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide. 8306 * 8307 * @knownIssue 8308 * 8309 * ### Double Compilation 8310 * 8311 Double compilation occurs when an already compiled part of the DOM gets 8312 compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, 8313 and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it 8314 section on double compilation} for an in-depth explanation and ways to avoid it. 8315 8316 * @knownIssue 8317 8318 ### Issues with `replace: true` 8319 * 8320 * <div class="alert alert-danger"> 8321 * **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use, 8322 * mainly due to the issues listed here. It has been completely removed in the new Angular. 8323 * </div> 8324 * 8325 * #### Attribute values are not merged 8326 * 8327 * When a `replace` directive encounters the same attribute on the original and the replace node, 8328 * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of 8329 * the `style` attribute. 8330 * ```html 8331 * Original Node: <span class="original" style="color: red;"></span> 8332 * Replace Template: <span class="replaced" style="background: blue;"></span> 8333 * Result: <span class="original replaced" style="color: red; background: blue;"></span> 8334 * ``` 8335 * 8336 * That means attributes that contain AngularJS expressions will not be merged correctly, e.g. 8337 * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error: 8338 * 8339 * ```html 8340 * Original Node: <span ng-class="{'something': something}" ng-show="!condition"></span> 8341 * Replace Template: <span ng-class="{'else': else}" ng-show="otherCondition"></span> 8342 * Result: <span ng-class="{'something': something} {'else': else}" ng-show="!condition otherCondition"></span> 8343 * ``` 8344 * 8345 * See issue [#5695](https://github.com/angular/angular.js/issues/5695). 8346 * 8347 * #### Directives are not deduplicated before compilation 8348 * 8349 * When the original node and the replace template declare the same directive(s), they will be 8350 * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler 8351 * does not deduplicate them. In many cases, this is not noticeable, but e.g. {@link ngModel} will 8352 * attach `$formatters` and `$parsers` twice. 8353 * 8354 * See issue [#2573](https://github.com/angular/angular.js/issues/2573). 8355 * 8356 * #### `transclude: element` in the replace template root can have unexpected effects 8357 * 8358 * When the replace template has a directive at the root node that uses 8359 * {@link $compile#-transclude- `transclude: element`}, e.g. 8360 * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect. 8361 * See the following issues: 8362 * 8363 * - Incorrect scope on replaced element: 8364 * [#9837](https://github.com/angular/angular.js/issues/9837) 8365 * - Different DOM between `template` and `templateUrl`: 8366 * [#10612](https://github.com/angular/angular.js/issues/14326) 8367 * 8368 */ 8369 8370/** 8371 * @ngdoc directive 8372 * @name ngProp 8373 * @restrict A 8374 * @element ANY 8375 * 8376 * @usage 8377 * 8378 * ```html 8379 * <ANY ng-prop-propname="expression"> 8380 * </ANY> 8381 * ``` 8382 * 8383 * or with uppercase letters in property (e.g. "propName"): 8384 * 8385 * 8386 * ```html 8387 * <ANY ng-prop-prop_name="expression"> 8388 * </ANY> 8389 * ``` 8390 * 8391 * 8392 * @description 8393 * The `ngProp` directive binds an expression to a DOM element property. 8394 * `ngProp` allows writing to arbitrary properties by including 8395 * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to 8396 * the `value` property. 8397 * 8398 * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives 8399 * handle the most common use cases (instead of the above example, you would use {@link ngValue}). 8400 * 8401 * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements) 8402 * often use custom properties to hold data, and `ngProp` can be used to provide input to these 8403 * custom elements. 8404 * 8405 * ## Binding to camelCase properties 8406 * 8407 * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped. 8408 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so 8409 * `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an 8410 * example, and for binding HTML {@link ngBindHtml} should be used. 8411 * 8412 * ## Security 8413 * 8414 * Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML` 8415 * can insert potentially dangerous HTML into the application, e.g. script tags that execute 8416 * malicious code. 8417 * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}. 8418 * This means vulnerable properties require their content to be "trusted", based on the 8419 * context of the property. For example, the `innerHTML` is in the `HTML` context, and the 8420 * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to 8421 * this property are trusted as a `RESOURCE_URL`. 8422 * 8423 * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is 8424 * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for 8425 * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al. 8426 * 8427 * In some cases you can also rely upon automatic sanitization of untrusted values - see below. 8428 * 8429 * Based on the context, other options may exist to mark a value as trusted / configure the behavior 8430 * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use 8431 * the {@link $sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList()} 8432 * and {@link $sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList()}. 8433 * 8434 * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}. 8435 * 8436 * ### HTML Sanitization 8437 * 8438 * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the 8439 * content. 8440 * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the 8441 * potentially dangerous HTML, e.g. strip non-trusted tags and attributes when binding to 8442 * `innerHTML`. 8443 * 8444 * @example 8445 * ### Binding to different contexts 8446 * 8447 * <example name="ngProp" module="exampleNgProp"> 8448 * <file name="app.js"> 8449 * angular.module('exampleNgProp', []) 8450 * .component('main', { 8451 * templateUrl: 'main.html', 8452 * controller: function($sce) { 8453 * this.safeContent = '<strong>Safe content</strong>'; 8454 * this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>'; 8455 * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); 8456 * } 8457 * }); 8458 * </file> 8459 * <file name="main.html"> 8460 * <div> 8461 * <div class="prop-unit"> 8462 * Binding to a property without security context: 8463 * <div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div> 8464 * <span class="prop-note">innerText</span> (safeContent) 8465 * </div> 8466 * 8467 * <div class="prop-unit"> 8468 * "Safe" content that requires a security context will throw because the contents could potentially be dangerous ... 8469 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div> 8470 * <span class="prop-note">innerHTML</span> (safeContent) 8471 * </div> 8472 * 8473 * <div class="prop-unit"> 8474 * ... so that actually dangerous content cannot be executed: 8475 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div> 8476 * <span class="prop-note">innerHTML</span> (unsafeContent) 8477 * </div> 8478 * 8479 * <div class="prop-unit"> 8480 * ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure! 8481 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div> 8482 * <span class="prop-note">innerHTML</span> (trustedUnsafeContent) 8483 * </div> 8484 * </div> 8485 * </file> 8486 * <file name="index.html"> 8487 * <main></main> 8488 * </file> 8489 * <file name="styles.css"> 8490 * .prop-unit { 8491 * margin-bottom: 10px; 8492 * } 8493 * 8494 * .prop-binding { 8495 * min-height: 30px; 8496 * border: 1px solid blue; 8497 * } 8498 * 8499 * .prop-note { 8500 * font-family: Monospace; 8501 * } 8502 * </file> 8503 * </example> 8504 * 8505 * 8506 * @example 8507 * ### Binding to innerHTML with ngSanitize 8508 * 8509 * <example name="ngProp" module="exampleNgProp" deps="angular-sanitize.js"> 8510 * <file name="app.js"> 8511 * angular.module('exampleNgProp', ['ngSanitize']) 8512 * .component('main', { 8513 * templateUrl: 'main.html', 8514 * controller: function($sce) { 8515 * this.safeContent = '<strong>Safe content</strong>'; 8516 * this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>'; 8517 * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); 8518 * } 8519 * }); 8520 * </file> 8521 * <file name="main.html"> 8522 * <div> 8523 * <div class="prop-unit"> 8524 * "Safe" content will be sanitized ... 8525 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div> 8526 * <span class="prop-note">innerHTML</span> (safeContent) 8527 * </div> 8528 * 8529 * <div class="prop-unit"> 8530 * ... as will dangerous content: 8531 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div> 8532 * <span class="prop-note">innerHTML</span> (unsafeContent) 8533 * </div> 8534 * 8535 * <div class="prop-unit"> 8536 * ... and content that has been trusted explicitly works the same as without ngSanitize: 8537 * <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div> 8538 * <span class="prop-note">innerHTML</span> (trustedUnsafeContent) 8539 * </div> 8540 * </div> 8541 * </file> 8542 * <file name="index.html"> 8543 * <main></main> 8544 * </file> 8545 * <file name="styles.css"> 8546 * .prop-unit { 8547 * margin-bottom: 10px; 8548 * } 8549 * 8550 * .prop-binding { 8551 * min-height: 30px; 8552 * border: 1px solid blue; 8553 * } 8554 * 8555 * .prop-note { 8556 * font-family: Monospace; 8557 * } 8558 * </file> 8559 * </example> 8560 * 8561 */ 8562 8563/** @ngdoc directive 8564 * @name ngOn 8565 * @restrict A 8566 * @element ANY 8567 * 8568 * @usage 8569 * 8570 * ```html 8571 * <ANY ng-on-eventname="expression"> 8572 * </ANY> 8573 * ``` 8574 * 8575 * or with uppercase letters in property (e.g. "eventName"): 8576 * 8577 * 8578 * ```html 8579 * <ANY ng-on-event_name="expression"> 8580 * </ANY> 8581 * ``` 8582 * 8583 * @description 8584 * The `ngOn` directive adds an event listener to a DOM element via 8585 * {@link angular.element angular.element().on()}, and evaluates an expression when the event is 8586 * fired. 8587 * `ngOn` allows adding listeners for arbitrary events by including 8588 * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression 8589 * when the `drop` event is fired. 8590 * 8591 * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most 8592 * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events 8593 * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM 8594 * standards. 8595 * 8596 * Another use-case for `ngOn` is listening to 8597 * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) 8598 * fired by 8599 * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements). 8600 * 8601 * ## Binding to camelCase properties 8602 * 8603 * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped. 8604 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so 8605 * `myEvent` must be written as `ng-on-my_event="expression"`. 8606 * 8607 * @example 8608 * ### Bind to built-in DOM events 8609 * 8610 * <example name="ngOn" module="exampleNgOn"> 8611 * <file name="app.js"> 8612 * angular.module('exampleNgOn', []) 8613 * .component('main', { 8614 * templateUrl: 'main.html', 8615 * controller: function() { 8616 * this.clickCount = 0; 8617 * this.mouseoverCount = 0; 8618 * 8619 * this.loadingState = 0; 8620 * } 8621 * }); 8622 * </file> 8623 * <file name="main.html"> 8624 * <div> 8625 * This is equivalent to `ngClick` and `ngMouseover`:<br> 8626 * <button 8627 * ng-on-click="$ctrl.clickCount = $ctrl.clickCount + 1" 8628 * ng-on-mouseover="$ctrl.mouseoverCount = $ctrl.mouseoverCount + 1">Click or mouseover</button><br> 8629 * clickCount: {{$ctrl.clickCount}}<br> 8630 * mouseover: {{$ctrl.mouseoverCount}} 8631 * 8632 * <hr> 8633 * 8634 * For the `error` and `load` event on images no built-in AngularJS directives exist:<br> 8635 * <img src="thisimagedoesnotexist.png" ng-on-error="$ctrl.loadingState = -1" ng-on-load="$ctrl.loadingState = 1"><br> 8636 * <div ng-switch="$ctrl.loadingState"> 8637 * <span ng-switch-when="0">Image is loading</span> 8638 * <span ng-switch-when="-1">Image load error</span> 8639 * <span ng-switch-when="1">Image loaded successfully</span> 8640 * </div> 8641 * </div> 8642 * </file> 8643 * <file name="index.html"> 8644 * <main></main> 8645 * </file> 8646 * </example> 8647 * 8648 * 8649 * @example 8650 * ### Bind to custom DOM events 8651 * 8652 * <example name="ngOnCustom" module="exampleNgOn"> 8653 * <file name="app.js"> 8654 * angular.module('exampleNgOn', []) 8655 * .component('main', { 8656 * templateUrl: 'main.html', 8657 * controller: function() { 8658 * this.eventLog = ''; 8659 * 8660 * this.listener = function($event) { 8661 * this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail; 8662 * }; 8663 * } 8664 * }) 8665 * .component('childComponent', { 8666 * templateUrl: 'child.html', 8667 * controller: function($element) { 8668 * this.fireEvent = function() { 8669 * var event = new CustomEvent('customtype', { detail: new Date()}); 8670 * 8671 * $element[0].dispatchEvent(event); 8672 * }; 8673 * } 8674 * }); 8675 * </file> 8676 * <file name="main.html"> 8677 * <child-component ng-on-customtype="$ctrl.listener($event)"></child-component><br> 8678 * <span>Event log: {{$ctrl.eventLog}}</span> 8679 * </file> 8680 * <file name="child.html"> 8681 <button ng-click="$ctrl.fireEvent()">Fire custom event</button> 8682 * </file> 8683 * <file name="index.html"> 8684 * <main></main> 8685 * </file> 8686 * </example> 8687 */ 8688 8689var $compileMinErr = minErr('$compile'); 8690 8691function UNINITIALIZED_VALUE() {} 8692var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); 8693 8694/** 8695 * @ngdoc provider 8696 * @name $compileProvider 8697 * 8698 * @description 8699 */ 8700$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 8701/** @this */ 8702function $CompileProvider($provide, $$sanitizeUriProvider) { 8703 var hasDirectives = {}, 8704 Suffix = 'Directive', 8705 COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, 8706 CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, 8707 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 8708 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 8709 8710 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 8711 // The assumption is that future DOM event attribute names will begin with 8712 // 'on' and be composed of only English letters. 8713 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 8714 var bindingCache = createMap(); 8715 8716 function parseIsolateBindings(scope, directiveName, isController) { 8717 var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/; 8718 8719 var bindings = createMap(); 8720 8721 forEach(scope, function(definition, scopeName) { 8722 definition = definition.trim(); 8723 8724 if (definition in bindingCache) { 8725 bindings[scopeName] = bindingCache[definition]; 8726 return; 8727 } 8728 var match = definition.match(LOCAL_REGEXP); 8729 8730 if (!match) { 8731 throw $compileMinErr('iscp', 8732 'Invalid {3} for directive \'{0}\'.' + 8733 ' Definition: {... {1}: \'{2}\' ...}', 8734 directiveName, scopeName, definition, 8735 (isController ? 'controller bindings definition' : 8736 'isolate scope definition')); 8737 } 8738 8739 bindings[scopeName] = { 8740 mode: match[1][0], 8741 collection: match[2] === '*', 8742 optional: match[3] === '?', 8743 attrName: match[4] || scopeName 8744 }; 8745 if (match[4]) { 8746 bindingCache[definition] = bindings[scopeName]; 8747 } 8748 }); 8749 8750 return bindings; 8751 } 8752 8753 function parseDirectiveBindings(directive, directiveName) { 8754 var bindings = { 8755 isolateScope: null, 8756 bindToController: null 8757 }; 8758 if (isObject(directive.scope)) { 8759 if (directive.bindToController === true) { 8760 bindings.bindToController = parseIsolateBindings(directive.scope, 8761 directiveName, true); 8762 bindings.isolateScope = {}; 8763 } else { 8764 bindings.isolateScope = parseIsolateBindings(directive.scope, 8765 directiveName, false); 8766 } 8767 } 8768 if (isObject(directive.bindToController)) { 8769 bindings.bindToController = 8770 parseIsolateBindings(directive.bindToController, directiveName, true); 8771 } 8772 if (bindings.bindToController && !directive.controller) { 8773 // There is no controller 8774 throw $compileMinErr('noctrl', 8775 'Cannot bind to controller without directive \'{0}\'s controller.', 8776 directiveName); 8777 } 8778 return bindings; 8779 } 8780 8781 function assertValidDirectiveName(name) { 8782 var letter = name.charAt(0); 8783 if (!letter || letter !== lowercase(letter)) { 8784 throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name); 8785 } 8786 if (name !== name.trim()) { 8787 throw $compileMinErr('baddir', 8788 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces', 8789 name); 8790 } 8791 } 8792 8793 function getDirectiveRequire(directive) { 8794 var require = directive.require || (directive.controller && directive.name); 8795 8796 if (!isArray(require) && isObject(require)) { 8797 forEach(require, function(value, key) { 8798 var match = value.match(REQUIRE_PREFIX_REGEXP); 8799 var name = value.substring(match[0].length); 8800 if (!name) require[key] = match[0] + key; 8801 }); 8802 } 8803 8804 return require; 8805 } 8806 8807 function getDirectiveRestrict(restrict, name) { 8808 if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) { 8809 throw $compileMinErr('badrestrict', 8810 'Restrict property \'{0}\' of directive \'{1}\' is invalid', 8811 restrict, 8812 name); 8813 } 8814 8815 return restrict || 'EA'; 8816 } 8817 8818 /** 8819 * @ngdoc method 8820 * @name $compileProvider#directive 8821 * @kind function 8822 * 8823 * @description 8824 * Register a new directive with the compiler. 8825 * 8826 * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match 8827 * as `ng-bind`), or an object map of directives where the keys are the names and the values 8828 * are the factories. 8829 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 8830 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 8831 * @returns {ng.$compileProvider} Self for chaining. 8832 */ 8833 this.directive = function registerDirective(name, directiveFactory) { 8834 assertArg(name, 'name'); 8835 assertNotHasOwnProperty(name, 'directive'); 8836 if (isString(name)) { 8837 assertValidDirectiveName(name); 8838 assertArg(directiveFactory, 'directiveFactory'); 8839 if (!hasDirectives.hasOwnProperty(name)) { 8840 hasDirectives[name] = []; 8841 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 8842 function($injector, $exceptionHandler) { 8843 var directives = []; 8844 forEach(hasDirectives[name], function(directiveFactory, index) { 8845 try { 8846 var directive = $injector.invoke(directiveFactory); 8847 if (isFunction(directive)) { 8848 directive = { compile: valueFn(directive) }; 8849 } else if (!directive.compile && directive.link) { 8850 directive.compile = valueFn(directive.link); 8851 } 8852 directive.priority = directive.priority || 0; 8853 directive.index = index; 8854 directive.name = directive.name || name; 8855 directive.require = getDirectiveRequire(directive); 8856 directive.restrict = getDirectiveRestrict(directive.restrict, name); 8857 directive.$$moduleName = directiveFactory.$$moduleName; 8858 directives.push(directive); 8859 } catch (e) { 8860 $exceptionHandler(e); 8861 } 8862 }); 8863 return directives; 8864 }]); 8865 } 8866 hasDirectives[name].push(directiveFactory); 8867 } else { 8868 forEach(name, reverseParams(registerDirective)); 8869 } 8870 return this; 8871 }; 8872 8873 /** 8874 * @ngdoc method 8875 * @name $compileProvider#component 8876 * @module ng 8877 * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`), 8878 * or an object map of components where the keys are the names and the values are the component definition objects. 8879 * @param {Object} options Component definition object (a simplified 8880 * {@link ng.$compile#directive-definition-object directive definition object}), 8881 * with the following properties (all optional): 8882 * 8883 * - `controller` – `{(string|function()=}` – controller constructor function that should be 8884 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 8885 * registered controller} if passed as a string. An empty `noop` function by default. 8886 * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope. 8887 * If present, the controller will be published to scope under the `controllerAs` name. 8888 * If not present, this will default to be `$ctrl`. 8889 * - `template` – `{string=|function()=}` – html template as a string or a function that 8890 * returns an html template as a string which should be used as the contents of this component. 8891 * Empty string by default. 8892 * 8893 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 8894 * the following locals: 8895 * 8896 * - `$element` - Current element 8897 * - `$attrs` - Current attributes object for the element 8898 * 8899 * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 8900 * template that should be used as the contents of this component. 8901 * 8902 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 8903 * the following locals: 8904 * 8905 * - `$element` - Current element 8906 * - `$attrs` - Current attributes object for the element 8907 * 8908 * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties. 8909 * Component properties are always bound to the component controller and not to the scope. 8910 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 8911 * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled. 8912 * Disabled by default. 8913 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to 8914 * this component's controller. The object keys specify the property names under which the required 8915 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. 8916 * - `$...` – additional properties to attach to the directive factory function and the controller 8917 * constructor function. (This is used by the component router to annotate) 8918 * 8919 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 8920 * @description 8921 * Register a **component definition** with the compiler. This is a shorthand for registering a special 8922 * type of directive, which represents a self-contained UI component in your application. Such components 8923 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 8924 * 8925 * Component definitions are very simple and do not require as much configuration as defining general 8926 * directives. Component definitions usually consist only of a template and a controller backing it. 8927 * 8928 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 8929 * `bindToController`. They always have **isolate scope** and are restricted to elements. 8930 * 8931 * Here are a few examples of how you would usually define components: 8932 * 8933 * ```js 8934 * var myMod = angular.module(...); 8935 * myMod.component('myComp', { 8936 * template: '<div>My name is {{$ctrl.name}}</div>', 8937 * controller: function() { 8938 * this.name = 'shahar'; 8939 * } 8940 * }); 8941 * 8942 * myMod.component('myComp', { 8943 * template: '<div>My name is {{$ctrl.name}}</div>', 8944 * bindings: {name: '@'} 8945 * }); 8946 * 8947 * myMod.component('myComp', { 8948 * templateUrl: 'views/my-comp.html', 8949 * controller: 'MyCtrl', 8950 * controllerAs: 'ctrl', 8951 * bindings: {name: '@'} 8952 * }); 8953 * 8954 * ``` 8955 * For more examples, and an in-depth guide, see the {@link guide/component component guide}. 8956 * 8957 * <br /> 8958 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 8959 */ 8960 this.component = function registerComponent(name, options) { 8961 if (!isString(name)) { 8962 forEach(name, reverseParams(bind(this, registerComponent))); 8963 return this; 8964 } 8965 8966 var controller = options.controller || function() {}; 8967 8968 function factory($injector) { 8969 function makeInjectable(fn) { 8970 if (isFunction(fn) || isArray(fn)) { 8971 return /** @this */ function(tElement, tAttrs) { 8972 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 8973 }; 8974 } else { 8975 return fn; 8976 } 8977 } 8978 8979 var template = (!options.template && !options.templateUrl ? '' : options.template); 8980 var ddo = { 8981 controller: controller, 8982 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 8983 template: makeInjectable(template), 8984 templateUrl: makeInjectable(options.templateUrl), 8985 transclude: options.transclude, 8986 scope: {}, 8987 bindToController: options.bindings || {}, 8988 restrict: 'E', 8989 require: options.require 8990 }; 8991 8992 // Copy annotations (starting with $) over to the DDO 8993 forEach(options, function(val, key) { 8994 if (key.charAt(0) === '$') ddo[key] = val; 8995 }); 8996 8997 return ddo; 8998 } 8999 9000 // TODO(pete) remove the following `forEach` before we release 1.6.0 9001 // The [email protected] looks for the annotations on the controller constructor 9002 // Nothing in AngularJS looks for annotations on the factory function but we can't remove 9003 // it from 1.5.x yet. 9004 9005 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions 9006 // These could be used by libraries such as the new component router 9007 forEach(options, function(val, key) { 9008 if (key.charAt(0) === '$') { 9009 factory[key] = val; 9010 // Don't try to copy over annotations to named controller 9011 if (isFunction(controller)) controller[key] = val; 9012 } 9013 }); 9014 9015 factory.$inject = ['$injector']; 9016 9017 return this.directive(name, factory); 9018 }; 9019 9020 9021 /** 9022 * @ngdoc method 9023 * @name $compileProvider#aHrefSanitizationTrustedUrlList 9024 * @kind function 9025 * 9026 * @description 9027 * Retrieves or overrides the default regular expression that is used for determining trusted safe 9028 * urls during a[href] sanitization. 9029 * 9030 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 9031 * 9032 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 9033 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationTrustedUrlList` 9034 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 9035 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 9036 * 9037 * @param {RegExp=} regexp New regexp to trust urls with. 9038 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 9039 * chaining otherwise. 9040 */ 9041 this.aHrefSanitizationTrustedUrlList = function(regexp) { 9042 if (isDefined(regexp)) { 9043 $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(regexp); 9044 return this; 9045 } else { 9046 return $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(); 9047 } 9048 }; 9049 9050 9051 /** 9052 * @ngdoc method 9053 * @name $compileProvider#aHrefSanitizationWhitelist 9054 * @kind function 9055 * 9056 * @deprecated 9057 * sinceVersion="1.8.1" 9058 * 9059 * This method is deprecated. Use {@link $compileProvider#aHrefSanitizationTrustedUrlList 9060 * aHrefSanitizationTrustedUrlList} instead. 9061 */ 9062 Object.defineProperty(this, 'aHrefSanitizationWhitelist', { 9063 get: function() { 9064 return this.aHrefSanitizationTrustedUrlList; 9065 }, 9066 set: function(value) { 9067 this.aHrefSanitizationTrustedUrlList = value; 9068 } 9069 }); 9070 9071 9072 /** 9073 * @ngdoc method 9074 * @name $compileProvider#imgSrcSanitizationTrustedUrlList 9075 * @kind function 9076 * 9077 * @description 9078 * Retrieves or overrides the default regular expression that is used for determining trusted safe 9079 * urls during img[src] sanitization. 9080 * 9081 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 9082 * 9083 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 9084 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationTrustedUrlList` 9085 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 9086 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 9087 * 9088 * @param {RegExp=} regexp New regexp to trust urls with. 9089 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 9090 * chaining otherwise. 9091 */ 9092 this.imgSrcSanitizationTrustedUrlList = function(regexp) { 9093 if (isDefined(regexp)) { 9094 $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(regexp); 9095 return this; 9096 } else { 9097 return $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(); 9098 } 9099 }; 9100 9101 9102 /** 9103 * @ngdoc method 9104 * @name $compileProvider#imgSrcSanitizationWhitelist 9105 * @kind function 9106 * 9107 * @deprecated 9108 * sinceVersion="1.8.1" 9109 * 9110 * This method is deprecated. Use {@link $compileProvider#imgSrcSanitizationTrustedUrlList 9111 * imgSrcSanitizationTrustedUrlList} instead. 9112 */ 9113 Object.defineProperty(this, 'imgSrcSanitizationWhitelist', { 9114 get: function() { 9115 return this.imgSrcSanitizationTrustedUrlList; 9116 }, 9117 set: function(value) { 9118 this.imgSrcSanitizationTrustedUrlList = value; 9119 } 9120 }); 9121 9122 /** 9123 * @ngdoc method 9124 * @name $compileProvider#debugInfoEnabled 9125 * 9126 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 9127 * current debugInfoEnabled state 9128 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9129 * 9130 * @kind function 9131 * 9132 * @description 9133 * Call this method to enable/disable various debug runtime information in the compiler such as adding 9134 * binding information and a reference to the current scope on to DOM elements. 9135 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 9136 * * `ng-binding` CSS class 9137 * * `ng-scope` and `ng-isolated-scope` CSS classes 9138 * * `$binding` data property containing an array of the binding expressions 9139 * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return 9140 * the element's scope. 9141 * * Placeholder comments will contain information about what directive and binding caused the placeholder. 9142 * E.g. `<!-- ngIf: shouldShow() -->`. 9143 * 9144 * You may want to disable this in production for a significant performance boost. See 9145 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 9146 * 9147 * The default value is true. 9148 */ 9149 var debugInfoEnabled = true; 9150 this.debugInfoEnabled = function(enabled) { 9151 if (isDefined(enabled)) { 9152 debugInfoEnabled = enabled; 9153 return this; 9154 } 9155 return debugInfoEnabled; 9156 }; 9157 9158 /** 9159 * @ngdoc method 9160 * @name $compileProvider#strictComponentBindingsEnabled 9161 * 9162 * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, 9163 * otherwise return the current strictComponentBindingsEnabled state. 9164 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9165 * 9166 * @kind function 9167 * 9168 * @description 9169 * Call this method to enable / disable the strict component bindings check. If enabled, the 9170 * compiler will enforce that all scope / controller bindings of a 9171 * {@link $compileProvider#directive directive} / {@link $compileProvider#component component} 9172 * that are not set as optional with `?`, must be provided when the directive is instantiated. 9173 * If not provided, the compiler will throw the 9174 * {@link error/$compile/missingattr $compile:missingattr error}. 9175 * 9176 * The default value is false. 9177 */ 9178 var strictComponentBindingsEnabled = false; 9179 this.strictComponentBindingsEnabled = function(enabled) { 9180 if (isDefined(enabled)) { 9181 strictComponentBindingsEnabled = enabled; 9182 return this; 9183 } 9184 return strictComponentBindingsEnabled; 9185 }; 9186 9187 var TTL = 10; 9188 /** 9189 * @ngdoc method 9190 * @name $compileProvider#onChangesTtl 9191 * @description 9192 * 9193 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and 9194 * assuming that the model is unstable. 9195 * 9196 * The current default is 10 iterations. 9197 * 9198 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result 9199 * in several iterations of calls to these hooks. However if an application needs more than the default 10 9200 * iterations to stabilize then you should investigate what is causing the model to continuously change during 9201 * the `$onChanges` hook execution. 9202 * 9203 * Increasing the TTL could have performance implications, so you should not change it without proper justification. 9204 * 9205 * @param {number} limit The number of `$onChanges` hook iterations. 9206 * @returns {number|object} the current limit (or `this` if called as a setter for chaining) 9207 */ 9208 this.onChangesTtl = function(value) { 9209 if (arguments.length) { 9210 TTL = value; 9211 return this; 9212 } 9213 return TTL; 9214 }; 9215 9216 var commentDirectivesEnabledConfig = true; 9217 /** 9218 * @ngdoc method 9219 * @name $compileProvider#commentDirectivesEnabled 9220 * @description 9221 * 9222 * It indicates to the compiler 9223 * whether or not directives on comments should be compiled. 9224 * Defaults to `true`. 9225 * 9226 * Calling this function with false disables the compilation of directives 9227 * on comments for the whole application. 9228 * This results in a compilation performance gain, 9229 * as the compiler doesn't have to check comments when looking for directives. 9230 * This should however only be used if you are sure that no comment directives are used in 9231 * the application (including any 3rd party directives). 9232 * 9233 * @param {boolean} enabled `false` if the compiler may ignore directives on comments 9234 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) 9235 */ 9236 this.commentDirectivesEnabled = function(value) { 9237 if (arguments.length) { 9238 commentDirectivesEnabledConfig = value; 9239 return this; 9240 } 9241 return commentDirectivesEnabledConfig; 9242 }; 9243 9244 9245 var cssClassDirectivesEnabledConfig = true; 9246 /** 9247 * @ngdoc method 9248 * @name $compileProvider#cssClassDirectivesEnabled 9249 * @description 9250 * 9251 * It indicates to the compiler 9252 * whether or not directives on element classes should be compiled. 9253 * Defaults to `true`. 9254 * 9255 * Calling this function with false disables the compilation of directives 9256 * on element classes for the whole application. 9257 * This results in a compilation performance gain, 9258 * as the compiler doesn't have to check element classes when looking for directives. 9259 * This should however only be used if you are sure that no class directives are used in 9260 * the application (including any 3rd party directives). 9261 * 9262 * @param {boolean} enabled `false` if the compiler may ignore directives on element classes 9263 * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) 9264 */ 9265 this.cssClassDirectivesEnabled = function(value) { 9266 if (arguments.length) { 9267 cssClassDirectivesEnabledConfig = value; 9268 return this; 9269 } 9270 return cssClassDirectivesEnabledConfig; 9271 }; 9272 9273 9274 /** 9275 * The security context of DOM Properties. 9276 * @private 9277 */ 9278 var PROP_CONTEXTS = createMap(); 9279 9280 /** 9281 * @ngdoc method 9282 * @name $compileProvider#addPropertySecurityContext 9283 * @description 9284 * 9285 * Defines the security context for DOM properties bound by ng-prop-*. 9286 * 9287 * @param {string} elementName The element name or '*' to match any element. 9288 * @param {string} propertyName The DOM property name. 9289 * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL` 9290 * @returns {object} `this` for chaining 9291 */ 9292 this.addPropertySecurityContext = function(elementName, propertyName, ctx) { 9293 var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase()); 9294 9295 if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) { 9296 throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx); 9297 } 9298 9299 PROP_CONTEXTS[key] = ctx; 9300 return this; 9301 }; 9302 9303 /* Default property contexts. 9304 * 9305 * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58 9306 * Changing: 9307 * - SecurityContext.* => SCE_CONTEXTS/$sce.* 9308 * - STYLE => CSS 9309 * - various URL => MEDIA_URL 9310 * - *|formAction, form|action URL => RESOURCE_URL (like the attribute) 9311 */ 9312 (function registerNativePropertyContexts() { 9313 function registerContext(ctx, values) { 9314 forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; }); 9315 } 9316 9317 registerContext(SCE_CONTEXTS.HTML, [ 9318 'iframe|srcdoc', 9319 '*|innerHTML', 9320 '*|outerHTML' 9321 ]); 9322 registerContext(SCE_CONTEXTS.CSS, ['*|style']); 9323 registerContext(SCE_CONTEXTS.URL, [ 9324 'area|href', 'area|ping', 9325 'a|href', 'a|ping', 9326 'blockquote|cite', 9327 'body|background', 9328 'del|cite', 9329 'input|src', 9330 'ins|cite', 9331 'q|cite' 9332 ]); 9333 registerContext(SCE_CONTEXTS.MEDIA_URL, [ 9334 'audio|src', 9335 'img|src', 'img|srcset', 9336 'source|src', 'source|srcset', 9337 'track|src', 9338 'video|src', 'video|poster' 9339 ]); 9340 registerContext(SCE_CONTEXTS.RESOURCE_URL, [ 9341 '*|formAction', 9342 'applet|code', 'applet|codebase', 9343 'base|href', 9344 'embed|src', 9345 'frame|src', 9346 'form|action', 9347 'head|profile', 9348 'html|manifest', 9349 'iframe|src', 9350 'link|href', 9351 'media|src', 9352 'object|codebase', 'object|data', 9353 'script|src' 9354 ]); 9355 })(); 9356 9357 9358 this.$get = [ 9359 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 9360 '$controller', '$rootScope', '$sce', '$animate', 9361 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 9362 $controller, $rootScope, $sce, $animate) { 9363 9364 var SIMPLE_ATTR_NAME = /^\w/; 9365 var specialAttrHolder = window.document.createElement('div'); 9366 9367 9368 var commentDirectivesEnabled = commentDirectivesEnabledConfig; 9369 var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig; 9370 9371 9372 var onChangesTtl = TTL; 9373 // The onChanges hooks should all be run together in a single digest 9374 // When changes occur, the call to trigger their hooks will be added to this queue 9375 var onChangesQueue; 9376 9377 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest 9378 function flushOnChangesQueue() { 9379 try { 9380 if (!(--onChangesTtl)) { 9381 // We have hit the TTL limit so reset everything 9382 onChangesQueue = undefined; 9383 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); 9384 } 9385 // We must run this hook in an apply since the $$postDigest runs outside apply 9386 $rootScope.$apply(function() { 9387 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { 9388 try { 9389 onChangesQueue[i](); 9390 } catch (e) { 9391 $exceptionHandler(e); 9392 } 9393 } 9394 // Reset the queue to trigger a new schedule next time there is a change 9395 onChangesQueue = undefined; 9396 }); 9397 } finally { 9398 onChangesTtl++; 9399 } 9400 } 9401 9402 9403 function sanitizeSrcset(value, invokeType) { 9404 if (!value) { 9405 return value; 9406 } 9407 if (!isString(value)) { 9408 throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString()); 9409 } 9410 9411 // Such values are a bit too complex to handle automatically inside $sce. 9412 // Instead, we sanitize each of the URIs individually, which works, even dynamically. 9413 9414 // It's not possible to work around this using `$sce.trustAsMediaUrl`. 9415 // If you want to programmatically set explicitly trusted unsafe URLs, you should use 9416 // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the 9417 // `ng-bind-html` directive. 9418 9419 var result = ''; 9420 9421 // first check if there are spaces because it's not the same pattern 9422 var trimmedSrcset = trim(value); 9423 // ( 999x ,| 999w ,| ,|, ) 9424 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 9425 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 9426 9427 // split srcset into tuple of uri and descriptor except for the last item 9428 var rawUris = trimmedSrcset.split(pattern); 9429 9430 // for each tuples 9431 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 9432 for (var i = 0; i < nbrUrisWith2parts; i++) { 9433 var innerIdx = i * 2; 9434 // sanitize the uri 9435 result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx])); 9436 // add the descriptor 9437 result += ' ' + trim(rawUris[innerIdx + 1]); 9438 } 9439 9440 // split the last item into uri and descriptor 9441 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 9442 9443 // sanitize the last uri 9444 result += $sce.getTrustedMediaUrl(trim(lastTuple[0])); 9445 9446 // and add the last descriptor if any 9447 if (lastTuple.length === 2) { 9448 result += (' ' + trim(lastTuple[1])); 9449 } 9450 return result; 9451 } 9452 9453 9454 function Attributes(element, attributesToCopy) { 9455 if (attributesToCopy) { 9456 var keys = Object.keys(attributesToCopy); 9457 var i, l, key; 9458 9459 for (i = 0, l = keys.length; i < l; i++) { 9460 key = keys[i]; 9461 this[key] = attributesToCopy[key]; 9462 } 9463 } else { 9464 this.$attr = {}; 9465 } 9466 9467 this.$$element = element; 9468 } 9469 9470 Attributes.prototype = { 9471 /** 9472 * @ngdoc method 9473 * @name $compile.directive.Attributes#$normalize 9474 * @kind function 9475 * 9476 * @description 9477 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 9478 * `data-`) to its normalized, camelCase form. 9479 * 9480 * Also there is special case for Moz prefix starting with upper case letter. 9481 * 9482 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 9483 * 9484 * @param {string} name Name to normalize 9485 */ 9486 $normalize: directiveNormalize, 9487 9488 9489 /** 9490 * @ngdoc method 9491 * @name $compile.directive.Attributes#$addClass 9492 * @kind function 9493 * 9494 * @description 9495 * Adds the CSS class value specified by the classVal parameter to the element. If animations 9496 * are enabled then an animation will be triggered for the class addition. 9497 * 9498 * @param {string} classVal The className value that will be added to the element 9499 */ 9500 $addClass: function(classVal) { 9501 if (classVal && classVal.length > 0) { 9502 $animate.addClass(this.$$element, classVal); 9503 } 9504 }, 9505 9506 /** 9507 * @ngdoc method 9508 * @name $compile.directive.Attributes#$removeClass 9509 * @kind function 9510 * 9511 * @description 9512 * Removes the CSS class value specified by the classVal parameter from the element. If 9513 * animations are enabled then an animation will be triggered for the class removal. 9514 * 9515 * @param {string} classVal The className value that will be removed from the element 9516 */ 9517 $removeClass: function(classVal) { 9518 if (classVal && classVal.length > 0) { 9519 $animate.removeClass(this.$$element, classVal); 9520 } 9521 }, 9522 9523 /** 9524 * @ngdoc method 9525 * @name $compile.directive.Attributes#$updateClass 9526 * @kind function 9527 * 9528 * @description 9529 * Adds and removes the appropriate CSS class values to the element based on the difference 9530 * between the new and old CSS class values (specified as newClasses and oldClasses). 9531 * 9532 * @param {string} newClasses The current CSS className value 9533 * @param {string} oldClasses The former CSS className value 9534 */ 9535 $updateClass: function(newClasses, oldClasses) { 9536 var toAdd = tokenDifference(newClasses, oldClasses); 9537 if (toAdd && toAdd.length) { 9538 $animate.addClass(this.$$element, toAdd); 9539 } 9540 9541 var toRemove = tokenDifference(oldClasses, newClasses); 9542 if (toRemove && toRemove.length) { 9543 $animate.removeClass(this.$$element, toRemove); 9544 } 9545 }, 9546 9547 /** 9548 * Set a normalized attribute on the element in a way such that all directives 9549 * can share the attribute. This function properly handles boolean attributes. 9550 * @param {string} key Normalized key. (ie ngAttribute) 9551 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 9552 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 9553 * Defaults to true. 9554 * @param {string=} attrName Optional none normalized name. Defaults to key. 9555 */ 9556 $set: function(key, value, writeAttr, attrName) { 9557 // TODO: decide whether or not to throw an error if "class" 9558 // is set through this function since it may cause $updateClass to 9559 // become unstable. 9560 9561 var node = this.$$element[0], 9562 booleanKey = getBooleanAttrName(node, key), 9563 aliasedKey = getAliasedAttrName(key), 9564 observer = key, 9565 nodeName; 9566 9567 if (booleanKey) { 9568 this.$$element.prop(key, value); 9569 attrName = booleanKey; 9570 } else if (aliasedKey) { 9571 this[aliasedKey] = value; 9572 observer = aliasedKey; 9573 } 9574 9575 this[key] = value; 9576 9577 // translate normalized key to actual key 9578 if (attrName) { 9579 this.$attr[key] = attrName; 9580 } else { 9581 attrName = this.$attr[key]; 9582 if (!attrName) { 9583 this.$attr[key] = attrName = snake_case(key, '-'); 9584 } 9585 } 9586 9587 nodeName = nodeName_(this.$$element); 9588 9589 // Sanitize img[srcset] values. 9590 if (nodeName === 'img' && key === 'srcset') { 9591 this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)'); 9592 } 9593 9594 if (writeAttr !== false) { 9595 if (value === null || isUndefined(value)) { 9596 this.$$element.removeAttr(attrName); 9597 } else { 9598 if (SIMPLE_ATTR_NAME.test(attrName)) { 9599 // jQuery skips special boolean attrs treatment in XML nodes for 9600 // historical reasons and hence AngularJS cannot freely call 9601 // `.attr(attrName, false) with such attributes. To avoid issues 9602 // in XHTML, call `removeAttr` in such cases instead. 9603 // See https://github.com/jquery/jquery/issues/4249 9604 if (booleanKey && value === false) { 9605 this.$$element.removeAttr(attrName); 9606 } else { 9607 this.$$element.attr(attrName, value); 9608 } 9609 } else { 9610 setSpecialAttr(this.$$element[0], attrName, value); 9611 } 9612 } 9613 } 9614 9615 // fire observers 9616 var $$observers = this.$$observers; 9617 if ($$observers) { 9618 forEach($$observers[observer], function(fn) { 9619 try { 9620 fn(value); 9621 } catch (e) { 9622 $exceptionHandler(e); 9623 } 9624 }); 9625 } 9626 }, 9627 9628 9629 /** 9630 * @ngdoc method 9631 * @name $compile.directive.Attributes#$observe 9632 * @kind function 9633 * 9634 * @description 9635 * Observes an interpolated attribute. 9636 * 9637 * The observer function will be invoked once during the next `$digest` following 9638 * compilation. The observer is then invoked whenever the interpolated value 9639 * changes. 9640 * 9641 * @param {string} key Normalized key. (ie ngAttribute) . 9642 * @param {function(interpolatedValue)} fn Function that will be called whenever 9643 the interpolated value of the attribute changes. 9644 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 9645 * guide} for more info. 9646 * @returns {function()} Returns a deregistration function for this observer. 9647 */ 9648 $observe: function(key, fn) { 9649 var attrs = this, 9650 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 9651 listeners = ($$observers[key] || ($$observers[key] = [])); 9652 9653 listeners.push(fn); 9654 $rootScope.$evalAsync(function() { 9655 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 9656 // no one registered attribute interpolation function, so lets call it manually 9657 fn(attrs[key]); 9658 } 9659 }); 9660 9661 return function() { 9662 arrayRemove(listeners, fn); 9663 }; 9664 } 9665 }; 9666 9667 function setSpecialAttr(element, attrName, value) { 9668 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 9669 // so we have to jump through some hoops to get such an attribute 9670 // https://github.com/angular/angular.js/pull/13318 9671 specialAttrHolder.innerHTML = '<span ' + attrName + '>'; 9672 var attributes = specialAttrHolder.firstChild.attributes; 9673 var attribute = attributes[0]; 9674 // We have to remove the attribute from its container element before we can add it to the destination element 9675 attributes.removeNamedItem(attribute.name); 9676 attribute.value = value; 9677 element.attributes.setNamedItem(attribute); 9678 } 9679 9680 function safeAddClass($element, className) { 9681 try { 9682 $element.addClass(className); 9683 } catch (e) { 9684 // ignore, since it means that we are trying to set class on 9685 // SVG element, where class name is read-only. 9686 } 9687 } 9688 9689 9690 var startSymbol = $interpolate.startSymbol(), 9691 endSymbol = $interpolate.endSymbol(), 9692 denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}') 9693 ? identity 9694 : function denormalizeTemplate(template) { 9695 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 9696 }, 9697 NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/; 9698 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 9699 9700 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 9701 var bindings = $element.data('$binding') || []; 9702 9703 if (isArray(binding)) { 9704 bindings = bindings.concat(binding); 9705 } else { 9706 bindings.push(binding); 9707 } 9708 9709 $element.data('$binding', bindings); 9710 } : noop; 9711 9712 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 9713 safeAddClass($element, 'ng-binding'); 9714 } : noop; 9715 9716 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 9717 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 9718 $element.data(dataName, scope); 9719 } : noop; 9720 9721 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 9722 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 9723 } : noop; 9724 9725 compile.$$createComment = function(directiveName, comment) { 9726 var content = ''; 9727 if (debugInfoEnabled) { 9728 content = ' ' + (directiveName || '') + ': '; 9729 if (comment) content += comment + ' '; 9730 } 9731 return window.document.createComment(content); 9732 }; 9733 9734 return compile; 9735 9736 //================================ 9737 9738 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 9739 previousCompileContext) { 9740 if (!($compileNodes instanceof jqLite)) { 9741 // jquery always rewraps, whereas we need to preserve the original selector so that we can 9742 // modify it. 9743 $compileNodes = jqLite($compileNodes); 9744 } 9745 var compositeLinkFn = 9746 compileNodes($compileNodes, transcludeFn, $compileNodes, 9747 maxPriority, ignoreDirective, previousCompileContext); 9748 compile.$$addScopeClass($compileNodes); 9749 var namespace = null; 9750 return function publicLinkFn(scope, cloneConnectFn, options) { 9751 if (!$compileNodes) { 9752 throw $compileMinErr('multilink', 'This element has already been linked.'); 9753 } 9754 assertArg(scope, 'scope'); 9755 9756 if (previousCompileContext && previousCompileContext.needsNewScope) { 9757 // A parent directive did a replace and a directive on this element asked 9758 // for transclusion, which caused us to lose a layer of element on which 9759 // we could hold the new transclusion scope, so we will create it manually 9760 // here. 9761 scope = scope.$parent.$new(); 9762 } 9763 9764 options = options || {}; 9765 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 9766 transcludeControllers = options.transcludeControllers, 9767 futureParentElement = options.futureParentElement; 9768 9769 // When `parentBoundTranscludeFn` is passed, it is a 9770 // `controllersBoundTransclude` function (it was previously passed 9771 // as `transclude` to directive.link) so we must unwrap it to get 9772 // its `boundTranscludeFn` 9773 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 9774 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 9775 } 9776 9777 if (!namespace) { 9778 namespace = detectNamespaceForChildElements(futureParentElement); 9779 } 9780 var $linkNode; 9781 if (namespace !== 'html') { 9782 // When using a directive with replace:true and templateUrl the $compileNodes 9783 // (or a child element inside of them) 9784 // might change, so we need to recreate the namespace adapted compileNodes 9785 // for call to the link function. 9786 // Note: This will already clone the nodes... 9787 $linkNode = jqLite( 9788 wrapTemplate(namespace, jqLite('<div></div>').append($compileNodes).html()) 9789 ); 9790 } else if (cloneConnectFn) { 9791 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 9792 // and sometimes changes the structure of the DOM. 9793 $linkNode = JQLitePrototype.clone.call($compileNodes); 9794 } else { 9795 $linkNode = $compileNodes; 9796 } 9797 9798 if (transcludeControllers) { 9799 for (var controllerName in transcludeControllers) { 9800 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 9801 } 9802 } 9803 9804 compile.$$addScopeInfo($linkNode, scope); 9805 9806 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 9807 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 9808 9809 if (!cloneConnectFn) { 9810 $compileNodes = compositeLinkFn = null; 9811 } 9812 return $linkNode; 9813 }; 9814 } 9815 9816 function detectNamespaceForChildElements(parentElement) { 9817 // TODO: Make this detect MathML as well... 9818 var node = parentElement && parentElement[0]; 9819 if (!node) { 9820 return 'html'; 9821 } else { 9822 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 9823 } 9824 } 9825 9826 /** 9827 * Compile function matches each node in nodeList against the directives. Once all directives 9828 * for a particular node are collected their compile functions are executed. The compile 9829 * functions return values - the linking functions - are combined into a composite linking 9830 * function, which is the a linking function for the node. 9831 * 9832 * @param {NodeList} nodeList an array of nodes or NodeList to compile 9833 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 9834 * scope argument is auto-generated to the new child of the transcluded parent scope. 9835 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 9836 * the rootElement must be set the jqLite collection of the compile root. This is 9837 * needed so that the jqLite collection items can be replaced with widgets. 9838 * @param {number=} maxPriority Max directive priority. 9839 * @returns {Function} A composite linking function of all of the matched directives or null. 9840 */ 9841 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 9842 previousCompileContext) { 9843 var linkFns = [], 9844 // `nodeList` can be either an element's `.childNodes` (live NodeList) 9845 // or a jqLite/jQuery collection or an array 9846 notLiveList = isArray(nodeList) || (nodeList instanceof jqLite), 9847 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 9848 9849 9850 for (var i = 0; i < nodeList.length; i++) { 9851 attrs = new Attributes(); 9852 9853 // Support: IE 11 only 9854 // Workaround for #11781 and #14924 9855 if (msie === 11) { 9856 mergeConsecutiveTextNodes(nodeList, i, notLiveList); 9857 } 9858 9859 // We must always refer to `nodeList[i]` hereafter, 9860 // since the nodes can be replaced underneath us. 9861 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 9862 ignoreDirective); 9863 9864 nodeLinkFn = (directives.length) 9865 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 9866 null, [], [], previousCompileContext) 9867 : null; 9868 9869 if (nodeLinkFn && nodeLinkFn.scope) { 9870 compile.$$addScopeClass(attrs.$$element); 9871 } 9872 9873 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 9874 !(childNodes = nodeList[i].childNodes) || 9875 !childNodes.length) 9876 ? null 9877 : compileNodes(childNodes, 9878 nodeLinkFn ? ( 9879 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 9880 && nodeLinkFn.transclude) : transcludeFn); 9881 9882 if (nodeLinkFn || childLinkFn) { 9883 linkFns.push(i, nodeLinkFn, childLinkFn); 9884 linkFnFound = true; 9885 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 9886 } 9887 9888 //use the previous context only for the first element in the virtual group 9889 previousCompileContext = null; 9890 } 9891 9892 // return a linking function if we have found anything, null otherwise 9893 return linkFnFound ? compositeLinkFn : null; 9894 9895 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 9896 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 9897 var stableNodeList; 9898 9899 9900 if (nodeLinkFnFound) { 9901 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 9902 // offsets don't get screwed up 9903 var nodeListLength = nodeList.length; 9904 stableNodeList = new Array(nodeListLength); 9905 9906 // create a sparse array by only copying the elements which have a linkFn 9907 for (i = 0; i < linkFns.length; i += 3) { 9908 idx = linkFns[i]; 9909 stableNodeList[idx] = nodeList[idx]; 9910 } 9911 } else { 9912 stableNodeList = nodeList; 9913 } 9914 9915 for (i = 0, ii = linkFns.length; i < ii;) { 9916 node = stableNodeList[linkFns[i++]]; 9917 nodeLinkFn = linkFns[i++]; 9918 childLinkFn = linkFns[i++]; 9919 9920 if (nodeLinkFn) { 9921 if (nodeLinkFn.scope) { 9922 childScope = scope.$new(); 9923 compile.$$addScopeInfo(jqLite(node), childScope); 9924 } else { 9925 childScope = scope; 9926 } 9927 9928 if (nodeLinkFn.transcludeOnThisElement) { 9929 childBoundTranscludeFn = createBoundTranscludeFn( 9930 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 9931 9932 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 9933 childBoundTranscludeFn = parentBoundTranscludeFn; 9934 9935 } else if (!parentBoundTranscludeFn && transcludeFn) { 9936 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 9937 9938 } else { 9939 childBoundTranscludeFn = null; 9940 } 9941 9942 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 9943 9944 } else if (childLinkFn) { 9945 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 9946 } 9947 } 9948 } 9949 } 9950 9951 function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) { 9952 var node = nodeList[idx]; 9953 var parent = node.parentNode; 9954 var sibling; 9955 9956 if (node.nodeType !== NODE_TYPE_TEXT) { 9957 return; 9958 } 9959 9960 while (true) { 9961 sibling = parent ? node.nextSibling : nodeList[idx + 1]; 9962 if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) { 9963 break; 9964 } 9965 9966 node.nodeValue = node.nodeValue + sibling.nodeValue; 9967 9968 if (sibling.parentNode) { 9969 sibling.parentNode.removeChild(sibling); 9970 } 9971 if (notLiveList && sibling === nodeList[idx + 1]) { 9972 nodeList.splice(idx + 1, 1); 9973 } 9974 } 9975 } 9976 9977 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 9978 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 9979 9980 if (!transcludedScope) { 9981 transcludedScope = scope.$new(false, containingScope); 9982 transcludedScope.$$transcluded = true; 9983 } 9984 9985 return transcludeFn(transcludedScope, cloneFn, { 9986 parentBoundTranscludeFn: previousBoundTranscludeFn, 9987 transcludeControllers: controllers, 9988 futureParentElement: futureParentElement 9989 }); 9990 } 9991 9992 // We need to attach the transclusion slots onto the `boundTranscludeFn` 9993 // so that they are available inside the `controllersBoundTransclude` function 9994 var boundSlots = boundTranscludeFn.$$slots = createMap(); 9995 for (var slotName in transcludeFn.$$slots) { 9996 if (transcludeFn.$$slots[slotName]) { 9997 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 9998 } else { 9999 boundSlots[slotName] = null; 10000 } 10001 } 10002 10003 return boundTranscludeFn; 10004 } 10005 10006 /** 10007 * Looks for directives on the given node and adds them to the directive collection which is 10008 * sorted. 10009 * 10010 * @param node Node to search. 10011 * @param directives An array to which the directives are added to. This array is sorted before 10012 * the function returns. 10013 * @param attrs The shared attrs object which is used to populate the normalized attributes. 10014 * @param {number=} maxPriority Max directive priority. 10015 */ 10016 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 10017 var nodeType = node.nodeType, 10018 attrsMap = attrs.$attr, 10019 match, 10020 nodeName, 10021 className; 10022 10023 switch (nodeType) { 10024 case NODE_TYPE_ELEMENT: /* Element */ 10025 10026 nodeName = nodeName_(node); 10027 10028 // use the node name: <directive> 10029 addDirective(directives, 10030 directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); 10031 10032 // iterate over the attributes 10033 for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes, 10034 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 10035 var attrStartName = false; 10036 var attrEndName = false; 10037 10038 var isNgAttr = false, isNgProp = false, isNgEvent = false; 10039 var multiElementMatch; 10040 10041 attr = nAttrs[j]; 10042 name = attr.name; 10043 value = attr.value; 10044 10045 nName = directiveNormalize(name.toLowerCase()); 10046 10047 // Support ng-attr-*, ng-prop-* and ng-on-* 10048 if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) { 10049 isNgAttr = ngPrefixMatch[1] === 'Attr'; 10050 isNgProp = ngPrefixMatch[1] === 'Prop'; 10051 isNgEvent = ngPrefixMatch[1] === 'On'; 10052 10053 // Normalize the non-prefixed name 10054 name = name.replace(PREFIX_REGEXP, '') 10055 .toLowerCase() 10056 .substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) { 10057 return letter.toUpperCase(); 10058 }); 10059 10060 // Support *-start / *-end multi element directives 10061 } else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) { 10062 attrStartName = name; 10063 attrEndName = name.substr(0, name.length - 5) + 'end'; 10064 name = name.substr(0, name.length - 6); 10065 } 10066 10067 if (isNgProp || isNgEvent) { 10068 attrs[nName] = value; 10069 attrsMap[nName] = attr.name; 10070 10071 if (isNgProp) { 10072 addPropertyDirective(node, directives, nName, name); 10073 } else { 10074 addEventDirective(directives, nName, name); 10075 } 10076 } else { 10077 // Update nName for cases where a prefix was removed 10078 // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-* 10079 nName = directiveNormalize(name.toLowerCase()); 10080 attrsMap[nName] = name; 10081 10082 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 10083 attrs[nName] = value; 10084 if (getBooleanAttrName(node, nName)) { 10085 attrs[nName] = true; // presence means true 10086 } 10087 } 10088 10089 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 10090 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 10091 attrEndName); 10092 } 10093 } 10094 10095 if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { 10096 // Hidden input elements can have strange behaviour when navigating back to the page 10097 // This tells the browser not to try to cache and reinstate previous values 10098 node.setAttribute('autocomplete', 'off'); 10099 } 10100 10101 // use class as directive 10102 if (!cssClassDirectivesEnabled) break; 10103 className = node.className; 10104 if (isObject(className)) { 10105 // Maybe SVGAnimatedString 10106 className = className.animVal; 10107 } 10108 if (isString(className) && className !== '') { 10109 while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) { 10110 nName = directiveNormalize(match[2]); 10111 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 10112 attrs[nName] = trim(match[3]); 10113 } 10114 className = className.substr(match.index + match[0].length); 10115 } 10116 } 10117 break; 10118 case NODE_TYPE_TEXT: /* Text Node */ 10119 addTextInterpolateDirective(directives, node.nodeValue); 10120 break; 10121 case NODE_TYPE_COMMENT: /* Comment */ 10122 if (!commentDirectivesEnabled) break; 10123 collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective); 10124 break; 10125 } 10126 10127 directives.sort(byPriority); 10128 return directives; 10129 } 10130 10131 function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 10132 // function created because of performance, try/catch disables 10133 // the optimization of the whole function #14848 10134 try { 10135 var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 10136 if (match) { 10137 var nName = directiveNormalize(match[1]); 10138 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 10139 attrs[nName] = trim(match[2]); 10140 } 10141 } 10142 } catch (e) { 10143 // turns out that under some circumstances IE9 throws errors when one attempts to read 10144 // comment's node value. 10145 // Just ignore it and continue. (Can't seem to reproduce in test case.) 10146 } 10147 } 10148 10149 /** 10150 * Given a node with a directive-start it collects all of the siblings until it finds 10151 * directive-end. 10152 * @param node 10153 * @param attrStart 10154 * @param attrEnd 10155 * @returns {*} 10156 */ 10157 function groupScan(node, attrStart, attrEnd) { 10158 var nodes = []; 10159 var depth = 0; 10160 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 10161 do { 10162 if (!node) { 10163 throw $compileMinErr('uterdir', 10164 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.', 10165 attrStart, attrEnd); 10166 } 10167 if (node.nodeType === NODE_TYPE_ELEMENT) { 10168 if (node.hasAttribute(attrStart)) depth++; 10169 if (node.hasAttribute(attrEnd)) depth--; 10170 } 10171 nodes.push(node); 10172 node = node.nextSibling; 10173 } while (depth > 0); 10174 } else { 10175 nodes.push(node); 10176 } 10177 10178 return jqLite(nodes); 10179 } 10180 10181 /** 10182 * Wrapper for linking function which converts normal linking function into a grouped 10183 * linking function. 10184 * @param linkFn 10185 * @param attrStart 10186 * @param attrEnd 10187 * @returns {Function} 10188 */ 10189 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 10190 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { 10191 element = groupScan(element[0], attrStart, attrEnd); 10192 return linkFn(scope, element, attrs, controllers, transcludeFn); 10193 }; 10194 } 10195 10196 /** 10197 * A function generator that is used to support both eager and lazy compilation 10198 * linking function. 10199 * @param eager 10200 * @param $compileNodes 10201 * @param transcludeFn 10202 * @param maxPriority 10203 * @param ignoreDirective 10204 * @param previousCompileContext 10205 * @returns {Function} 10206 */ 10207 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 10208 var compiled; 10209 10210 if (eager) { 10211 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 10212 } 10213 return /** @this */ function lazyCompilation() { 10214 if (!compiled) { 10215 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 10216 10217 // Null out all of these references in order to make them eligible for garbage collection 10218 // since this is a potentially long lived closure 10219 $compileNodes = transcludeFn = previousCompileContext = null; 10220 } 10221 return compiled.apply(this, arguments); 10222 }; 10223 } 10224 10225 /** 10226 * Once the directives have been collected, their compile functions are executed. This method 10227 * is responsible for inlining directive templates as well as terminating the application 10228 * of the directives if the terminal directive has been reached. 10229 * 10230 * @param {Array} directives Array of collected directives to execute their compile function. 10231 * this needs to be pre-sorted by priority order. 10232 * @param {Node} compileNode The raw DOM node to apply the compile functions to 10233 * @param {Object} templateAttrs The shared attribute function 10234 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 10235 * scope argument is auto-generated to the new 10236 * child of the transcluded parent scope. 10237 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 10238 * argument has the root jqLite array so that we can replace nodes 10239 * on it. 10240 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 10241 * compiling the transclusion. 10242 * @param {Array.<Function>} preLinkFns 10243 * @param {Array.<Function>} postLinkFns 10244 * @param {Object} previousCompileContext Context used for previous compilation of the current 10245 * node 10246 * @returns {Function} linkFn 10247 */ 10248 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 10249 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 10250 previousCompileContext) { 10251 previousCompileContext = previousCompileContext || {}; 10252 10253 var terminalPriority = -Number.MAX_VALUE, 10254 newScopeDirective = previousCompileContext.newScopeDirective, 10255 controllerDirectives = previousCompileContext.controllerDirectives, 10256 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 10257 templateDirective = previousCompileContext.templateDirective, 10258 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 10259 hasTranscludeDirective = false, 10260 hasTemplate = false, 10261 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 10262 $compileNode = templateAttrs.$$element = jqLite(compileNode), 10263 directive, 10264 directiveName, 10265 $template, 10266 replaceDirective = originalReplaceDirective, 10267 childTranscludeFn = transcludeFn, 10268 linkFn, 10269 didScanForMultipleTransclusion = false, 10270 mightHaveMultipleTransclusionError = false, 10271 directiveValue; 10272 10273 // executes all directives on the current element 10274 for (var i = 0, ii = directives.length; i < ii; i++) { 10275 directive = directives[i]; 10276 var attrStart = directive.$$start; 10277 var attrEnd = directive.$$end; 10278 10279 // collect multiblock sections 10280 if (attrStart) { 10281 $compileNode = groupScan(compileNode, attrStart, attrEnd); 10282 } 10283 $template = undefined; 10284 10285 if (terminalPriority > directive.priority) { 10286 break; // prevent further processing of directives 10287 } 10288 10289 directiveValue = directive.scope; 10290 10291 if (directiveValue) { 10292 10293 // skip the check for directives with async templates, we'll check the derived sync 10294 // directive when the template arrives 10295 if (!directive.templateUrl) { 10296 if (isObject(directiveValue)) { 10297 // This directive is trying to add an isolated scope. 10298 // Check that there is no scope of any kind already 10299 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 10300 directive, $compileNode); 10301 newIsolateScopeDirective = directive; 10302 } else { 10303 // This directive is trying to add a child scope. 10304 // Check that there is no isolated scope already 10305 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 10306 $compileNode); 10307 } 10308 } 10309 10310 newScopeDirective = newScopeDirective || directive; 10311 } 10312 10313 directiveName = directive.name; 10314 10315 // If we encounter a condition that can result in transclusion on the directive, 10316 // then scan ahead in the remaining directives for others that may cause a multiple 10317 // transclusion error to be thrown during the compilation process. If a matching directive 10318 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 10319 // compile the `transclude` function rather than doing it lazily in order to throw 10320 // exceptions at the correct time 10321 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 10322 || (directive.transclude && !directive.$$tlb))) { 10323 var candidateDirective; 10324 10325 for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) { 10326 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 10327 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 10328 mightHaveMultipleTransclusionError = true; 10329 break; 10330 } 10331 } 10332 10333 didScanForMultipleTransclusion = true; 10334 } 10335 10336 if (!directive.templateUrl && directive.controller) { 10337 controllerDirectives = controllerDirectives || createMap(); 10338 assertNoDuplicate('\'' + directiveName + '\' controller', 10339 controllerDirectives[directiveName], directive, $compileNode); 10340 controllerDirectives[directiveName] = directive; 10341 } 10342 10343 directiveValue = directive.transclude; 10344 10345 if (directiveValue) { 10346 hasTranscludeDirective = true; 10347 10348 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 10349 // This option should only be used by directives that know how to safely handle element transclusion, 10350 // where the transcluded nodes are added or replaced after linking. 10351 if (!directive.$$tlb) { 10352 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 10353 nonTlbTranscludeDirective = directive; 10354 } 10355 10356 if (directiveValue === 'element') { 10357 hasElementTranscludeDirective = true; 10358 terminalPriority = directive.priority; 10359 $template = $compileNode; 10360 $compileNode = templateAttrs.$$element = 10361 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); 10362 compileNode = $compileNode[0]; 10363 replaceWith(jqCollection, sliceArgs($template), compileNode); 10364 10365 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 10366 replaceDirective && replaceDirective.name, { 10367 // Don't pass in: 10368 // - controllerDirectives - otherwise we'll create duplicates controllers 10369 // - newIsolateScopeDirective or templateDirective - combining templates with 10370 // element transclusion doesn't make sense. 10371 // 10372 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 10373 // on the same element more than once. 10374 nonTlbTranscludeDirective: nonTlbTranscludeDirective 10375 }); 10376 } else { 10377 10378 var slots = createMap(); 10379 10380 if (!isObject(directiveValue)) { 10381 $template = jqLite(jqLiteClone(compileNode)).contents(); 10382 } else { 10383 10384 // We have transclusion slots, 10385 // collect them up, compile them and store their transclusion functions 10386 $template = window.document.createDocumentFragment(); 10387 10388 var slotMap = createMap(); 10389 var filledSlots = createMap(); 10390 10391 // Parse the element selectors 10392 forEach(directiveValue, function(elementSelector, slotName) { 10393 // If an element selector starts with a ? then it is optional 10394 var optional = (elementSelector.charAt(0) === '?'); 10395 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 10396 10397 slotMap[elementSelector] = slotName; 10398 10399 // We explicitly assign `null` since this implies that a slot was defined but not filled. 10400 // Later when calling boundTransclusion functions with a slot name we only error if the 10401 // slot is `undefined` 10402 slots[slotName] = null; 10403 10404 // filledSlots contains `true` for all slots that are either optional or have been 10405 // filled. This is used to check that we have not missed any required slots 10406 filledSlots[slotName] = optional; 10407 }); 10408 10409 // Add the matching elements into their slot 10410 forEach($compileNode.contents(), function(node) { 10411 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 10412 if (slotName) { 10413 filledSlots[slotName] = true; 10414 slots[slotName] = slots[slotName] || window.document.createDocumentFragment(); 10415 slots[slotName].appendChild(node); 10416 } else { 10417 $template.appendChild(node); 10418 } 10419 }); 10420 10421 // Check for required slots that were not filled 10422 forEach(filledSlots, function(filled, slotName) { 10423 if (!filled) { 10424 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 10425 } 10426 }); 10427 10428 for (var slotName in slots) { 10429 if (slots[slotName]) { 10430 // Only define a transclusion function if the slot was filled 10431 var slotCompileNodes = jqLite(slots[slotName].childNodes); 10432 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn); 10433 } 10434 } 10435 10436 $template = jqLite($template.childNodes); 10437 } 10438 10439 $compileNode.empty(); // clear contents 10440 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 10441 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 10442 childTranscludeFn.$$slots = slots; 10443 } 10444 } 10445 10446 if (directive.template) { 10447 hasTemplate = true; 10448 assertNoDuplicate('template', templateDirective, directive, $compileNode); 10449 templateDirective = directive; 10450 10451 directiveValue = (isFunction(directive.template)) 10452 ? directive.template($compileNode, templateAttrs) 10453 : directive.template; 10454 10455 directiveValue = denormalizeTemplate(directiveValue); 10456 10457 if (directive.replace) { 10458 replaceDirective = directive; 10459 if (jqLiteIsTextNode(directiveValue)) { 10460 $template = []; 10461 } else { 10462 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 10463 } 10464 compileNode = $template[0]; 10465 10466 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 10467 throw $compileMinErr('tplrt', 10468 'Template for directive \'{0}\' must have exactly one root element. {1}', 10469 directiveName, ''); 10470 } 10471 10472 replaceWith(jqCollection, $compileNode, compileNode); 10473 10474 var newTemplateAttrs = {$attr: {}}; 10475 10476 // combine directives from the original node and from the template: 10477 // - take the array of directives for this element 10478 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 10479 // - collect directives from the template and sort them by priority 10480 // - combine directives as: processed + template + unprocessed 10481 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 10482 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 10483 10484 if (newIsolateScopeDirective || newScopeDirective) { 10485 // The original directive caused the current element to be replaced but this element 10486 // also needs to have a new scope, so we need to tell the template directives 10487 // that they would need to get their scope from further up, if they require transclusion 10488 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 10489 } 10490 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 10491 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 10492 10493 ii = directives.length; 10494 } else { 10495 $compileNode.html(directiveValue); 10496 } 10497 } 10498 10499 if (directive.templateUrl) { 10500 hasTemplate = true; 10501 assertNoDuplicate('template', templateDirective, directive, $compileNode); 10502 templateDirective = directive; 10503 10504 if (directive.replace) { 10505 replaceDirective = directive; 10506 } 10507 10508 // eslint-disable-next-line no-func-assign 10509 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 10510 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 10511 controllerDirectives: controllerDirectives, 10512 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 10513 newIsolateScopeDirective: newIsolateScopeDirective, 10514 templateDirective: templateDirective, 10515 nonTlbTranscludeDirective: nonTlbTranscludeDirective 10516 }); 10517 ii = directives.length; 10518 } else if (directive.compile) { 10519 try { 10520 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 10521 var context = directive.$$originalDirective || directive; 10522 if (isFunction(linkFn)) { 10523 addLinkFns(null, bind(context, linkFn), attrStart, attrEnd); 10524 } else if (linkFn) { 10525 addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd); 10526 } 10527 } catch (e) { 10528 $exceptionHandler(e, startingTag($compileNode)); 10529 } 10530 } 10531 10532 if (directive.terminal) { 10533 nodeLinkFn.terminal = true; 10534 terminalPriority = Math.max(terminalPriority, directive.priority); 10535 } 10536 10537 } 10538 10539 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 10540 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 10541 nodeLinkFn.templateOnThisElement = hasTemplate; 10542 nodeLinkFn.transclude = childTranscludeFn; 10543 10544 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 10545 10546 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 10547 return nodeLinkFn; 10548 10549 //////////////////// 10550 10551 function addLinkFns(pre, post, attrStart, attrEnd) { 10552 if (pre) { 10553 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 10554 pre.require = directive.require; 10555 pre.directiveName = directiveName; 10556 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 10557 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 10558 } 10559 preLinkFns.push(pre); 10560 } 10561 if (post) { 10562 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 10563 post.require = directive.require; 10564 post.directiveName = directiveName; 10565 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 10566 post = cloneAndAnnotateFn(post, {isolateScope: true}); 10567 } 10568 postLinkFns.push(post); 10569 } 10570 } 10571 10572 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 10573 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 10574 attrs, scopeBindingInfo; 10575 10576 if (compileNode === linkNode) { 10577 attrs = templateAttrs; 10578 $element = templateAttrs.$$element; 10579 } else { 10580 $element = jqLite(linkNode); 10581 attrs = new Attributes($element, templateAttrs); 10582 } 10583 10584 controllerScope = scope; 10585 if (newIsolateScopeDirective) { 10586 isolateScope = scope.$new(true); 10587 } else if (newScopeDirective) { 10588 controllerScope = scope.$parent; 10589 } 10590 10591 if (boundTranscludeFn) { 10592 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 10593 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 10594 transcludeFn = controllersBoundTransclude; 10595 transcludeFn.$$boundTransclude = boundTranscludeFn; 10596 // expose the slots on the `$transclude` function 10597 transcludeFn.isSlotFilled = function(slotName) { 10598 return !!boundTranscludeFn.$$slots[slotName]; 10599 }; 10600 } 10601 10602 if (controllerDirectives) { 10603 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); 10604 } 10605 10606 if (newIsolateScopeDirective) { 10607 // Initialize isolate scope bindings for new isolate scope directive. 10608 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 10609 templateDirective === newIsolateScopeDirective.$$originalDirective))); 10610 compile.$$addScopeClass($element, true); 10611 isolateScope.$$isolateBindings = 10612 newIsolateScopeDirective.$$isolateBindings; 10613 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, 10614 isolateScope.$$isolateBindings, 10615 newIsolateScopeDirective); 10616 if (scopeBindingInfo.removeWatches) { 10617 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); 10618 } 10619 } 10620 10621 // Initialize bindToController bindings 10622 for (var name in elementControllers) { 10623 var controllerDirective = controllerDirectives[name]; 10624 var controller = elementControllers[name]; 10625 var bindings = controllerDirective.$$bindings.bindToController; 10626 10627 controller.instance = controller(); 10628 $element.data('$' + controllerDirective.name + 'Controller', controller.instance); 10629 controller.bindingInfo = 10630 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 10631 } 10632 10633 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy 10634 forEach(controllerDirectives, function(controllerDirective, name) { 10635 var require = controllerDirective.require; 10636 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { 10637 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); 10638 } 10639 }); 10640 10641 // Handle the init and destroy lifecycle hooks on all controllers that have them 10642 forEach(elementControllers, function(controller) { 10643 var controllerInstance = controller.instance; 10644 if (isFunction(controllerInstance.$onChanges)) { 10645 try { 10646 controllerInstance.$onChanges(controller.bindingInfo.initialChanges); 10647 } catch (e) { 10648 $exceptionHandler(e); 10649 } 10650 } 10651 if (isFunction(controllerInstance.$onInit)) { 10652 try { 10653 controllerInstance.$onInit(); 10654 } catch (e) { 10655 $exceptionHandler(e); 10656 } 10657 } 10658 if (isFunction(controllerInstance.$doCheck)) { 10659 controllerScope.$watch(function() { controllerInstance.$doCheck(); }); 10660 controllerInstance.$doCheck(); 10661 } 10662 if (isFunction(controllerInstance.$onDestroy)) { 10663 controllerScope.$on('$destroy', function callOnDestroyHook() { 10664 controllerInstance.$onDestroy(); 10665 }); 10666 } 10667 }); 10668 10669 // PRELINKING 10670 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 10671 linkFn = preLinkFns[i]; 10672 invokeLinkFn(linkFn, 10673 linkFn.isolateScope ? isolateScope : scope, 10674 $element, 10675 attrs, 10676 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 10677 transcludeFn 10678 ); 10679 } 10680 10681 // RECURSION 10682 // We only pass the isolate scope, if the isolate directive has a template, 10683 // otherwise the child elements do not belong to the isolate directive. 10684 var scopeToChild = scope; 10685 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 10686 scopeToChild = isolateScope; 10687 } 10688 if (childLinkFn) { 10689 childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 10690 } 10691 10692 // POSTLINKING 10693 for (i = postLinkFns.length - 1; i >= 0; i--) { 10694 linkFn = postLinkFns[i]; 10695 invokeLinkFn(linkFn, 10696 linkFn.isolateScope ? isolateScope : scope, 10697 $element, 10698 attrs, 10699 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 10700 transcludeFn 10701 ); 10702 } 10703 10704 // Trigger $postLink lifecycle hooks 10705 forEach(elementControllers, function(controller) { 10706 var controllerInstance = controller.instance; 10707 if (isFunction(controllerInstance.$postLink)) { 10708 controllerInstance.$postLink(); 10709 } 10710 }); 10711 10712 // This is the function that is injected as `$transclude`. 10713 // Note: all arguments are optional! 10714 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 10715 var transcludeControllers; 10716 // No scope passed in: 10717 if (!isScope(scope)) { 10718 slotName = futureParentElement; 10719 futureParentElement = cloneAttachFn; 10720 cloneAttachFn = scope; 10721 scope = undefined; 10722 } 10723 10724 if (hasElementTranscludeDirective) { 10725 transcludeControllers = elementControllers; 10726 } 10727 if (!futureParentElement) { 10728 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 10729 } 10730 if (slotName) { 10731 // slotTranscludeFn can be one of three things: 10732 // * a transclude function - a filled slot 10733 // * `null` - an optional slot that was not filled 10734 // * `undefined` - a slot that was not declared (i.e. invalid) 10735 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 10736 if (slotTranscludeFn) { 10737 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 10738 } else if (isUndefined(slotTranscludeFn)) { 10739 throw $compileMinErr('noslot', 10740 'No parent directive that requires a transclusion with slot name "{0}". ' + 10741 'Element: {1}', 10742 slotName, startingTag($element)); 10743 } 10744 } else { 10745 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 10746 } 10747 } 10748 } 10749 } 10750 10751 function getControllers(directiveName, require, $element, elementControllers) { 10752 var value; 10753 10754 if (isString(require)) { 10755 var match = require.match(REQUIRE_PREFIX_REGEXP); 10756 var name = require.substring(match[0].length); 10757 var inheritType = match[1] || match[3]; 10758 var optional = match[2] === '?'; 10759 10760 //If only parents then start at the parent element 10761 if (inheritType === '^^') { 10762 $element = $element.parent(); 10763 //Otherwise attempt getting the controller from elementControllers in case 10764 //the element is transcluded (and has no data) and to avoid .data if possible 10765 } else { 10766 value = elementControllers && elementControllers[name]; 10767 value = value && value.instance; 10768 } 10769 10770 if (!value) { 10771 var dataName = '$' + name + 'Controller'; 10772 10773 if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) { 10774 // inheritedData() uses the documentElement when it finds the document, so we would 10775 // require from the element itself. 10776 value = null; 10777 } else { 10778 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 10779 } 10780 } 10781 10782 if (!value && !optional) { 10783 throw $compileMinErr('ctreq', 10784 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!', 10785 name, directiveName); 10786 } 10787 } else if (isArray(require)) { 10788 value = []; 10789 for (var i = 0, ii = require.length; i < ii; i++) { 10790 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 10791 } 10792 } else if (isObject(require)) { 10793 value = {}; 10794 forEach(require, function(controller, property) { 10795 value[property] = getControllers(directiveName, controller, $element, elementControllers); 10796 }); 10797 } 10798 10799 return value || null; 10800 } 10801 10802 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { 10803 var elementControllers = createMap(); 10804 for (var controllerKey in controllerDirectives) { 10805 var directive = controllerDirectives[controllerKey]; 10806 var locals = { 10807 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 10808 $element: $element, 10809 $attrs: attrs, 10810 $transclude: transcludeFn 10811 }; 10812 10813 var controller = directive.controller; 10814 if (controller === '@') { 10815 controller = attrs[directive.name]; 10816 } 10817 10818 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 10819 10820 // For directives with element transclusion the element is a comment. 10821 // In this case .data will not attach any data. 10822 // Instead, we save the controllers for the element in a local hash and attach to .data 10823 // later, once we have the actual element. 10824 elementControllers[directive.name] = controllerInstance; 10825 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 10826 } 10827 return elementControllers; 10828 } 10829 10830 // Depending upon the context in which a directive finds itself it might need to have a new isolated 10831 // or child scope created. For instance: 10832 // * if the directive has been pulled into a template because another directive with a higher priority 10833 // asked for element transclusion 10834 // * if the directive itself asks for transclusion but it is at the root of a template and the original 10835 // element was replaced. See https://github.com/angular/angular.js/issues/12936 10836 function markDirectiveScope(directives, isolateScope, newScope) { 10837 for (var j = 0, jj = directives.length; j < jj; j++) { 10838 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 10839 } 10840 } 10841 10842 /** 10843 * looks up the directive and decorates it with exception handling and proper parameters. We 10844 * call this the boundDirective. 10845 * 10846 * @param {string} name name of the directive to look up. 10847 * @param {string} location The directive must be found in specific format. 10848 * String containing any of theses characters: 10849 * 10850 * * `E`: element name 10851 * * `A': attribute 10852 * * `C`: class 10853 * * `M`: comment 10854 * @returns {boolean} true if directive was added. 10855 */ 10856 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 10857 endAttrName) { 10858 if (name === ignoreDirective) return null; 10859 var match = null; 10860 if (hasDirectives.hasOwnProperty(name)) { 10861 for (var directive, directives = $injector.get(name + Suffix), 10862 i = 0, ii = directives.length; i < ii; i++) { 10863 directive = directives[i]; 10864 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 10865 directive.restrict.indexOf(location) !== -1) { 10866 if (startAttrName) { 10867 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 10868 } 10869 if (!directive.$$bindings) { 10870 var bindings = directive.$$bindings = 10871 parseDirectiveBindings(directive, directive.name); 10872 if (isObject(bindings.isolateScope)) { 10873 directive.$$isolateBindings = bindings.isolateScope; 10874 } 10875 } 10876 tDirectives.push(directive); 10877 match = directive; 10878 } 10879 } 10880 } 10881 return match; 10882 } 10883 10884 10885 /** 10886 * looks up the directive and returns true if it is a multi-element directive, 10887 * and therefore requires DOM nodes between -start and -end markers to be grouped 10888 * together. 10889 * 10890 * @param {string} name name of the directive to look up. 10891 * @returns true if directive was registered as multi-element. 10892 */ 10893 function directiveIsMultiElement(name) { 10894 if (hasDirectives.hasOwnProperty(name)) { 10895 for (var directive, directives = $injector.get(name + Suffix), 10896 i = 0, ii = directives.length; i < ii; i++) { 10897 directive = directives[i]; 10898 if (directive.multiElement) { 10899 return true; 10900 } 10901 } 10902 } 10903 return false; 10904 } 10905 10906 /** 10907 * When the element is replaced with HTML template then the new attributes 10908 * on the template need to be merged with the existing attributes in the DOM. 10909 * The desired effect is to have both of the attributes present. 10910 * 10911 * @param {object} dst destination attributes (original DOM) 10912 * @param {object} src source attributes (from the directive template) 10913 */ 10914 function mergeTemplateAttributes(dst, src) { 10915 var srcAttr = src.$attr, 10916 dstAttr = dst.$attr; 10917 10918 // reapply the old attributes to the new element 10919 forEach(dst, function(value, key) { 10920 if (key.charAt(0) !== '$') { 10921 if (src[key] && src[key] !== value) { 10922 if (value.length) { 10923 value += (key === 'style' ? ';' : ' ') + src[key]; 10924 } else { 10925 value = src[key]; 10926 } 10927 } 10928 dst.$set(key, value, true, srcAttr[key]); 10929 } 10930 }); 10931 10932 // copy the new attributes on the old attrs object 10933 forEach(src, function(value, key) { 10934 // Check if we already set this attribute in the loop above. 10935 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 10936 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 10937 // have an attribute like "has-own-property" or "data-has-own-property", etc. 10938 if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') { 10939 dst[key] = value; 10940 10941 if (key !== 'class' && key !== 'style') { 10942 dstAttr[key] = srcAttr[key]; 10943 } 10944 } 10945 }); 10946 } 10947 10948 10949 function compileTemplateUrl(directives, $compileNode, tAttrs, 10950 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 10951 var linkQueue = [], 10952 afterTemplateNodeLinkFn, 10953 afterTemplateChildLinkFn, 10954 beforeTemplateCompileNode = $compileNode[0], 10955 origAsyncDirective = directives.shift(), 10956 derivedSyncDirective = inherit(origAsyncDirective, { 10957 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 10958 }), 10959 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 10960 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 10961 : origAsyncDirective.templateUrl, 10962 templateNamespace = origAsyncDirective.templateNamespace; 10963 10964 $compileNode.empty(); 10965 10966 $templateRequest(templateUrl) 10967 .then(function(content) { 10968 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 10969 10970 content = denormalizeTemplate(content); 10971 10972 if (origAsyncDirective.replace) { 10973 if (jqLiteIsTextNode(content)) { 10974 $template = []; 10975 } else { 10976 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 10977 } 10978 compileNode = $template[0]; 10979 10980 if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 10981 throw $compileMinErr('tplrt', 10982 'Template for directive \'{0}\' must have exactly one root element. {1}', 10983 origAsyncDirective.name, templateUrl); 10984 } 10985 10986 tempTemplateAttrs = {$attr: {}}; 10987 replaceWith($rootElement, $compileNode, compileNode); 10988 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 10989 10990 if (isObject(origAsyncDirective.scope)) { 10991 // the original directive that caused the template to be loaded async required 10992 // an isolate scope 10993 markDirectiveScope(templateDirectives, true); 10994 } 10995 directives = templateDirectives.concat(directives); 10996 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 10997 } else { 10998 compileNode = beforeTemplateCompileNode; 10999 $compileNode.html(content); 11000 } 11001 11002 directives.unshift(derivedSyncDirective); 11003 11004 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 11005 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 11006 previousCompileContext); 11007 forEach($rootElement, function(node, i) { 11008 if (node === compileNode) { 11009 $rootElement[i] = $compileNode[0]; 11010 } 11011 }); 11012 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 11013 11014 while (linkQueue.length) { 11015 var scope = linkQueue.shift(), 11016 beforeTemplateLinkNode = linkQueue.shift(), 11017 linkRootElement = linkQueue.shift(), 11018 boundTranscludeFn = linkQueue.shift(), 11019 linkNode = $compileNode[0]; 11020 11021 if (scope.$$destroyed) continue; 11022 11023 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 11024 var oldClasses = beforeTemplateLinkNode.className; 11025 11026 if (!(previousCompileContext.hasElementTranscludeDirective && 11027 origAsyncDirective.replace)) { 11028 // it was cloned therefore we have to clone as well. 11029 linkNode = jqLiteClone(compileNode); 11030 } 11031 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 11032 11033 // Copy in CSS classes from original node 11034 safeAddClass(jqLite(linkNode), oldClasses); 11035 } 11036 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 11037 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 11038 } else { 11039 childBoundTranscludeFn = boundTranscludeFn; 11040 } 11041 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 11042 childBoundTranscludeFn); 11043 } 11044 linkQueue = null; 11045 }).catch(function(error) { 11046 if (isError(error)) { 11047 $exceptionHandler(error); 11048 } 11049 }); 11050 11051 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 11052 var childBoundTranscludeFn = boundTranscludeFn; 11053 if (scope.$$destroyed) return; 11054 if (linkQueue) { 11055 linkQueue.push(scope, 11056 node, 11057 rootElement, 11058 childBoundTranscludeFn); 11059 } else { 11060 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 11061 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 11062 } 11063 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 11064 } 11065 }; 11066 } 11067 11068 11069 /** 11070 * Sorting function for bound directives. 11071 */ 11072 function byPriority(a, b) { 11073 var diff = b.priority - a.priority; 11074 if (diff !== 0) return diff; 11075 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 11076 return a.index - b.index; 11077 } 11078 11079 function assertNoDuplicate(what, previousDirective, directive, element) { 11080 11081 function wrapModuleNameIfDefined(moduleName) { 11082 return moduleName ? 11083 (' (module: ' + moduleName + ')') : 11084 ''; 11085 } 11086 11087 if (previousDirective) { 11088 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 11089 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 11090 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 11091 } 11092 } 11093 11094 11095 function addTextInterpolateDirective(directives, text) { 11096 var interpolateFn = $interpolate(text, true); 11097 if (interpolateFn) { 11098 directives.push({ 11099 priority: 0, 11100 compile: function textInterpolateCompileFn(templateNode) { 11101 var templateNodeParent = templateNode.parent(), 11102 hasCompileParent = !!templateNodeParent.length; 11103 11104 // When transcluding a template that has bindings in the root 11105 // we don't have a parent and thus need to add the class during linking fn. 11106 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 11107 11108 return function textInterpolateLinkFn(scope, node) { 11109 var parent = node.parent(); 11110 if (!hasCompileParent) compile.$$addBindingClass(parent); 11111 compile.$$addBindingInfo(parent, interpolateFn.expressions); 11112 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 11113 node[0].nodeValue = value; 11114 }); 11115 }; 11116 } 11117 }); 11118 } 11119 } 11120 11121 11122 function wrapTemplate(type, template) { 11123 type = lowercase(type || 'html'); 11124 switch (type) { 11125 case 'svg': 11126 case 'math': 11127 var wrapper = window.document.createElement('div'); 11128 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 11129 return wrapper.childNodes[0].childNodes; 11130 default: 11131 return template; 11132 } 11133 } 11134 11135 11136 function getTrustedAttrContext(nodeName, attrNormalizedName) { 11137 if (attrNormalizedName === 'srcdoc') { 11138 return $sce.HTML; 11139 } 11140 // All nodes with src attributes require a RESOURCE_URL value, except for 11141 // img and various html5 media nodes, which require the MEDIA_URL context. 11142 if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { 11143 if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) { 11144 return $sce.RESOURCE_URL; 11145 } 11146 return $sce.MEDIA_URL; 11147 } else if (attrNormalizedName === 'xlinkHref') { 11148 // Some xlink:href are okay, most aren't 11149 if (nodeName === 'image') return $sce.MEDIA_URL; 11150 if (nodeName === 'a') return $sce.URL; 11151 return $sce.RESOURCE_URL; 11152 } else if ( 11153 // Formaction 11154 (nodeName === 'form' && attrNormalizedName === 'action') || 11155 // If relative URLs can go where they are not expected to, then 11156 // all sorts of trust issues can arise. 11157 (nodeName === 'base' && attrNormalizedName === 'href') || 11158 // links can be stylesheets or imports, which can run script in the current origin 11159 (nodeName === 'link' && attrNormalizedName === 'href') 11160 ) { 11161 return $sce.RESOURCE_URL; 11162 } else if (nodeName === 'a' && (attrNormalizedName === 'href' || 11163 attrNormalizedName === 'ngHref')) { 11164 return $sce.URL; 11165 } 11166 } 11167 11168 function getTrustedPropContext(nodeName, propNormalizedName) { 11169 var prop = propNormalizedName.toLowerCase(); 11170 return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop]; 11171 } 11172 11173 function sanitizeSrcsetPropertyValue(value) { 11174 return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset'); 11175 } 11176 function addPropertyDirective(node, directives, attrName, propName) { 11177 if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) { 11178 throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed'); 11179 } 11180 11181 var nodeName = nodeName_(node); 11182 var trustedContext = getTrustedPropContext(nodeName, propName); 11183 11184 var sanitizer = identity; 11185 // Sanitize img[srcset] + source[srcset] values. 11186 if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) { 11187 sanitizer = sanitizeSrcsetPropertyValue; 11188 } else if (trustedContext) { 11189 sanitizer = $sce.getTrusted.bind($sce, trustedContext); 11190 } 11191 11192 directives.push({ 11193 priority: 100, 11194 compile: function ngPropCompileFn(_, attr) { 11195 var ngPropGetter = $parse(attr[attrName]); 11196 var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) { 11197 // Unwrap the value to compare the actual inner safe value, not the wrapper object. 11198 return $sce.valueOf(val); 11199 }); 11200 11201 return { 11202 pre: function ngPropPreLinkFn(scope, $element) { 11203 function applyPropValue() { 11204 var propValue = ngPropGetter(scope); 11205 $element[0][propName] = sanitizer(propValue); 11206 } 11207 11208 applyPropValue(); 11209 scope.$watch(ngPropWatch, applyPropValue); 11210 } 11211 }; 11212 } 11213 }); 11214 } 11215 11216 function addEventDirective(directives, attrName, eventName) { 11217 directives.push( 11218 createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false) 11219 ); 11220 } 11221 11222 function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { 11223 var nodeName = nodeName_(node); 11224 var trustedContext = getTrustedAttrContext(nodeName, name); 11225 var mustHaveExpression = !isNgAttr; 11226 var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; 11227 11228 var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing); 11229 11230 // no interpolation found -> ignore 11231 if (!interpolateFn) return; 11232 11233 if (name === 'multiple' && nodeName === 'select') { 11234 throw $compileMinErr('selmulti', 11235 'Binding to the \'multiple\' attribute is not supported. Element: {0}', 11236 startingTag(node)); 11237 } 11238 11239 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 11240 throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed'); 11241 } 11242 11243 directives.push({ 11244 priority: 100, 11245 compile: function() { 11246 return { 11247 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 11248 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 11249 11250 // If the attribute has changed since last $interpolate()ed 11251 var newValue = attr[name]; 11252 if (newValue !== value) { 11253 // we need to interpolate again since the attribute value has been updated 11254 // (e.g. by another directive's compile function) 11255 // ensure unset/empty values make interpolateFn falsy 11256 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 11257 value = newValue; 11258 } 11259 11260 // if attribute was updated so that there is no interpolation going on we don't want to 11261 // register any observers 11262 if (!interpolateFn) return; 11263 11264 // initialize attr object so that it's ready in case we need the value for isolate 11265 // scope initialization, otherwise the value would not be available from isolate 11266 // directive's linking fn during linking phase 11267 attr[name] = interpolateFn(scope); 11268 11269 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 11270 (attr.$$observers && attr.$$observers[name].$$scope || scope). 11271 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 11272 //special case for class attribute addition + removal 11273 //so that class changes can tap into the animation 11274 //hooks provided by the $animate service. Be sure to 11275 //skip animations when the first digest occurs (when 11276 //both the new and the old values are the same) since 11277 //the CSS classes are the non-interpolated values 11278 if (name === 'class' && newValue !== oldValue) { 11279 attr.$updateClass(newValue, oldValue); 11280 } else { 11281 attr.$set(name, newValue); 11282 } 11283 }); 11284 } 11285 }; 11286 } 11287 }); 11288 } 11289 11290 11291 /** 11292 * This is a special jqLite.replaceWith, which can replace items which 11293 * have no parents, provided that the containing jqLite collection is provided. 11294 * 11295 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 11296 * in the root of the tree. 11297 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 11298 * the shell, but replace its DOM node reference. 11299 * @param {Node} newNode The new DOM node. 11300 */ 11301 function replaceWith($rootElement, elementsToRemove, newNode) { 11302 var firstElementToRemove = elementsToRemove[0], 11303 removeCount = elementsToRemove.length, 11304 parent = firstElementToRemove.parentNode, 11305 i, ii; 11306 11307 if ($rootElement) { 11308 for (i = 0, ii = $rootElement.length; i < ii; i++) { 11309 if ($rootElement[i] === firstElementToRemove) { 11310 $rootElement[i++] = newNode; 11311 for (var j = i, j2 = j + removeCount - 1, 11312 jj = $rootElement.length; 11313 j < jj; j++, j2++) { 11314 if (j2 < jj) { 11315 $rootElement[j] = $rootElement[j2]; 11316 } else { 11317 delete $rootElement[j]; 11318 } 11319 } 11320 $rootElement.length -= removeCount - 1; 11321 11322 // If the replaced element is also the jQuery .context then replace it 11323 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 11324 // http://api.jquery.com/context/ 11325 if ($rootElement.context === firstElementToRemove) { 11326 $rootElement.context = newNode; 11327 } 11328 break; 11329 } 11330 } 11331 } 11332 11333 if (parent) { 11334 parent.replaceChild(newNode, firstElementToRemove); 11335 } 11336 11337 // Append all the `elementsToRemove` to a fragment. This will... 11338 // - remove them from the DOM 11339 // - allow them to still be traversed with .nextSibling 11340 // - allow a single fragment.qSA to fetch all elements being removed 11341 var fragment = window.document.createDocumentFragment(); 11342 for (i = 0; i < removeCount; i++) { 11343 fragment.appendChild(elementsToRemove[i]); 11344 } 11345 11346 if (jqLite.hasData(firstElementToRemove)) { 11347 // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private 11348 // data here because there's no public interface in jQuery to do that and copying over 11349 // event listeners (which is the main use of private data) wouldn't work anyway. 11350 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 11351 11352 // Remove $destroy event listeners from `firstElementToRemove` 11353 jqLite(firstElementToRemove).off('$destroy'); 11354 } 11355 11356 // Cleanup any data/listeners on the elements and children. 11357 // This includes invoking the $destroy event on any elements with listeners. 11358 jqLite.cleanData(fragment.querySelectorAll('*')); 11359 11360 // Update the jqLite collection to only contain the `newNode` 11361 for (i = 1; i < removeCount; i++) { 11362 delete elementsToRemove[i]; 11363 } 11364 elementsToRemove[0] = newNode; 11365 elementsToRemove.length = 1; 11366 } 11367 11368 11369 function cloneAndAnnotateFn(fn, annotation) { 11370 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 11371 } 11372 11373 11374 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 11375 try { 11376 linkFn(scope, $element, attrs, controllers, transcludeFn); 11377 } catch (e) { 11378 $exceptionHandler(e, startingTag($element)); 11379 } 11380 } 11381 11382 function strictBindingsCheck(attrName, directiveName) { 11383 if (strictComponentBindingsEnabled) { 11384 throw $compileMinErr('missingattr', 11385 'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!', 11386 attrName, directiveName); 11387 } 11388 } 11389 11390 // Set up $watches for isolate scope and controller bindings. 11391 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 11392 var removeWatchCollection = []; 11393 var initialChanges = {}; 11394 var changes; 11395 11396 forEach(bindings, function initializeBinding(definition, scopeName) { 11397 var attrName = definition.attrName, 11398 optional = definition.optional, 11399 mode = definition.mode, // @, =, <, or & 11400 lastValue, 11401 parentGet, parentSet, compare, removeWatch; 11402 11403 switch (mode) { 11404 11405 case '@': 11406 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 11407 strictBindingsCheck(attrName, directive.name); 11408 destination[scopeName] = attrs[attrName] = undefined; 11409 11410 } 11411 removeWatch = attrs.$observe(attrName, function(value) { 11412 if (isString(value) || isBoolean(value)) { 11413 var oldValue = destination[scopeName]; 11414 recordChanges(scopeName, value, oldValue); 11415 destination[scopeName] = value; 11416 } 11417 }); 11418 attrs.$$observers[attrName].$$scope = scope; 11419 lastValue = attrs[attrName]; 11420 if (isString(lastValue)) { 11421 // If the attribute has been provided then we trigger an interpolation to ensure 11422 // the value is there for use in the link fn 11423 destination[scopeName] = $interpolate(lastValue)(scope); 11424 } else if (isBoolean(lastValue)) { 11425 // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted 11426 // the value to boolean rather than a string, so we special case this situation 11427 destination[scopeName] = lastValue; 11428 } 11429 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 11430 removeWatchCollection.push(removeWatch); 11431 break; 11432 11433 case '=': 11434 if (!hasOwnProperty.call(attrs, attrName)) { 11435 if (optional) break; 11436 strictBindingsCheck(attrName, directive.name); 11437 attrs[attrName] = undefined; 11438 } 11439 if (optional && !attrs[attrName]) break; 11440 11441 parentGet = $parse(attrs[attrName]); 11442 if (parentGet.literal) { 11443 compare = equals; 11444 } else { 11445 compare = simpleCompare; 11446 } 11447 parentSet = parentGet.assign || function() { 11448 // reset the change, or we will throw this exception on every $digest 11449 lastValue = destination[scopeName] = parentGet(scope); 11450 throw $compileMinErr('nonassign', 11451 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!', 11452 attrs[attrName], attrName, directive.name); 11453 }; 11454 lastValue = destination[scopeName] = parentGet(scope); 11455 var parentValueWatch = function parentValueWatch(parentValue) { 11456 if (!compare(parentValue, destination[scopeName])) { 11457 // we are out of sync and need to copy 11458 if (!compare(parentValue, lastValue)) { 11459 // parent changed and it has precedence 11460 destination[scopeName] = parentValue; 11461 } else { 11462 // if the parent can be assigned then do so 11463 parentSet(scope, parentValue = destination[scopeName]); 11464 } 11465 } 11466 lastValue = parentValue; 11467 return lastValue; 11468 }; 11469 parentValueWatch.$stateful = true; 11470 if (definition.collection) { 11471 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 11472 } else { 11473 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 11474 } 11475 removeWatchCollection.push(removeWatch); 11476 break; 11477 11478 case '<': 11479 if (!hasOwnProperty.call(attrs, attrName)) { 11480 if (optional) break; 11481 strictBindingsCheck(attrName, directive.name); 11482 attrs[attrName] = undefined; 11483 } 11484 if (optional && !attrs[attrName]) break; 11485 11486 parentGet = $parse(attrs[attrName]); 11487 var isLiteral = parentGet.literal; 11488 11489 var initialValue = destination[scopeName] = parentGet(scope); 11490 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 11491 11492 removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) { 11493 if (oldValue === newValue) { 11494 if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) { 11495 return; 11496 } 11497 oldValue = initialValue; 11498 } 11499 recordChanges(scopeName, newValue, oldValue); 11500 destination[scopeName] = newValue; 11501 }); 11502 11503 removeWatchCollection.push(removeWatch); 11504 break; 11505 11506 case '&': 11507 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 11508 strictBindingsCheck(attrName, directive.name); 11509 } 11510 // Don't assign Object.prototype method to scope 11511 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 11512 11513 // Don't assign noop to destination if expression is not valid 11514 if (parentGet === noop && optional) break; 11515 11516 destination[scopeName] = function(locals) { 11517 return parentGet(scope, locals); 11518 }; 11519 break; 11520 } 11521 }); 11522 11523 function recordChanges(key, currentValue, previousValue) { 11524 if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) { 11525 // If we have not already scheduled the top level onChangesQueue handler then do so now 11526 if (!onChangesQueue) { 11527 scope.$$postDigest(flushOnChangesQueue); 11528 onChangesQueue = []; 11529 } 11530 // If we have not already queued a trigger of onChanges for this controller then do so now 11531 if (!changes) { 11532 changes = {}; 11533 onChangesQueue.push(triggerOnChangesHook); 11534 } 11535 // If the has been a change on this property already then we need to reuse the previous value 11536 if (changes[key]) { 11537 previousValue = changes[key].previousValue; 11538 } 11539 // Store this change 11540 changes[key] = new SimpleChange(previousValue, currentValue); 11541 } 11542 } 11543 11544 function triggerOnChangesHook() { 11545 destination.$onChanges(changes); 11546 // Now clear the changes so that we schedule onChanges when more changes arrive 11547 changes = undefined; 11548 } 11549 11550 return { 11551 initialChanges: initialChanges, 11552 removeWatches: removeWatchCollection.length && function removeWatches() { 11553 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 11554 removeWatchCollection[i](); 11555 } 11556 } 11557 }; 11558 } 11559 }]; 11560} 11561 11562function SimpleChange(previous, current) { 11563 this.previousValue = previous; 11564 this.currentValue = current; 11565} 11566SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; 11567 11568 11569var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; 11570var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; 11571 11572/** 11573 * Converts all accepted directives format into proper directive name. 11574 * @param name Name to normalize 11575 */ 11576function directiveNormalize(name) { 11577 return name 11578 .replace(PREFIX_REGEXP, '') 11579 .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) { 11580 return offset ? letter.toUpperCase() : letter; 11581 }); 11582} 11583 11584/** 11585 * @ngdoc type 11586 * @name $compile.directive.Attributes 11587 * 11588 * @description 11589 * A shared object between directive compile / linking functions which contains normalized DOM 11590 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 11591 * needed since all of these are treated as equivalent in AngularJS: 11592 * 11593 * ``` 11594 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 11595 * ``` 11596 */ 11597 11598/** 11599 * @ngdoc property 11600 * @name $compile.directive.Attributes#$attr 11601 * 11602 * @description 11603 * A map of DOM element attribute names to the normalized name. This is 11604 * needed to do reverse lookup from normalized name back to actual name. 11605 */ 11606 11607 11608/** 11609 * @ngdoc method 11610 * @name $compile.directive.Attributes#$set 11611 * @kind function 11612 * 11613 * @description 11614 * Set DOM element attribute value. 11615 * 11616 * 11617 * @param {string} name Normalized element attribute name of the property to modify. The name is 11618 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 11619 * property to the original name. 11620 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 11621 */ 11622 11623 11624 11625/** 11626 * Closure compiler type information 11627 */ 11628 11629function nodesetLinkingFn( 11630 /* angular.Scope */ scope, 11631 /* NodeList */ nodeList, 11632 /* Element */ rootElement, 11633 /* function(Function) */ boundTranscludeFn 11634) {} 11635 11636function directiveLinkingFn( 11637 /* nodesetLinkingFn */ nodesetLinkingFn, 11638 /* angular.Scope */ scope, 11639 /* Node */ node, 11640 /* Element */ rootElement, 11641 /* function(Function) */ boundTranscludeFn 11642) {} 11643 11644function tokenDifference(str1, str2) { 11645 var values = '', 11646 tokens1 = str1.split(/\s+/), 11647 tokens2 = str2.split(/\s+/); 11648 11649 outer: 11650 for (var i = 0; i < tokens1.length; i++) { 11651 var token = tokens1[i]; 11652 for (var j = 0; j < tokens2.length; j++) { 11653 if (token === tokens2[j]) continue outer; 11654 } 11655 values += (values.length > 0 ? ' ' : '') + token; 11656 } 11657 return values; 11658} 11659 11660function removeComments(jqNodes) { 11661 jqNodes = jqLite(jqNodes); 11662 var i = jqNodes.length; 11663 11664 if (i <= 1) { 11665 return jqNodes; 11666 } 11667 11668 while (i--) { 11669 var node = jqNodes[i]; 11670 if (node.nodeType === NODE_TYPE_COMMENT || 11671 (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) { 11672 splice.call(jqNodes, i, 1); 11673 } 11674 } 11675 return jqNodes; 11676} 11677 11678var $controllerMinErr = minErr('$controller'); 11679 11680 11681var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 11682function identifierForController(controller, ident) { 11683 if (ident && isString(ident)) return ident; 11684 if (isString(controller)) { 11685 var match = CNTRL_REG.exec(controller); 11686 if (match) return match[3]; 11687 } 11688} 11689 11690 11691/** 11692 * @ngdoc provider 11693 * @name $controllerProvider 11694 * @this 11695 * 11696 * @description 11697 * The {@link ng.$controller $controller service} is used by AngularJS to create new 11698 * controllers. 11699 * 11700 * This provider allows controller registration via the 11701 * {@link ng.$controllerProvider#register register} method. 11702 */ 11703function $ControllerProvider() { 11704 var controllers = {}; 11705 11706 /** 11707 * @ngdoc method 11708 * @name $controllerProvider#has 11709 * @param {string} name Controller name to check. 11710 */ 11711 this.has = function(name) { 11712 return controllers.hasOwnProperty(name); 11713 }; 11714 11715 /** 11716 * @ngdoc method 11717 * @name $controllerProvider#register 11718 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 11719 * the names and the values are the constructors. 11720 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 11721 * annotations in the array notation). 11722 */ 11723 this.register = function(name, constructor) { 11724 assertNotHasOwnProperty(name, 'controller'); 11725 if (isObject(name)) { 11726 extend(controllers, name); 11727 } else { 11728 controllers[name] = constructor; 11729 } 11730 }; 11731 11732 this.$get = ['$injector', function($injector) { 11733 11734 /** 11735 * @ngdoc service 11736 * @name $controller 11737 * @requires $injector 11738 * 11739 * @param {Function|string} constructor If called with a function then it's considered to be the 11740 * controller constructor function. Otherwise it's considered to be a string which is used 11741 * to retrieve the controller constructor using the following steps: 11742 * 11743 * * check if a controller with given name is registered via `$controllerProvider` 11744 * * check if evaluating the string on the current scope returns a constructor 11745 * 11746 * The string can use the `controller as property` syntax, where the controller instance is published 11747 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 11748 * to work correctly. 11749 * 11750 * @param {Object} locals Injection locals for Controller. 11751 * @return {Object} Instance of given controller. 11752 * 11753 * @description 11754 * `$controller` service is responsible for instantiating controllers. 11755 * 11756 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 11757 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 11758 */ 11759 return function $controller(expression, locals, later, ident) { 11760 // PRIVATE API: 11761 // param `later` --- indicates that the controller's constructor is invoked at a later time. 11762 // If true, $controller will allocate the object with the correct 11763 // prototype chain, but will not invoke the controller until a returned 11764 // callback is invoked. 11765 // param `ident` --- An optional label which overrides the label parsed from the controller 11766 // expression, if any. 11767 var instance, match, constructor, identifier; 11768 later = later === true; 11769 if (ident && isString(ident)) { 11770 identifier = ident; 11771 } 11772 11773 if (isString(expression)) { 11774 match = expression.match(CNTRL_REG); 11775 if (!match) { 11776 throw $controllerMinErr('ctrlfmt', 11777 'Badly formed controller string \'{0}\'. ' + 11778 'Must match `__name__ as __id__` or `__name__`.', expression); 11779 } 11780 constructor = match[1]; 11781 identifier = identifier || match[3]; 11782 expression = controllers.hasOwnProperty(constructor) 11783 ? controllers[constructor] 11784 : getter(locals.$scope, constructor, true); 11785 11786 if (!expression) { 11787 throw $controllerMinErr('ctrlreg', 11788 'The controller with the name \'{0}\' is not registered.', constructor); 11789 } 11790 11791 assertArgFn(expression, constructor, true); 11792 } 11793 11794 if (later) { 11795 // Instantiate controller later: 11796 // This machinery is used to create an instance of the object before calling the 11797 // controller's constructor itself. 11798 // 11799 // This allows properties to be added to the controller before the constructor is 11800 // invoked. Primarily, this is used for isolate scope bindings in $compile. 11801 // 11802 // This feature is not intended for use by applications, and is thus not documented 11803 // publicly. 11804 // Object creation: http://jsperf.com/create-constructor/2 11805 var controllerPrototype = (isArray(expression) ? 11806 expression[expression.length - 1] : expression).prototype; 11807 instance = Object.create(controllerPrototype || null); 11808 11809 if (identifier) { 11810 addIdentifier(locals, identifier, instance, constructor || expression.name); 11811 } 11812 11813 return extend(function $controllerInit() { 11814 var result = $injector.invoke(expression, instance, locals, constructor); 11815 if (result !== instance && (isObject(result) || isFunction(result))) { 11816 instance = result; 11817 if (identifier) { 11818 // If result changed, re-assign controllerAs value to scope. 11819 addIdentifier(locals, identifier, instance, constructor || expression.name); 11820 } 11821 } 11822 return instance; 11823 }, { 11824 instance: instance, 11825 identifier: identifier 11826 }); 11827 } 11828 11829 instance = $injector.instantiate(expression, locals, constructor); 11830 11831 if (identifier) { 11832 addIdentifier(locals, identifier, instance, constructor || expression.name); 11833 } 11834 11835 return instance; 11836 }; 11837 11838 function addIdentifier(locals, identifier, instance, name) { 11839 if (!(locals && isObject(locals.$scope))) { 11840 throw minErr('$controller')('noscp', 11841 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.', 11842 name, identifier); 11843 } 11844 11845 locals.$scope[identifier] = instance; 11846 } 11847 }]; 11848} 11849 11850/** 11851 * @ngdoc service 11852 * @name $document 11853 * @requires $window 11854 * @this 11855 * 11856 * @description 11857 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 11858 * 11859 * @example 11860 <example module="documentExample" name="document"> 11861 <file name="index.html"> 11862 <div ng-controller="ExampleController"> 11863 <p>$document title: <b ng-bind="title"></b></p> 11864 <p>window.document title: <b ng-bind="windowTitle"></b></p> 11865 </div> 11866 </file> 11867 <file name="script.js"> 11868 angular.module('documentExample', []) 11869 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 11870 $scope.title = $document[0].title; 11871 $scope.windowTitle = angular.element(window.document)[0].title; 11872 }]); 11873 </file> 11874 </example> 11875 */ 11876function $DocumentProvider() { 11877 this.$get = ['$window', function(window) { 11878 return jqLite(window.document); 11879 }]; 11880} 11881 11882 11883/** 11884 * @private 11885 * @this 11886 * Listens for document visibility change and makes the current status accessible. 11887 */ 11888function $$IsDocumentHiddenProvider() { 11889 this.$get = ['$document', '$rootScope', function($document, $rootScope) { 11890 var doc = $document[0]; 11891 var hidden = doc && doc.hidden; 11892 11893 $document.on('visibilitychange', changeListener); 11894 11895 $rootScope.$on('$destroy', function() { 11896 $document.off('visibilitychange', changeListener); 11897 }); 11898 11899 function changeListener() { 11900 hidden = doc.hidden; 11901 } 11902 11903 return function() { 11904 return hidden; 11905 }; 11906 }]; 11907} 11908 11909/** 11910 * @ngdoc service 11911 * @name $exceptionHandler 11912 * @requires ng.$log 11913 * @this 11914 * 11915 * @description 11916 * Any uncaught exception in AngularJS expressions is delegated to this service. 11917 * The default implementation simply delegates to `$log.error` which logs it into 11918 * the browser console. 11919 * 11920 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 11921 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 11922 * 11923 * ## Example: 11924 * 11925 * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught 11926 * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead 11927 * of `$log.error()`. 11928 * 11929 * ```js 11930 * angular. 11931 * module('exceptionOverwrite', []). 11932 * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) { 11933 * return function myExceptionHandler(exception, cause) { 11934 * logErrorsToBackend(exception, cause); 11935 * $log.warn(exception, cause); 11936 * }; 11937 * }]); 11938 * ``` 11939 * 11940 * <hr /> 11941 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 11942 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 11943 * (unless executed during a digest). 11944 * 11945 * If you wish, you can manually delegate exceptions, e.g. 11946 * `try { ... } catch(e) { $exceptionHandler(e); }` 11947 * 11948 * @param {Error} exception Exception associated with the error. 11949 * @param {string=} cause Optional information about the context in which 11950 * the error was thrown. 11951 * 11952 */ 11953function $ExceptionHandlerProvider() { 11954 this.$get = ['$log', function($log) { 11955 return function(exception, cause) { 11956 $log.error.apply($log, arguments); 11957 }; 11958 }]; 11959} 11960 11961var $$ForceReflowProvider = /** @this */ function() { 11962 this.$get = ['$document', function($document) { 11963 return function(domNode) { 11964 //the line below will force the browser to perform a repaint so 11965 //that all the animated elements within the animation frame will 11966 //be properly updated and drawn on screen. This is required to 11967 //ensure that the preparation animation is properly flushed so that 11968 //the active state picks up from there. DO NOT REMOVE THIS LINE. 11969 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 11970 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 11971 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 11972 if (domNode) { 11973 if (!domNode.nodeType && domNode instanceof jqLite) { 11974 domNode = domNode[0]; 11975 } 11976 } else { 11977 domNode = $document[0].body; 11978 } 11979 return domNode.offsetWidth + 1; 11980 }; 11981 }]; 11982}; 11983 11984var APPLICATION_JSON = 'application/json'; 11985var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 11986var JSON_START = /^\[|^\{(?!\{)/; 11987var JSON_ENDS = { 11988 '[': /]$/, 11989 '{': /}$/ 11990}; 11991var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/; 11992var $httpMinErr = minErr('$http'); 11993 11994function serializeValue(v) { 11995 if (isObject(v)) { 11996 return isDate(v) ? v.toISOString() : toJson(v); 11997 } 11998 return v; 11999} 12000 12001 12002/** @this */ 12003function $HttpParamSerializerProvider() { 12004 /** 12005 * @ngdoc service 12006 * @name $httpParamSerializer 12007 * @description 12008 * 12009 * Default {@link $http `$http`} params serializer that converts objects to strings 12010 * according to the following rules: 12011 * 12012 * * `{'foo': 'bar'}` results in `foo=bar` 12013 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 12014 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 12015 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object) 12016 * 12017 * Note that serializer will sort the request parameters alphabetically. 12018 */ 12019 12020 this.$get = function() { 12021 return function ngParamSerializer(params) { 12022 if (!params) return ''; 12023 var parts = []; 12024 forEachSorted(params, function(value, key) { 12025 if (value === null || isUndefined(value) || isFunction(value)) return; 12026 if (isArray(value)) { 12027 forEach(value, function(v) { 12028 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 12029 }); 12030 } else { 12031 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 12032 } 12033 }); 12034 12035 return parts.join('&'); 12036 }; 12037 }; 12038} 12039 12040/** @this */ 12041function $HttpParamSerializerJQLikeProvider() { 12042 /** 12043 * @ngdoc service 12044 * @name $httpParamSerializerJQLike 12045 * 12046 * @description 12047 * 12048 * Alternative {@link $http `$http`} params serializer that follows 12049 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 12050 * The serializer will also sort the params alphabetically. 12051 * 12052 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 12053 * 12054 * ```js 12055 * $http({ 12056 * url: myUrl, 12057 * method: 'GET', 12058 * params: myParams, 12059 * paramSerializer: '$httpParamSerializerJQLike' 12060 * }); 12061 * ``` 12062 * 12063 * It is also possible to set it as the default `paramSerializer` in the 12064 * {@link $httpProvider#defaults `$httpProvider`}. 12065 * 12066 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 12067 * form data for submission: 12068 * 12069 * ```js 12070 * .controller(function($http, $httpParamSerializerJQLike) { 12071 * //... 12072 * 12073 * $http({ 12074 * url: myUrl, 12075 * method: 'POST', 12076 * data: $httpParamSerializerJQLike(myData), 12077 * headers: { 12078 * 'Content-Type': 'application/x-www-form-urlencoded' 12079 * } 12080 * }); 12081 * 12082 * }); 12083 * ``` 12084 * 12085 */ 12086 this.$get = function() { 12087 return function jQueryLikeParamSerializer(params) { 12088 if (!params) return ''; 12089 var parts = []; 12090 serialize(params, '', true); 12091 return parts.join('&'); 12092 12093 function serialize(toSerialize, prefix, topLevel) { 12094 if (isArray(toSerialize)) { 12095 forEach(toSerialize, function(value, index) { 12096 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 12097 }); 12098 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 12099 forEachSorted(toSerialize, function(value, key) { 12100 serialize(value, prefix + 12101 (topLevel ? '' : '[') + 12102 key + 12103 (topLevel ? '' : ']')); 12104 }); 12105 } else { 12106 if (isFunction(toSerialize)) { 12107 toSerialize = toSerialize(); 12108 } 12109 parts.push(encodeUriQuery(prefix) + '=' + 12110 (toSerialize == null ? '' : encodeUriQuery(serializeValue(toSerialize)))); 12111 } 12112 } 12113 }; 12114 }; 12115} 12116 12117function defaultHttpResponseTransform(data, headers) { 12118 if (isString(data)) { 12119 // Strip json vulnerability protection prefix and trim whitespace 12120 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 12121 12122 if (tempData) { 12123 var contentType = headers('Content-Type'); 12124 var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0); 12125 12126 if (hasJsonContentType || isJsonLike(tempData)) { 12127 try { 12128 data = fromJson(tempData); 12129 } catch (e) { 12130 if (!hasJsonContentType) { 12131 return data; 12132 } 12133 throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' + 12134 'Parse error: "{1}"', data, e); 12135 } 12136 } 12137 } 12138 } 12139 12140 return data; 12141} 12142 12143function isJsonLike(str) { 12144 var jsonStart = str.match(JSON_START); 12145 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 12146} 12147 12148/** 12149 * Parse headers into key value object 12150 * 12151 * @param {string} headers Raw headers as a string 12152 * @returns {Object} Parsed headers as key value object 12153 */ 12154function parseHeaders(headers) { 12155 var parsed = createMap(), i; 12156 12157 function fillInParsed(key, val) { 12158 if (key) { 12159 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 12160 } 12161 } 12162 12163 if (isString(headers)) { 12164 forEach(headers.split('\n'), function(line) { 12165 i = line.indexOf(':'); 12166 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 12167 }); 12168 } else if (isObject(headers)) { 12169 forEach(headers, function(headerVal, headerKey) { 12170 fillInParsed(lowercase(headerKey), trim(headerVal)); 12171 }); 12172 } 12173 12174 return parsed; 12175} 12176 12177 12178/** 12179 * Returns a function that provides access to parsed headers. 12180 * 12181 * Headers are lazy parsed when first requested. 12182 * @see parseHeaders 12183 * 12184 * @param {(string|Object)} headers Headers to provide access to. 12185 * @returns {function(string=)} Returns a getter function which if called with: 12186 * 12187 * - if called with an argument returns a single header value or null 12188 * - if called with no arguments returns an object containing all headers. 12189 */ 12190function headersGetter(headers) { 12191 var headersObj; 12192 12193 return function(name) { 12194 if (!headersObj) headersObj = parseHeaders(headers); 12195 12196 if (name) { 12197 var value = headersObj[lowercase(name)]; 12198 if (value === undefined) { 12199 value = null; 12200 } 12201 return value; 12202 } 12203 12204 return headersObj; 12205 }; 12206} 12207 12208 12209/** 12210 * Chain all given functions 12211 * 12212 * This function is used for both request and response transforming 12213 * 12214 * @param {*} data Data to transform. 12215 * @param {function(string=)} headers HTTP headers getter fn. 12216 * @param {number} status HTTP status code of the response. 12217 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 12218 * @returns {*} Transformed data. 12219 */ 12220function transformData(data, headers, status, fns) { 12221 if (isFunction(fns)) { 12222 return fns(data, headers, status); 12223 } 12224 12225 forEach(fns, function(fn) { 12226 data = fn(data, headers, status); 12227 }); 12228 12229 return data; 12230} 12231 12232 12233function isSuccess(status) { 12234 return 200 <= status && status < 300; 12235} 12236 12237 12238/** 12239 * @ngdoc provider 12240 * @name $httpProvider 12241 * @this 12242 * 12243 * @description 12244 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 12245 */ 12246function $HttpProvider() { 12247 /** 12248 * @ngdoc property 12249 * @name $httpProvider#defaults 12250 * @description 12251 * 12252 * Object containing default values for all {@link ng.$http $http} requests. 12253 * 12254 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with 12255 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses 12256 * by default. See {@link $http#caching $http Caching} for more information. 12257 * 12258 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 12259 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 12260 * setting default headers. 12261 * - **`defaults.headers.common`** 12262 * - **`defaults.headers.post`** 12263 * - **`defaults.headers.put`** 12264 * - **`defaults.headers.patch`** 12265 * 12266 * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the 12267 * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the 12268 * {@link $jsonpCallbacks} service. Defaults to `'callback'`. 12269 * 12270 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 12271 * used to the prepare string representation of request parameters (specified as an object). 12272 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 12273 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 12274 * 12275 * - **`defaults.transformRequest`** - 12276 * `{Array<function(data, headersGetter)>|function(data, headersGetter)}` - 12277 * An array of functions (or a single function) which are applied to the request data. 12278 * By default, this is an array with one request transformation function: 12279 * 12280 * - If the `data` property of the request configuration object contains an object, serialize it 12281 * into JSON format. 12282 * 12283 * - **`defaults.transformResponse`** - 12284 * `{Array<function(data, headersGetter, status)>|function(data, headersGetter, status)}` - 12285 * An array of functions (or a single function) which are applied to the response data. By default, 12286 * this is an array which applies one response transformation function that does two things: 12287 * 12288 * - If XSRF prefix is detected, strip it 12289 * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}). 12290 * - If the `Content-Type` is `application/json` or the response looks like JSON, 12291 * deserialize it using a JSON parser. 12292 * 12293 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 12294 * Defaults value is `'XSRF-TOKEN'`. 12295 * 12296 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 12297 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 12298 * 12299 */ 12300 var defaults = this.defaults = { 12301 // transform incoming response data 12302 transformResponse: [defaultHttpResponseTransform], 12303 12304 // transform outgoing request data 12305 transformRequest: [function(d) { 12306 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 12307 }], 12308 12309 // default headers 12310 headers: { 12311 common: { 12312 'Accept': 'application/json, text/plain, */*' 12313 }, 12314 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 12315 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 12316 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 12317 }, 12318 12319 xsrfCookieName: 'XSRF-TOKEN', 12320 xsrfHeaderName: 'X-XSRF-TOKEN', 12321 12322 paramSerializer: '$httpParamSerializer', 12323 12324 jsonpCallbackParam: 'callback' 12325 }; 12326 12327 var useApplyAsync = false; 12328 /** 12329 * @ngdoc method 12330 * @name $httpProvider#useApplyAsync 12331 * @description 12332 * 12333 * Configure $http service to combine processing of multiple http responses received at around 12334 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 12335 * significant performance improvement for bigger applications that make many HTTP requests 12336 * concurrently (common during application bootstrap). 12337 * 12338 * Defaults to false. If no value is specified, returns the current configured value. 12339 * 12340 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 12341 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 12342 * to load and share the same digest cycle. 12343 * 12344 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 12345 * otherwise, returns the current configured value. 12346 */ 12347 this.useApplyAsync = function(value) { 12348 if (isDefined(value)) { 12349 useApplyAsync = !!value; 12350 return this; 12351 } 12352 return useApplyAsync; 12353 }; 12354 12355 /** 12356 * @ngdoc property 12357 * @name $httpProvider#interceptors 12358 * @description 12359 * 12360 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 12361 * pre-processing of request or postprocessing of responses. 12362 * 12363 * These service factories are ordered by request, i.e. they are applied in the same order as the 12364 * array, on request, but reverse order, on response. 12365 * 12366 * {@link ng.$http#interceptors Interceptors detailed info} 12367 */ 12368 var interceptorFactories = this.interceptors = []; 12369 12370 /** 12371 * @ngdoc property 12372 * @name $httpProvider#xsrfTrustedOrigins 12373 * @description 12374 * 12375 * Array containing URLs whose origins are trusted to receive the XSRF token. See the 12376 * {@link ng.$http#security-considerations Security Considerations} sections for more details on 12377 * XSRF. 12378 * 12379 * **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme), 12380 * the [hostname](https://en.wikipedia.org/wiki/Hostname) and the 12381 * [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and 12382 * `https:`, the port number can be omitted if using th default ports (80 and 443 respectively). 12383 * Examples: `http://example.com`, `https://api.example.com:9876` 12384 * 12385 * <div class="alert alert-warning"> 12386 * It is not possible to trust specific URLs/paths. The `path`, `query` and `fragment` parts 12387 * of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be 12388 * treated as `https://foo.com`, meaning that **all** requests to URLs starting with 12389 * `https://foo.com/` will include the XSRF token. 12390 * </div> 12391 * 12392 * @example 12393 * 12394 * ```js 12395 * // App served from `https://example.com/`. 12396 * angular. 12397 * module('xsrfTrustedOriginsExample', []). 12398 * config(['$httpProvider', function($httpProvider) { 12399 * $httpProvider.xsrfTrustedOrigins.push('https://api.example.com'); 12400 * }]). 12401 * run(['$http', function($http) { 12402 * // The XSRF token will be sent. 12403 * $http.get('https://api.example.com/preferences').then(...); 12404 * 12405 * // The XSRF token will NOT be sent. 12406 * $http.get('https://stats.example.com/activity').then(...); 12407 * }]); 12408 * ``` 12409 */ 12410 var xsrfTrustedOrigins = this.xsrfTrustedOrigins = []; 12411 12412 /** 12413 * @ngdoc property 12414 * @name $httpProvider#xsrfWhitelistedOrigins 12415 * @description 12416 * 12417 * @deprecated 12418 * sinceVersion="1.8.1" 12419 * 12420 * This property is deprecated. Use {@link $httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins} 12421 * instead. 12422 */ 12423 Object.defineProperty(this, 'xsrfWhitelistedOrigins', { 12424 get: function() { 12425 return this.xsrfTrustedOrigins; 12426 }, 12427 set: function(origins) { 12428 this.xsrfTrustedOrigins = origins; 12429 } 12430 }); 12431 12432 this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce', 12433 function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) { 12434 12435 var defaultCache = $cacheFactory('$http'); 12436 12437 /** 12438 * Make sure that default param serializer is exposed as a function 12439 */ 12440 defaults.paramSerializer = isString(defaults.paramSerializer) ? 12441 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 12442 12443 /** 12444 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 12445 * The reversal is needed so that we can build up the interception chain around the 12446 * server request. 12447 */ 12448 var reversedInterceptors = []; 12449 12450 forEach(interceptorFactories, function(interceptorFactory) { 12451 reversedInterceptors.unshift(isString(interceptorFactory) 12452 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 12453 }); 12454 12455 /** 12456 * A function to check request URLs against a list of allowed origins. 12457 */ 12458 var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfTrustedOrigins); 12459 12460 /** 12461 * @ngdoc service 12462 * @kind function 12463 * @name $http 12464 * @requires ng.$httpBackend 12465 * @requires $cacheFactory 12466 * @requires $rootScope 12467 * @requires $q 12468 * @requires $injector 12469 * 12470 * @description 12471 * The `$http` service is a core AngularJS service that facilitates communication with the remote 12472 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 12473 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 12474 * 12475 * For unit testing applications that use `$http` service, see 12476 * {@link ngMock.$httpBackend $httpBackend mock}. 12477 * 12478 * For a higher level of abstraction, please check out the {@link ngResource.$resource 12479 * $resource} service. 12480 * 12481 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 12482 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 12483 * it is important to familiarize yourself with these APIs and the guarantees they provide. 12484 * 12485 * 12486 * ## General usage 12487 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} — 12488 * that is used to generate an HTTP request and returns a {@link ng.$q promise} that is 12489 * resolved (request success) or rejected (request failure) with a 12490 * {@link ng.$http#$http-returns response} object. 12491 * 12492 * ```js 12493 * // Simple GET request example: 12494 * $http({ 12495 * method: 'GET', 12496 * url: '/someUrl' 12497 * }).then(function successCallback(response) { 12498 * // this callback will be called asynchronously 12499 * // when the response is available 12500 * }, function errorCallback(response) { 12501 * // called asynchronously if an error occurs 12502 * // or server returns response with an error status. 12503 * }); 12504 * ``` 12505 * 12506 * 12507 * ## Shortcut methods 12508 * 12509 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 12510 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 12511 * last argument. 12512 * 12513 * ```js 12514 * $http.get('/someUrl', config).then(successCallback, errorCallback); 12515 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 12516 * ``` 12517 * 12518 * Complete list of shortcut methods: 12519 * 12520 * - {@link ng.$http#get $http.get} 12521 * - {@link ng.$http#head $http.head} 12522 * - {@link ng.$http#post $http.post} 12523 * - {@link ng.$http#put $http.put} 12524 * - {@link ng.$http#delete $http.delete} 12525 * - {@link ng.$http#jsonp $http.jsonp} 12526 * - {@link ng.$http#patch $http.patch} 12527 * 12528 * 12529 * ## Writing Unit Tests that use $http 12530 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 12531 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 12532 * request using trained responses. 12533 * 12534 * ``` 12535 * $httpBackend.expectGET(...); 12536 * $http.get(...); 12537 * $httpBackend.flush(); 12538 * ``` 12539 * 12540 * ## Setting HTTP Headers 12541 * 12542 * The $http service will automatically add certain HTTP headers to all requests. These defaults 12543 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 12544 * object, which currently contains this default configuration: 12545 * 12546 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 12547 * - <code>Accept: application/json, text/plain, \*/\*</code> 12548 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 12549 * - `Content-Type: application/json` 12550 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 12551 * - `Content-Type: application/json` 12552 * 12553 * To add or overwrite these defaults, simply add or remove a property from these configuration 12554 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 12555 * with the lowercased HTTP method name as the key, e.g. 12556 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 12557 * 12558 * The defaults can also be set at runtime via the `$http.defaults` object in the same 12559 * fashion. For example: 12560 * 12561 * ``` 12562 * module.run(function($http) { 12563 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 12564 * }); 12565 * ``` 12566 * 12567 * In addition, you can supply a `headers` property in the config object passed when 12568 * calling `$http(config)`, which overrides the defaults without changing them globally. 12569 * 12570 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 12571 * Use the `headers` property, setting the desired header to `undefined`. For example: 12572 * 12573 * ```js 12574 * var req = { 12575 * method: 'POST', 12576 * url: 'http://example.com', 12577 * headers: { 12578 * 'Content-Type': undefined 12579 * }, 12580 * data: { test: 'test' } 12581 * } 12582 * 12583 * $http(req).then(function(){...}, function(){...}); 12584 * ``` 12585 * 12586 * ## Transforming Requests and Responses 12587 * 12588 * Both requests and responses can be transformed using transformation functions: `transformRequest` 12589 * and `transformResponse`. These properties can be a single function that returns 12590 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 12591 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 12592 * 12593 * <div class="alert alert-warning"> 12594 * **Note:** AngularJS does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. 12595 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). 12596 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest 12597 * function will be reflected on the scope and in any templates where the object is data-bound. 12598 * To prevent this, transform functions should have no side-effects. 12599 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. 12600 * </div> 12601 * 12602 * ### Default Transformations 12603 * 12604 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 12605 * `defaults.transformResponse` properties. If a request does not provide its own transformations 12606 * then these will be applied. 12607 * 12608 * You can augment or replace the default transformations by modifying these properties by adding to or 12609 * replacing the array. 12610 * 12611 * AngularJS provides the following default transformations: 12612 * 12613 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is 12614 * an array with one function that does the following: 12615 * 12616 * - If the `data` property of the request configuration object contains an object, serialize it 12617 * into JSON format. 12618 * 12619 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is 12620 * an array with one function that does the following: 12621 * 12622 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 12623 * - If the `Content-Type` is `application/json` or the response looks like JSON, 12624 * deserialize it using a JSON parser. 12625 * 12626 * 12627 * ### Overriding the Default Transformations Per Request 12628 * 12629 * If you wish to override the request/response transformations only for a single request then provide 12630 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 12631 * into `$http`. 12632 * 12633 * Note that if you provide these properties on the config object the default transformations will be 12634 * overwritten. If you wish to augment the default transformations then you must include them in your 12635 * local transformation array. 12636 * 12637 * The following code demonstrates adding a new response transformation to be run after the default response 12638 * transformations have been run. 12639 * 12640 * ```js 12641 * function appendTransform(defaults, transform) { 12642 * 12643 * // We can't guarantee that the default transformation is an array 12644 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 12645 * 12646 * // Append the new transformation to the defaults 12647 * return defaults.concat(transform); 12648 * } 12649 * 12650 * $http({ 12651 * url: '...', 12652 * method: 'GET', 12653 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 12654 * return doTransform(value); 12655 * }) 12656 * }); 12657 * ``` 12658 * 12659 * 12660 * ## Caching 12661 * 12662 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must 12663 * set the config.cache value or the default cache value to TRUE or to a cache object (created 12664 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes 12665 * precedence over the default cache value. 12666 * 12667 * In order to: 12668 * * cache all responses - set the default cache value to TRUE or to a cache object 12669 * * cache a specific response - set config.cache value to TRUE or to a cache object 12670 * 12671 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, 12672 * then the default `$cacheFactory("$http")` object is used. 12673 * 12674 * The default cache value can be set by updating the 12675 * {@link ng.$http#defaults `$http.defaults.cache`} property or the 12676 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. 12677 * 12678 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using 12679 * the relevant cache object. The next time the same request is made, the response is returned 12680 * from the cache without sending a request to the server. 12681 * 12682 * Take note that: 12683 * 12684 * * Only GET and JSONP requests are cached. 12685 * * The cache key is the request URL including search parameters; headers are not considered. 12686 * * Cached responses are returned asynchronously, in the same way as responses from the server. 12687 * * If multiple identical requests are made using the same cache, which is not yet populated, 12688 * one request will be made to the server and remaining requests will return the same response. 12689 * * A cache-control header on the response does not affect if or how responses are cached. 12690 * 12691 * 12692 * ## Interceptors 12693 * 12694 * Before you start creating interceptors, be sure to understand the 12695 * {@link ng.$q $q and deferred/promise APIs}. 12696 * 12697 * For purposes of global error handling, authentication, or any kind of synchronous or 12698 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 12699 * able to intercept requests before they are handed to the server and 12700 * responses before they are handed over to the application code that 12701 * initiated these requests. The interceptors leverage the {@link ng.$q 12702 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 12703 * 12704 * The interceptors are service factories that are registered with the `$httpProvider` by 12705 * adding them to the `$httpProvider.interceptors` array. The factory is called and 12706 * injected with dependencies (if specified) and returns the interceptor. 12707 * 12708 * There are two kinds of interceptors (and two kinds of rejection interceptors): 12709 * 12710 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 12711 * modify the `config` object or create a new one. The function needs to return the `config` 12712 * object directly, or a promise containing the `config` or a new `config` object. 12713 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 12714 * resolved with a rejection. 12715 * * `response`: interceptors get called with http `response` object. The function is free to 12716 * modify the `response` object or create a new one. The function needs to return the `response` 12717 * object directly, or as a promise containing the `response` or a new `response` object. 12718 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 12719 * resolved with a rejection. 12720 * 12721 * 12722 * ```js 12723 * // register the interceptor as a service 12724 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 12725 * return { 12726 * // optional method 12727 * 'request': function(config) { 12728 * // do something on success 12729 * return config; 12730 * }, 12731 * 12732 * // optional method 12733 * 'requestError': function(rejection) { 12734 * // do something on error 12735 * if (canRecover(rejection)) { 12736 * return responseOrNewPromise 12737 * } 12738 * return $q.reject(rejection); 12739 * }, 12740 * 12741 * 12742 * 12743 * // optional method 12744 * 'response': function(response) { 12745 * // do something on success 12746 * return response; 12747 * }, 12748 * 12749 * // optional method 12750 * 'responseError': function(rejection) { 12751 * // do something on error 12752 * if (canRecover(rejection)) { 12753 * return responseOrNewPromise 12754 * } 12755 * return $q.reject(rejection); 12756 * } 12757 * }; 12758 * }); 12759 * 12760 * $httpProvider.interceptors.push('myHttpInterceptor'); 12761 * 12762 * 12763 * // alternatively, register the interceptor via an anonymous factory 12764 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 12765 * return { 12766 * 'request': function(config) { 12767 * // same as above 12768 * }, 12769 * 12770 * 'response': function(response) { 12771 * // same as above 12772 * } 12773 * }; 12774 * }); 12775 * ``` 12776 * 12777 * ## Security Considerations 12778 * 12779 * When designing web applications, consider security threats from: 12780 * 12781 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 12782 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 12783 * 12784 * Both server and the client must cooperate in order to eliminate these threats. AngularJS comes 12785 * pre-configured with strategies that address these issues, but for this to work backend server 12786 * cooperation is required. 12787 * 12788 * ### JSON Vulnerability Protection 12789 * 12790 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 12791 * allows third party website to turn your JSON resource URL into 12792 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 12793 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 12794 * AngularJS will automatically strip the prefix before processing it as JSON. 12795 * 12796 * For example if your server needs to return: 12797 * ```js 12798 * ['one','two'] 12799 * ``` 12800 * 12801 * which is vulnerable to attack, your server can return: 12802 * ```js 12803 * )]}', 12804 * ['one','two'] 12805 * ``` 12806 * 12807 * AngularJS will strip the prefix, before processing the JSON. 12808 * 12809 * 12810 * ### Cross Site Request Forgery (XSRF) Protection 12811 * 12812 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by 12813 * which the attacker can trick an authenticated user into unknowingly executing actions on your 12814 * website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the 12815 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP 12816 * header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read 12817 * the cookie, your server can be assured that the XHR came from JavaScript running on your 12818 * domain. 12819 * 12820 * To take advantage of this, your server needs to set a token in a JavaScript readable session 12821 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 12822 * server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be 12823 * sure that only JavaScript running on your domain could have sent the request. The token must 12824 * be unique for each user and must be verifiable by the server (to prevent the JavaScript from 12825 * making up its own tokens). We recommend that the token is a digest of your site's 12826 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 12827 * for added security. 12828 * 12829 * The header will — by default — **not** be set for cross-domain requests. This 12830 * prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining 12831 * access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you 12832 * want to, you can trust additional origins to also receive the XSRF token, by adding them 12833 * to {@link ng.$httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}. This might be 12834 * useful, for example, if your application, served from `example.com`, needs to access your API 12835 * at `api.example.com`. 12836 * See {@link ng.$httpProvider#xsrfTrustedOrigins $httpProvider.xsrfTrustedOrigins} for 12837 * more details. 12838 * 12839 * <div class="alert alert-danger"> 12840 * **Warning**<br /> 12841 * Only trusted origins that you have control over and make sure you understand the 12842 * implications of doing so. 12843 * </div> 12844 * 12845 * The name of the cookie and the header can be specified using the `xsrfCookieName` and 12846 * `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time, 12847 * `$http.defaults` at run-time, or the per-request config object. 12848 * 12849 * In order to prevent collisions in environments where multiple AngularJS apps share the 12850 * same domain or subdomain, we recommend that each application uses a unique cookie name. 12851 * 12852 * 12853 * @param {object} config Object describing the request to be made and how it should be 12854 * processed. The object has following properties: 12855 * 12856 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) 12857 * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested; 12858 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 12859 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized 12860 * with the `paramSerializer` and appended as GET parameters. 12861 * - **data** – `{string|Object}` – Data to be sent as the request message data. 12862 * - **headers** – `{Object}` – Map of strings or functions which return strings representing 12863 * HTTP headers to send to the server. If the return value of a function is null, the 12864 * header will not be sent. Functions accept a config object as an argument. 12865 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. 12866 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. 12867 * The handler will be called in the context of a `$apply` block. 12868 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload 12869 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. 12870 * The handler will be called in the context of a `$apply` block. 12871 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. 12872 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. 12873 * - **transformRequest** – 12874 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – 12875 * transform function or an array of such functions. The transform function takes the http 12876 * request body and headers and returns its transformed (typically serialized) version. 12877 * See {@link ng.$http#overriding-the-default-transformations-per-request 12878 * Overriding the Default Transformations} 12879 * - **transformResponse** – 12880 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` – 12881 * transform function or an array of such functions. The transform function takes the http 12882 * response body, headers and status and returns its transformed (typically deserialized) version. 12883 * See {@link ng.$http#overriding-the-default-transformations-per-request 12884 * Overriding the Default Transformations} 12885 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 12886 * prepare the string representation of request parameters (specified as an object). 12887 * If specified as string, it is interpreted as function registered with the 12888 * {@link $injector $injector}, which means you can create your own serializer 12889 * by registering it as a {@link auto.$provide#service service}. 12890 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 12891 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 12892 * - **cache** – `{boolean|Object}` – A boolean value or object created with 12893 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 12894 * See {@link $http#caching $http Caching} for more information. 12895 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} 12896 * that should abort the request when resolved. 12897 * 12898 * A numerical timeout or a promise returned from {@link ng.$timeout $timeout}, will set 12899 * the `xhrStatus` in the {@link $http#$http-returns response} to "timeout", and any other 12900 * resolved promise will set it to "abort", following standard XMLHttpRequest behavior. 12901 * 12902 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 12903 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 12904 * for more information. 12905 * - **responseType** - `{string}` - see 12906 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 12907 * 12908 * @returns {HttpPromise} A {@link ng.$q `Promise}` that will be resolved (request success) 12909 * or rejected (request failure) with a response object. 12910 * 12911 * The response object has these properties: 12912 * 12913 * - **data** – `{string|Object}` – The response body transformed with 12914 * the transform functions. 12915 * - **status** – `{number}` – HTTP status code of the response. 12916 * - **headers** – `{function([headerName])}` – Header getter function. 12917 * - **config** – `{Object}` – The configuration object that was used 12918 * to generate the request. 12919 * - **statusText** – `{string}` – HTTP status text of the response. 12920 * - **xhrStatus** – `{string}` – Status of the XMLHttpRequest 12921 * (`complete`, `error`, `timeout` or `abort`). 12922 * 12923 * 12924 * A response status code between 200 and 299 is considered a success status 12925 * and will result in the success callback being called. Any response status 12926 * code outside of that range is considered an error status and will result 12927 * in the error callback being called. 12928 * Also, status codes less than -1 are normalized to zero. -1 usually means 12929 * the request was aborted, e.g. using a `config.timeout`. More information 12930 * about the status might be available in the `xhrStatus` property. 12931 * 12932 * Note that if the response is a redirect, XMLHttpRequest will transparently 12933 * follow it, meaning that the outcome (success or error) will be determined 12934 * by the final response status code. 12935 * 12936 * 12937 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 12938 * requests. This is primarily meant to be used for debugging purposes. 12939 * 12940 * 12941 * @example 12942<example module="httpExample" name="http-service"> 12943<file name="index.html"> 12944 <div ng-controller="FetchController"> 12945 <select ng-model="method" aria-label="Request method"> 12946 <option>GET</option> 12947 <option>JSONP</option> 12948 </select> 12949 <input type="text" ng-model="url" size="80" aria-label="URL" /> 12950 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 12951 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 12952 <button id="samplejsonpbtn" 12953 ng-click="updateModel('JSONP', 12954 'https://angularjs.org/greet.php?name=Super%20Hero')"> 12955 Sample JSONP 12956 </button> 12957 <button id="invalidjsonpbtn" 12958 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')"> 12959 Invalid JSONP 12960 </button> 12961 <pre>http status code: {{status}}</pre> 12962 <pre>http response data: {{data}}</pre> 12963 </div> 12964</file> 12965<file name="script.js"> 12966 angular.module('httpExample', []) 12967 .config(['$sceDelegateProvider', function($sceDelegateProvider) { 12968 // We must add the JSONP endpoint that we are using to the trusted list to show that we trust it 12969 $sceDelegateProvider.trustedResourceUrlList([ 12970 'self', 12971 'https://angularjs.org/**' 12972 ]); 12973 }]) 12974 .controller('FetchController', ['$scope', '$http', '$templateCache', 12975 function($scope, $http, $templateCache) { 12976 $scope.method = 'GET'; 12977 $scope.url = 'http-hello.html'; 12978 12979 $scope.fetch = function() { 12980 $scope.code = null; 12981 $scope.response = null; 12982 12983 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 12984 then(function(response) { 12985 $scope.status = response.status; 12986 $scope.data = response.data; 12987 }, function(response) { 12988 $scope.data = response.data || 'Request failed'; 12989 $scope.status = response.status; 12990 }); 12991 }; 12992 12993 $scope.updateModel = function(method, url) { 12994 $scope.method = method; 12995 $scope.url = url; 12996 }; 12997 }]); 12998</file> 12999<file name="http-hello.html"> 13000 Hello, $http! 13001</file> 13002<file name="protractor.js" type="protractor"> 13003 var status = element(by.binding('status')); 13004 var data = element(by.binding('data')); 13005 var fetchBtn = element(by.id('fetchbtn')); 13006 var sampleGetBtn = element(by.id('samplegetbtn')); 13007 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 13008 13009 it('should make an xhr GET request', function() { 13010 sampleGetBtn.click(); 13011 fetchBtn.click(); 13012 expect(status.getText()).toMatch('200'); 13013 expect(data.getText()).toMatch(/Hello, \$http!/); 13014 }); 13015 13016// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 13017// it('should make a JSONP request to angularjs.org', function() { 13018// var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 13019// sampleJsonpBtn.click(); 13020// fetchBtn.click(); 13021// expect(status.getText()).toMatch('200'); 13022// expect(data.getText()).toMatch(/Super Hero!/); 13023// }); 13024 13025 it('should make JSONP request to invalid URL and invoke the error handler', 13026 function() { 13027 invalidJsonpBtn.click(); 13028 fetchBtn.click(); 13029 expect(status.getText()).toMatch('0'); 13030 expect(data.getText()).toMatch('Request failed'); 13031 }); 13032</file> 13033</example> 13034 */ 13035 function $http(requestConfig) { 13036 13037 if (!isObject(requestConfig)) { 13038 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 13039 } 13040 13041 if (!isString($sce.valueOf(requestConfig.url))) { 13042 throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url); 13043 } 13044 13045 var config = extend({ 13046 method: 'get', 13047 transformRequest: defaults.transformRequest, 13048 transformResponse: defaults.transformResponse, 13049 paramSerializer: defaults.paramSerializer, 13050 jsonpCallbackParam: defaults.jsonpCallbackParam 13051 }, requestConfig); 13052 13053 config.headers = mergeHeaders(requestConfig); 13054 config.method = uppercase(config.method); 13055 config.paramSerializer = isString(config.paramSerializer) ? 13056 $injector.get(config.paramSerializer) : config.paramSerializer; 13057 13058 $browser.$$incOutstandingRequestCount('$http'); 13059 13060 var requestInterceptors = []; 13061 var responseInterceptors = []; 13062 var promise = $q.resolve(config); 13063 13064 // apply interceptors 13065 forEach(reversedInterceptors, function(interceptor) { 13066 if (interceptor.request || interceptor.requestError) { 13067 requestInterceptors.unshift(interceptor.request, interceptor.requestError); 13068 } 13069 if (interceptor.response || interceptor.responseError) { 13070 responseInterceptors.push(interceptor.response, interceptor.responseError); 13071 } 13072 }); 13073 13074 promise = chainInterceptors(promise, requestInterceptors); 13075 promise = promise.then(serverRequest); 13076 promise = chainInterceptors(promise, responseInterceptors); 13077 promise = promise.finally(completeOutstandingRequest); 13078 13079 return promise; 13080 13081 13082 function chainInterceptors(promise, interceptors) { 13083 for (var i = 0, ii = interceptors.length; i < ii;) { 13084 var thenFn = interceptors[i++]; 13085 var rejectFn = interceptors[i++]; 13086 13087 promise = promise.then(thenFn, rejectFn); 13088 } 13089 13090 interceptors.length = 0; 13091 13092 return promise; 13093 } 13094 13095 function completeOutstandingRequest() { 13096 $browser.$$completeOutstandingRequest(noop, '$http'); 13097 } 13098 13099 function executeHeaderFns(headers, config) { 13100 var headerContent, processedHeaders = {}; 13101 13102 forEach(headers, function(headerFn, header) { 13103 if (isFunction(headerFn)) { 13104 headerContent = headerFn(config); 13105 if (headerContent != null) { 13106 processedHeaders[header] = headerContent; 13107 } 13108 } else { 13109 processedHeaders[header] = headerFn; 13110 } 13111 }); 13112 13113 return processedHeaders; 13114 } 13115 13116 function mergeHeaders(config) { 13117 var defHeaders = defaults.headers, 13118 reqHeaders = extend({}, config.headers), 13119 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 13120 13121 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 13122 13123 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 13124 defaultHeadersIteration: 13125 for (defHeaderName in defHeaders) { 13126 lowercaseDefHeaderName = lowercase(defHeaderName); 13127 13128 for (reqHeaderName in reqHeaders) { 13129 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 13130 continue defaultHeadersIteration; 13131 } 13132 } 13133 13134 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 13135 } 13136 13137 // execute if header value is a function for merged headers 13138 return executeHeaderFns(reqHeaders, shallowCopy(config)); 13139 } 13140 13141 function serverRequest(config) { 13142 var headers = config.headers; 13143 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 13144 13145 // strip content-type if data is undefined 13146 if (isUndefined(reqData)) { 13147 forEach(headers, function(value, header) { 13148 if (lowercase(header) === 'content-type') { 13149 delete headers[header]; 13150 } 13151 }); 13152 } 13153 13154 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 13155 config.withCredentials = defaults.withCredentials; 13156 } 13157 13158 // send request 13159 return sendReq(config, reqData).then(transformResponse, transformResponse); 13160 } 13161 13162 function transformResponse(response) { 13163 // make a copy since the response must be cacheable 13164 var resp = extend({}, response); 13165 resp.data = transformData(response.data, response.headers, response.status, 13166 config.transformResponse); 13167 return (isSuccess(response.status)) 13168 ? resp 13169 : $q.reject(resp); 13170 } 13171 } 13172 13173 $http.pendingRequests = []; 13174 13175 /** 13176 * @ngdoc method 13177 * @name $http#get 13178 * 13179 * @description 13180 * Shortcut method to perform `GET` request. 13181 * 13182 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13183 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13184 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13185 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13186 * See {@link ng.$http#$http-returns `$http()` return value}. 13187 */ 13188 13189 /** 13190 * @ngdoc method 13191 * @name $http#delete 13192 * 13193 * @description 13194 * Shortcut method to perform `DELETE` request. 13195 * 13196 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13197 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13198 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13199 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13200 * See {@link ng.$http#$http-returns `$http()` return value}. 13201 */ 13202 13203 /** 13204 * @ngdoc method 13205 * @name $http#head 13206 * 13207 * @description 13208 * Shortcut method to perform `HEAD` request. 13209 * 13210 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13211 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13212 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13213 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13214 * See {@link ng.$http#$http-returns `$http()` return value}. 13215 */ 13216 13217 /** 13218 * @ngdoc method 13219 * @name $http#jsonp 13220 * 13221 * @description 13222 * Shortcut method to perform `JSONP` request. 13223 * 13224 * Note that, since JSONP requests are sensitive because the response is given full access to the browser, 13225 * the url must be declared, via {@link $sce} as a trusted resource URL. 13226 * You can trust a URL by adding it to the trusted resource URL list via 13227 * {@link $sceDelegateProvider#trustedResourceUrlList `$sceDelegateProvider.trustedResourceUrlList`} or 13228 * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}. 13229 * 13230 * You should avoid generating the URL for the JSONP request from user provided data. 13231 * Provide additional query parameters via `params` property of the `config` parameter, rather than 13232 * modifying the URL itself. 13233 * 13234 * JSONP requests must specify a callback to be used in the response from the server. This callback 13235 * is passed as a query parameter in the request. You must specify the name of this parameter by 13236 * setting the `jsonpCallbackParam` property on the request config object. 13237 * 13238 * ``` 13239 * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'}) 13240 * ``` 13241 * 13242 * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`. 13243 * Initially this is set to `'callback'`. 13244 * 13245 * <div class="alert alert-danger"> 13246 * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback 13247 * parameter value should go. 13248 * </div> 13249 * 13250 * If you would like to customise where and how the callbacks are stored then try overriding 13251 * or decorating the {@link $jsonpCallbacks} service. 13252 * 13253 * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; 13254 * or an object created by a call to `$sce.trustAsResourceUrl(url)`. 13255 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13256 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13257 * See {@link ng.$http#$http-returns `$http()` return value}. 13258 */ 13259 createShortMethods('get', 'delete', 'head', 'jsonp'); 13260 13261 /** 13262 * @ngdoc method 13263 * @name $http#post 13264 * 13265 * @description 13266 * Shortcut method to perform `POST` request. 13267 * 13268 * @param {string} url Relative or absolute URL specifying the destination of the request 13269 * @param {*} data Request content 13270 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13271 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13272 * See {@link ng.$http#$http-returns `$http()` return value}. 13273 */ 13274 13275 /** 13276 * @ngdoc method 13277 * @name $http#put 13278 * 13279 * @description 13280 * Shortcut method to perform `PUT` request. 13281 * 13282 * @param {string} url Relative or absolute URL specifying the destination of the request 13283 * @param {*} data Request content 13284 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13285 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13286 * See {@link ng.$http#$http-returns `$http()` return value}. 13287 */ 13288 13289 /** 13290 * @ngdoc method 13291 * @name $http#patch 13292 * 13293 * @description 13294 * Shortcut method to perform `PATCH` request. 13295 * 13296 * @param {string} url Relative or absolute URL specifying the destination of the request 13297 * @param {*} data Request content 13298 * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. 13299 * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. 13300 * See {@link ng.$http#$http-returns `$http()` return value}. 13301 */ 13302 createShortMethodsWithData('post', 'put', 'patch'); 13303 13304 /** 13305 * @ngdoc property 13306 * @name $http#defaults 13307 * 13308 * @description 13309 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 13310 * default headers, withCredentials as well as request and response transformations. 13311 * 13312 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 13313 */ 13314 $http.defaults = defaults; 13315 13316 13317 return $http; 13318 13319 13320 function createShortMethods(names) { 13321 forEach(arguments, function(name) { 13322 $http[name] = function(url, config) { 13323 return $http(extend({}, config || {}, { 13324 method: name, 13325 url: url 13326 })); 13327 }; 13328 }); 13329 } 13330 13331 13332 function createShortMethodsWithData(name) { 13333 forEach(arguments, function(name) { 13334 $http[name] = function(url, data, config) { 13335 return $http(extend({}, config || {}, { 13336 method: name, 13337 url: url, 13338 data: data 13339 })); 13340 }; 13341 }); 13342 } 13343 13344 13345 /** 13346 * Makes the request. 13347 * 13348 * !!! ACCESSES CLOSURE VARS: 13349 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 13350 */ 13351 function sendReq(config, reqData) { 13352 var deferred = $q.defer(), 13353 promise = deferred.promise, 13354 cache, 13355 cachedResp, 13356 reqHeaders = config.headers, 13357 isJsonp = lowercase(config.method) === 'jsonp', 13358 url = config.url; 13359 13360 if (isJsonp) { 13361 // JSONP is a pretty sensitive operation where we're allowing a script to have full access to 13362 // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL. 13363 url = $sce.getTrustedResourceUrl(url); 13364 } else if (!isString(url)) { 13365 // If it is not a string then the URL must be a $sce trusted object 13366 url = $sce.valueOf(url); 13367 } 13368 13369 url = buildUrl(url, config.paramSerializer(config.params)); 13370 13371 if (isJsonp) { 13372 // Check the url and add the JSONP callback placeholder 13373 url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam); 13374 } 13375 13376 $http.pendingRequests.push(config); 13377 promise.then(removePendingReq, removePendingReq); 13378 13379 if ((config.cache || defaults.cache) && config.cache !== false && 13380 (config.method === 'GET' || config.method === 'JSONP')) { 13381 cache = isObject(config.cache) ? config.cache 13382 : isObject(/** @type {?} */ (defaults).cache) 13383 ? /** @type {?} */ (defaults).cache 13384 : defaultCache; 13385 } 13386 13387 if (cache) { 13388 cachedResp = cache.get(url); 13389 if (isDefined(cachedResp)) { 13390 if (isPromiseLike(cachedResp)) { 13391 // cached request has already been sent, but there is no response yet 13392 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 13393 } else { 13394 // serving from cache 13395 if (isArray(cachedResp)) { 13396 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]); 13397 } else { 13398 resolvePromise(cachedResp, 200, {}, 'OK', 'complete'); 13399 } 13400 } 13401 } else { 13402 // put the promise for the non-transformed response into cache as a placeholder 13403 cache.put(url, promise); 13404 } 13405 } 13406 13407 13408 // if we won't have the response in cache, set the xsrf headers and 13409 // send the request to the backend 13410 if (isUndefined(cachedResp)) { 13411 var xsrfValue = urlIsAllowedOrigin(config.url) 13412 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 13413 : undefined; 13414 if (xsrfValue) { 13415 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 13416 } 13417 13418 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 13419 config.withCredentials, config.responseType, 13420 createApplyHandlers(config.eventHandlers), 13421 createApplyHandlers(config.uploadEventHandlers)); 13422 } 13423 13424 return promise; 13425 13426 function createApplyHandlers(eventHandlers) { 13427 if (eventHandlers) { 13428 var applyHandlers = {}; 13429 forEach(eventHandlers, function(eventHandler, key) { 13430 applyHandlers[key] = function(event) { 13431 if (useApplyAsync) { 13432 $rootScope.$applyAsync(callEventHandler); 13433 } else if ($rootScope.$$phase) { 13434 callEventHandler(); 13435 } else { 13436 $rootScope.$apply(callEventHandler); 13437 } 13438 13439 function callEventHandler() { 13440 eventHandler(event); 13441 } 13442 }; 13443 }); 13444 return applyHandlers; 13445 } 13446 } 13447 13448 13449 /** 13450 * Callback registered to $httpBackend(): 13451 * - caches the response if desired 13452 * - resolves the raw $http promise 13453 * - calls $apply 13454 */ 13455 function done(status, response, headersString, statusText, xhrStatus) { 13456 if (cache) { 13457 if (isSuccess(status)) { 13458 cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]); 13459 } else { 13460 // remove promise from the cache 13461 cache.remove(url); 13462 } 13463 } 13464 13465 function resolveHttpPromise() { 13466 resolvePromise(response, status, headersString, statusText, xhrStatus); 13467 } 13468 13469 if (useApplyAsync) { 13470 $rootScope.$applyAsync(resolveHttpPromise); 13471 } else { 13472 resolveHttpPromise(); 13473 if (!$rootScope.$$phase) $rootScope.$apply(); 13474 } 13475 } 13476 13477 13478 /** 13479 * Resolves the raw $http promise. 13480 */ 13481 function resolvePromise(response, status, headers, statusText, xhrStatus) { 13482 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 13483 status = status >= -1 ? status : 0; 13484 13485 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 13486 data: response, 13487 status: status, 13488 headers: headersGetter(headers), 13489 config: config, 13490 statusText: statusText, 13491 xhrStatus: xhrStatus 13492 }); 13493 } 13494 13495 function resolvePromiseWithResult(result) { 13496 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus); 13497 } 13498 13499 function removePendingReq() { 13500 var idx = $http.pendingRequests.indexOf(config); 13501 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 13502 } 13503 } 13504 13505 13506 function buildUrl(url, serializedParams) { 13507 if (serializedParams.length > 0) { 13508 url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams; 13509 } 13510 return url; 13511 } 13512 13513 function sanitizeJsonpCallbackParam(url, cbKey) { 13514 var parts = url.split('?'); 13515 if (parts.length > 2) { 13516 // Throw if the url contains more than one `?` query indicator 13517 throw $httpMinErr('badjsonp', 'Illegal use more than one "?", in url, "{1}"', url); 13518 } 13519 var params = parseKeyValue(parts[1]); 13520 forEach(params, function(value, key) { 13521 if (value === 'JSON_CALLBACK') { 13522 // Throw if the url already contains a reference to JSON_CALLBACK 13523 throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url); 13524 } 13525 if (key === cbKey) { 13526 // Throw if the callback param was already provided 13527 throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', cbKey, url); 13528 } 13529 }); 13530 13531 // Add in the JSON_CALLBACK callback param value 13532 url += ((url.indexOf('?') === -1) ? '?' : '&') + cbKey + '=JSON_CALLBACK'; 13533 13534 return url; 13535 } 13536 }]; 13537} 13538 13539/** 13540 * @ngdoc service 13541 * @name $xhrFactory 13542 * @this 13543 * 13544 * @description 13545 * Factory function used to create XMLHttpRequest objects. 13546 * 13547 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 13548 * 13549 * ``` 13550 * angular.module('myApp', []) 13551 * .factory('$xhrFactory', function() { 13552 * return function createXhr(method, url) { 13553 * return new window.XMLHttpRequest({mozSystem: true}); 13554 * }; 13555 * }); 13556 * ``` 13557 * 13558 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 13559 * @param {string} url URL of the request. 13560 */ 13561function $xhrFactoryProvider() { 13562 this.$get = function() { 13563 return function createXhr() { 13564 return new window.XMLHttpRequest(); 13565 }; 13566 }; 13567} 13568 13569/** 13570 * @ngdoc service 13571 * @name $httpBackend 13572 * @requires $jsonpCallbacks 13573 * @requires $document 13574 * @requires $xhrFactory 13575 * @this 13576 * 13577 * @description 13578 * HTTP backend used by the {@link ng.$http service} that delegates to 13579 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 13580 * 13581 * You should never need to use this service directly, instead use the higher-level abstractions: 13582 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 13583 * 13584 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 13585 * $httpBackend} which can be trained with responses. 13586 */ 13587function $HttpBackendProvider() { 13588 this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) { 13589 return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]); 13590 }]; 13591} 13592 13593function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 13594 // TODO(vojta): fix the signature 13595 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { 13596 url = url || $browser.url(); 13597 13598 if (lowercase(method) === 'jsonp') { 13599 var callbackPath = callbacks.createCallback(url); 13600 var jsonpDone = jsonpReq(url, callbackPath, function(status, text) { 13601 // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING) 13602 var response = (status === 200) && callbacks.getResponse(callbackPath); 13603 completeRequest(callback, status, response, '', text, 'complete'); 13604 callbacks.removeCallback(callbackPath); 13605 }); 13606 } else { 13607 13608 var xhr = createXhr(method, url); 13609 var abortedByTimeout = false; 13610 13611 xhr.open(method, url, true); 13612 forEach(headers, function(value, key) { 13613 if (isDefined(value)) { 13614 xhr.setRequestHeader(key, value); 13615 } 13616 }); 13617 13618 xhr.onload = function requestLoaded() { 13619 var statusText = xhr.statusText || ''; 13620 13621 // responseText is the old-school way of retrieving response (supported by IE9) 13622 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 13623 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 13624 13625 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 13626 var status = xhr.status === 1223 ? 204 : xhr.status; 13627 13628 // fix status code when it is 0 (0 status is undocumented). 13629 // Occurs when accessing file resources or on Android 4.1 stock browser 13630 // while retrieving files from application cache. 13631 if (status === 0) { 13632 status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0; 13633 } 13634 13635 completeRequest(callback, 13636 status, 13637 response, 13638 xhr.getAllResponseHeaders(), 13639 statusText, 13640 'complete'); 13641 }; 13642 13643 var requestError = function() { 13644 // The response is always empty 13645 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 13646 completeRequest(callback, -1, null, null, '', 'error'); 13647 }; 13648 13649 var requestAborted = function() { 13650 completeRequest(callback, -1, null, null, '', abortedByTimeout ? 'timeout' : 'abort'); 13651 }; 13652 13653 var requestTimeout = function() { 13654 // The response is always empty 13655 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 13656 completeRequest(callback, -1, null, null, '', 'timeout'); 13657 }; 13658 13659 xhr.onerror = requestError; 13660 xhr.ontimeout = requestTimeout; 13661 xhr.onabort = requestAborted; 13662 13663 forEach(eventHandlers, function(value, key) { 13664 xhr.addEventListener(key, value); 13665 }); 13666 13667 forEach(uploadEventHandlers, function(value, key) { 13668 xhr.upload.addEventListener(key, value); 13669 }); 13670 13671 if (withCredentials) { 13672 xhr.withCredentials = true; 13673 } 13674 13675 if (responseType) { 13676 try { 13677 xhr.responseType = responseType; 13678 } catch (e) { 13679 // WebKit added support for the json responseType value on 09/03/2013 13680 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 13681 // known to throw when setting the value "json" as the response type. Other older 13682 // browsers implementing the responseType 13683 // 13684 // The json response type can be ignored if not supported, because JSON payloads are 13685 // parsed on the client-side regardless. 13686 if (responseType !== 'json') { 13687 throw e; 13688 } 13689 } 13690 } 13691 13692 xhr.send(isUndefined(post) ? null : post); 13693 } 13694 13695 // Since we are using xhr.abort() when a request times out, we have to set a flag that 13696 // indicates to requestAborted if the request timed out or was aborted. 13697 // 13698 // http.timeout = numerical timeout timeout 13699 // http.timeout = $timeout timeout 13700 // http.timeout = promise abort 13701 // xhr.abort() abort (The xhr object is normally inaccessible, but 13702 // can be exposed with the xhrFactory) 13703 if (timeout > 0) { 13704 var timeoutId = $browserDefer(function() { 13705 timeoutRequest('timeout'); 13706 }, timeout); 13707 } else if (isPromiseLike(timeout)) { 13708 timeout.then(function() { 13709 timeoutRequest(isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort'); 13710 }); 13711 } 13712 13713 function timeoutRequest(reason) { 13714 abortedByTimeout = reason === 'timeout'; 13715 if (jsonpDone) { 13716 jsonpDone(); 13717 } 13718 if (xhr) { 13719 xhr.abort(); 13720 } 13721 } 13722 13723 function completeRequest(callback, status, response, headersString, statusText, xhrStatus) { 13724 // cancel timeout and subsequent timeout promise resolution 13725 if (isDefined(timeoutId)) { 13726 $browserDefer.cancel(timeoutId); 13727 } 13728 jsonpDone = xhr = null; 13729 13730 callback(status, response, headersString, statusText, xhrStatus); 13731 } 13732 }; 13733 13734 function jsonpReq(url, callbackPath, done) { 13735 url = url.replace('JSON_CALLBACK', callbackPath); 13736 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 13737 // - fetches local scripts via XHR and evals them 13738 // - adds and immediately removes script elements from the document 13739 var script = rawDocument.createElement('script'), callback = null; 13740 script.type = 'text/javascript'; 13741 script.src = url; 13742 script.async = true; 13743 13744 callback = function(event) { 13745 script.removeEventListener('load', callback); 13746 script.removeEventListener('error', callback); 13747 rawDocument.body.removeChild(script); 13748 script = null; 13749 var status = -1; 13750 var text = 'unknown'; 13751 13752 if (event) { 13753 if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) { 13754 event = { type: 'error' }; 13755 } 13756 text = event.type; 13757 status = event.type === 'error' ? 404 : 200; 13758 } 13759 13760 if (done) { 13761 done(status, text); 13762 } 13763 }; 13764 13765 script.addEventListener('load', callback); 13766 script.addEventListener('error', callback); 13767 rawDocument.body.appendChild(script); 13768 return callback; 13769 } 13770} 13771 13772var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 13773$interpolateMinErr.throwNoconcat = function(text) { 13774 throw $interpolateMinErr('noconcat', 13775 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' + 13776 'interpolations that concatenate multiple expressions when a trusted value is ' + 13777 'required. See http://docs.angularjs.org/api/ng.$sce', text); 13778}; 13779 13780$interpolateMinErr.interr = function(text, err) { 13781 return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString()); 13782}; 13783 13784/** 13785 * @ngdoc provider 13786 * @name $interpolateProvider 13787 * @this 13788 * 13789 * @description 13790 * 13791 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 13792 * 13793 * <div class="alert alert-danger"> 13794 * This feature is sometimes used to mix different markup languages, e.g. to wrap an AngularJS 13795 * template within a Python Jinja template (or any other template language). Mixing templating 13796 * languages is **very dangerous**. The embedding template language will not safely escape AngularJS 13797 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 13798 * security bugs! 13799 * </div> 13800 * 13801 * @example 13802<example name="custom-interpolation-markup" module="customInterpolationApp"> 13803<file name="index.html"> 13804<script> 13805 var customInterpolationApp = angular.module('customInterpolationApp', []); 13806 13807 customInterpolationApp.config(function($interpolateProvider) { 13808 $interpolateProvider.startSymbol('//'); 13809 $interpolateProvider.endSymbol('//'); 13810 }); 13811 13812 13813 customInterpolationApp.controller('DemoController', function() { 13814 this.label = "This binding is brought you by // interpolation symbols."; 13815 }); 13816</script> 13817<div ng-controller="DemoController as demo"> 13818 //demo.label// 13819</div> 13820</file> 13821<file name="protractor.js" type="protractor"> 13822 it('should interpolate binding with custom symbols', function() { 13823 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 13824 }); 13825</file> 13826</example> 13827 */ 13828function $InterpolateProvider() { 13829 var startSymbol = '{{'; 13830 var endSymbol = '}}'; 13831 13832 /** 13833 * @ngdoc method 13834 * @name $interpolateProvider#startSymbol 13835 * @description 13836 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 13837 * 13838 * @param {string=} value new value to set the starting symbol to. 13839 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 13840 */ 13841 this.startSymbol = function(value) { 13842 if (value) { 13843 startSymbol = value; 13844 return this; 13845 } 13846 return startSymbol; 13847 }; 13848 13849 /** 13850 * @ngdoc method 13851 * @name $interpolateProvider#endSymbol 13852 * @description 13853 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 13854 * 13855 * @param {string=} value new value to set the ending symbol to. 13856 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 13857 */ 13858 this.endSymbol = function(value) { 13859 if (value) { 13860 endSymbol = value; 13861 return this; 13862 } 13863 return endSymbol; 13864 }; 13865 13866 13867 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 13868 var startSymbolLength = startSymbol.length, 13869 endSymbolLength = endSymbol.length, 13870 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 13871 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 13872 13873 function escape(ch) { 13874 return '\\\\\\' + ch; 13875 } 13876 13877 function unescapeText(text) { 13878 return text.replace(escapedStartRegexp, startSymbol). 13879 replace(escapedEndRegexp, endSymbol); 13880 } 13881 13882 // TODO: this is the same as the constantWatchDelegate in parse.js 13883 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 13884 var unwatch = scope.$watch(function constantInterpolateWatch(scope) { 13885 unwatch(); 13886 return constantInterp(scope); 13887 }, listener, objectEquality); 13888 return unwatch; 13889 } 13890 13891 /** 13892 * @ngdoc service 13893 * @name $interpolate 13894 * @kind function 13895 * 13896 * @requires $parse 13897 * @requires $sce 13898 * 13899 * @description 13900 * 13901 * Compiles a string with markup into an interpolation function. This service is used by the 13902 * HTML {@link ng.$compile $compile} service for data binding. See 13903 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 13904 * interpolation markup. 13905 * 13906 * 13907 * ```js 13908 * var $interpolate = ...; // injected 13909 * var exp = $interpolate('Hello {{name | uppercase}}!'); 13910 * expect(exp({name:'AngularJS'})).toEqual('Hello ANGULARJS!'); 13911 * ``` 13912 * 13913 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 13914 * `true`, the interpolation function will return `undefined` unless all embedded expressions 13915 * evaluate to a value other than `undefined`. 13916 * 13917 * ```js 13918 * var $interpolate = ...; // injected 13919 * var context = {greeting: 'Hello', name: undefined }; 13920 * 13921 * // default "forgiving" mode 13922 * var exp = $interpolate('{{greeting}} {{name}}!'); 13923 * expect(exp(context)).toEqual('Hello !'); 13924 * 13925 * // "allOrNothing" mode 13926 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 13927 * expect(exp(context)).toBeUndefined(); 13928 * context.name = 'AngularJS'; 13929 * expect(exp(context)).toEqual('Hello AngularJS!'); 13930 * ``` 13931 * 13932 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 13933 * 13934 * #### Escaped Interpolation 13935 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 13936 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 13937 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 13938 * or binding. 13939 * 13940 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 13941 * degree, while also enabling code examples to work without relying on the 13942 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 13943 * 13944 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 13945 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 13946 * interpolation start/end markers with their escaped counterparts.** 13947 * 13948 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 13949 * output when the $interpolate service processes the text. So, for HTML elements interpolated 13950 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter 13951 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 13952 * this is typically useful only when user-data is used in rendering a template from the server, or 13953 * when otherwise untrusted data is used by a directive. 13954 * 13955 * <example name="interpolation"> 13956 * <file name="index.html"> 13957 * <div ng-init="username='A user'"> 13958 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 13959 * </p> 13960 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 13961 * application, but fails to accomplish their task, because the server has correctly 13962 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 13963 * characters.</p> 13964 * <p>Instead, the result of the attempted script injection is visible, and can be removed 13965 * from the database by an administrator.</p> 13966 * </div> 13967 * </file> 13968 * </example> 13969 * 13970 * @knownIssue 13971 * It is currently not possible for an interpolated expression to contain the interpolation end 13972 * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e. 13973 * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string. 13974 * 13975 * @knownIssue 13976 * All directives and components must use the standard `{{` `}}` interpolation symbols 13977 * in their templates. If you change the application interpolation symbols the {@link $compile} 13978 * service will attempt to denormalize the standard symbols to the custom symbols. 13979 * The denormalization process is not clever enough to know not to replace instances of the standard 13980 * symbols where they would not normally be treated as interpolation symbols. For example in the following 13981 * code snippet the closing braces of the literal object will get incorrectly denormalized: 13982 * 13983 * ``` 13984 * <div data-context='{"context":{"id":3,"type":"page"}}"> 13985 * ``` 13986 * 13987 * The workaround is to ensure that such instances are separated by whitespace: 13988 * ``` 13989 * <div data-context='{"context":{"id":3,"type":"page"} }"> 13990 * ``` 13991 * 13992 * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information. 13993 * 13994 * @param {string} text The text with markup to interpolate. 13995 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 13996 * embedded expression in order to return an interpolation function. Strings with no 13997 * embedded expression will return null for the interpolation function. 13998 * @param {string=} trustedContext when provided, the returned function passes the interpolated 13999 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 14000 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 14001 * provides Strict Contextual Escaping for details. 14002 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 14003 * unless all embedded expressions evaluate to a value other than `undefined`. 14004 * @returns {function(context)} an interpolation function which is used to compute the 14005 * interpolated string. The function has these parameters: 14006 * 14007 * - `context`: evaluation context for all expressions embedded in the interpolated text 14008 */ 14009 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 14010 var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL; 14011 14012 // Provide a quick exit and simplified result function for text with no interpolation 14013 if (!text.length || text.indexOf(startSymbol) === -1) { 14014 if (mustHaveExpression) return; 14015 14016 var unescapedText = unescapeText(text); 14017 if (contextAllowsConcatenation) { 14018 unescapedText = $sce.getTrusted(trustedContext, unescapedText); 14019 } 14020 var constantInterp = valueFn(unescapedText); 14021 constantInterp.exp = text; 14022 constantInterp.expressions = []; 14023 constantInterp.$$watchDelegate = constantWatchDelegate; 14024 14025 return constantInterp; 14026 } 14027 14028 allOrNothing = !!allOrNothing; 14029 var startIndex, 14030 endIndex, 14031 index = 0, 14032 expressions = [], 14033 parseFns, 14034 textLength = text.length, 14035 exp, 14036 concat = [], 14037 expressionPositions = [], 14038 singleExpression; 14039 14040 14041 while (index < textLength) { 14042 if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && 14043 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) { 14044 if (index !== startIndex) { 14045 concat.push(unescapeText(text.substring(index, startIndex))); 14046 } 14047 exp = text.substring(startIndex + startSymbolLength, endIndex); 14048 expressions.push(exp); 14049 index = endIndex + endSymbolLength; 14050 expressionPositions.push(concat.length); 14051 concat.push(''); // Placeholder that will get replaced with the evaluated expression. 14052 } else { 14053 // we did not find an interpolation, so we have to add the remainder to the separators array 14054 if (index !== textLength) { 14055 concat.push(unescapeText(text.substring(index))); 14056 } 14057 break; 14058 } 14059 } 14060 14061 singleExpression = concat.length === 1 && expressionPositions.length === 1; 14062 // Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted 14063 // objects rather than simple strings 14064 // (we don't modify the expression if the input consists of only a single trusted input) 14065 var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor; 14066 parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); }); 14067 14068 // Concatenating expressions makes it hard to reason about whether some combination of 14069 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 14070 // single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly), 14071 // we ensure that the value that's used is assigned or constructed by some JS code somewhere 14072 // that is more testable or make it obvious that you bound the value to some user controlled 14073 // value. This helps reduce the load when auditing for XSS issues. 14074 14075 // Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values 14076 // passed to it. In that case, `$sce.getTrusted` will be called on either the single expression 14077 // or on the overall concatenated string (losing trusted types used in the mix, by design). 14078 // Both these methods will sanitize plain strings. Also, HTML could be included, but since it's 14079 // only used in srcdoc attributes, this would not be very useful. 14080 14081 if (!mustHaveExpression || expressions.length) { 14082 var compute = function(values) { 14083 for (var i = 0, ii = expressions.length; i < ii; i++) { 14084 if (allOrNothing && isUndefined(values[i])) return; 14085 concat[expressionPositions[i]] = values[i]; 14086 } 14087 14088 if (contextAllowsConcatenation) { 14089 // If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string 14090 return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join('')); 14091 } else if (trustedContext && concat.length > 1) { 14092 // This context does not allow more than one part, e.g. expr + string or exp + exp. 14093 $interpolateMinErr.throwNoconcat(text); 14094 } 14095 // In an unprivileged context or only one part: just concatenate and return. 14096 return concat.join(''); 14097 }; 14098 14099 return extend(function interpolationFn(context) { 14100 var i = 0; 14101 var ii = expressions.length; 14102 var values = new Array(ii); 14103 14104 try { 14105 for (; i < ii; i++) { 14106 values[i] = parseFns[i](context); 14107 } 14108 14109 return compute(values); 14110 } catch (err) { 14111 $exceptionHandler($interpolateMinErr.interr(text, err)); 14112 } 14113 14114 }, { 14115 // all of these properties are undocumented for now 14116 exp: text, //just for compatibility with regular watchers created via $watch 14117 expressions: expressions, 14118 $$watchDelegate: function(scope, listener) { 14119 var lastValue; 14120 return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) { 14121 var currValue = compute(values); 14122 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 14123 lastValue = currValue; 14124 }); 14125 } 14126 }); 14127 } 14128 14129 function parseStringifyInterceptor(value) { 14130 try { 14131 // In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual 14132 // parts of a full URL. We don't care about losing the trustedness here. 14133 // In non-concatenable contexts, where there is only one expression, this interceptor is 14134 // not applied to the expression. 14135 value = (trustedContext && !contextAllowsConcatenation) ? 14136 $sce.getTrusted(trustedContext, value) : 14137 $sce.valueOf(value); 14138 return allOrNothing && !isDefined(value) ? value : stringify(value); 14139 } catch (err) { 14140 $exceptionHandler($interpolateMinErr.interr(text, err)); 14141 } 14142 } 14143 } 14144 14145 14146 /** 14147 * @ngdoc method 14148 * @name $interpolate#startSymbol 14149 * @description 14150 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 14151 * 14152 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 14153 * the symbol. 14154 * 14155 * @returns {string} start symbol. 14156 */ 14157 $interpolate.startSymbol = function() { 14158 return startSymbol; 14159 }; 14160 14161 14162 /** 14163 * @ngdoc method 14164 * @name $interpolate#endSymbol 14165 * @description 14166 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 14167 * 14168 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 14169 * the symbol. 14170 * 14171 * @returns {string} end symbol. 14172 */ 14173 $interpolate.endSymbol = function() { 14174 return endSymbol; 14175 }; 14176 14177 return $interpolate; 14178 }]; 14179} 14180 14181var $intervalMinErr = minErr('$interval'); 14182 14183/** @this */ 14184function $IntervalProvider() { 14185 this.$get = ['$$intervalFactory', '$window', 14186 function($$intervalFactory, $window) { 14187 var intervals = {}; 14188 var setIntervalFn = function(tick, delay, deferred) { 14189 var id = $window.setInterval(tick, delay); 14190 intervals[id] = deferred; 14191 return id; 14192 }; 14193 var clearIntervalFn = function(id) { 14194 $window.clearInterval(id); 14195 delete intervals[id]; 14196 }; 14197 14198 /** 14199 * @ngdoc service 14200 * @name $interval 14201 * 14202 * @description 14203 * AngularJS's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 14204 * milliseconds. 14205 * 14206 * The return value of registering an interval function is a promise. This promise will be 14207 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 14208 * run indefinitely if `count` is not defined. The value of the notification will be the 14209 * number of iterations that have run. 14210 * To cancel an interval, call `$interval.cancel(promise)`. 14211 * 14212 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 14213 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 14214 * time. 14215 * 14216 * <div class="alert alert-warning"> 14217 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 14218 * with them. In particular they are not automatically destroyed when a controller's scope or a 14219 * directive's element are destroyed. 14220 * You should take this into consideration and make sure to always cancel the interval at the 14221 * appropriate moment. See the example below for more details on how and when to do this. 14222 * </div> 14223 * 14224 * @param {function()} fn A function that should be called repeatedly. If no additional arguments 14225 * are passed (see below), the function is called with the current iteration count. 14226 * @param {number} delay Number of milliseconds between each function call. 14227 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 14228 * indefinitely. 14229 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14230 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14231 * @param {...*=} Pass additional parameters to the executed function. 14232 * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete. 14233 * 14234 * @example 14235 * <example module="intervalExample" name="interval-service"> 14236 * <file name="index.html"> 14237 * <script> 14238 * angular.module('intervalExample', []) 14239 * .controller('ExampleController', ['$scope', '$interval', 14240 * function($scope, $interval) { 14241 * $scope.format = 'M/d/yy h:mm:ss a'; 14242 * $scope.blood_1 = 100; 14243 * $scope.blood_2 = 120; 14244 * 14245 * var stop; 14246 * $scope.fight = function() { 14247 * // Don't start a new fight if we are already fighting 14248 * if ( angular.isDefined(stop) ) return; 14249 * 14250 * stop = $interval(function() { 14251 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 14252 * $scope.blood_1 = $scope.blood_1 - 3; 14253 * $scope.blood_2 = $scope.blood_2 - 4; 14254 * } else { 14255 * $scope.stopFight(); 14256 * } 14257 * }, 100); 14258 * }; 14259 * 14260 * $scope.stopFight = function() { 14261 * if (angular.isDefined(stop)) { 14262 * $interval.cancel(stop); 14263 * stop = undefined; 14264 * } 14265 * }; 14266 * 14267 * $scope.resetFight = function() { 14268 * $scope.blood_1 = 100; 14269 * $scope.blood_2 = 120; 14270 * }; 14271 * 14272 * $scope.$on('$destroy', function() { 14273 * // Make sure that the interval is destroyed too 14274 * $scope.stopFight(); 14275 * }); 14276 * }]) 14277 * // Register the 'myCurrentTime' directive factory method. 14278 * // We inject $interval and dateFilter service since the factory method is DI. 14279 * .directive('myCurrentTime', ['$interval', 'dateFilter', 14280 * function($interval, dateFilter) { 14281 * // return the directive link function. (compile function not needed) 14282 * return function(scope, element, attrs) { 14283 * var format, // date format 14284 * stopTime; // so that we can cancel the time updates 14285 * 14286 * // used to update the UI 14287 * function updateTime() { 14288 * element.text(dateFilter(new Date(), format)); 14289 * } 14290 * 14291 * // watch the expression, and update the UI on change. 14292 * scope.$watch(attrs.myCurrentTime, function(value) { 14293 * format = value; 14294 * updateTime(); 14295 * }); 14296 * 14297 * stopTime = $interval(updateTime, 1000); 14298 * 14299 * // listen on DOM destroy (removal) event, and cancel the next UI update 14300 * // to prevent updating time after the DOM element was removed. 14301 * element.on('$destroy', function() { 14302 * $interval.cancel(stopTime); 14303 * }); 14304 * } 14305 * }]); 14306 * </script> 14307 * 14308 * <div> 14309 * <div ng-controller="ExampleController"> 14310 * <label>Date format: <input ng-model="format"></label> <hr/> 14311 * Current time is: <span my-current-time="format"></span> 14312 * <hr/> 14313 * Blood 1 : <font color='red'>{{blood_1}}</font> 14314 * Blood 2 : <font color='red'>{{blood_2}}</font> 14315 * <button type="button" data-ng-click="fight()">Fight</button> 14316 * <button type="button" data-ng-click="stopFight()">StopFight</button> 14317 * <button type="button" data-ng-click="resetFight()">resetFight</button> 14318 * </div> 14319 * </div> 14320 * 14321 * </file> 14322 * </example> 14323 */ 14324 var interval = $$intervalFactory(setIntervalFn, clearIntervalFn); 14325 14326 /** 14327 * @ngdoc method 14328 * @name $interval#cancel 14329 * 14330 * @description 14331 * Cancels a task associated with the `promise`. 14332 * 14333 * @param {Promise=} promise returned by the `$interval` function. 14334 * @returns {boolean} Returns `true` if the task was successfully canceled. 14335 */ 14336 interval.cancel = function(promise) { 14337 if (!promise) return false; 14338 14339 if (!promise.hasOwnProperty('$$intervalId')) { 14340 throw $intervalMinErr('badprom', 14341 '`$interval.cancel()` called with a promise that was not generated by `$interval()`.'); 14342 } 14343 14344 if (!intervals.hasOwnProperty(promise.$$intervalId)) return false; 14345 14346 var id = promise.$$intervalId; 14347 var deferred = intervals[id]; 14348 14349 // Interval cancels should not report an unhandled promise. 14350 markQExceptionHandled(deferred.promise); 14351 deferred.reject('canceled'); 14352 clearIntervalFn(id); 14353 14354 return true; 14355 }; 14356 14357 return interval; 14358 }]; 14359} 14360 14361/** @this */ 14362function $$IntervalFactoryProvider() { 14363 this.$get = ['$browser', '$q', '$$q', '$rootScope', 14364 function($browser, $q, $$q, $rootScope) { 14365 return function intervalFactory(setIntervalFn, clearIntervalFn) { 14366 return function intervalFn(fn, delay, count, invokeApply) { 14367 var hasParams = arguments.length > 4, 14368 args = hasParams ? sliceArgs(arguments, 4) : [], 14369 iteration = 0, 14370 skipApply = isDefined(invokeApply) && !invokeApply, 14371 deferred = (skipApply ? $$q : $q).defer(), 14372 promise = deferred.promise; 14373 14374 count = isDefined(count) ? count : 0; 14375 14376 function callback() { 14377 if (!hasParams) { 14378 fn(iteration); 14379 } else { 14380 fn.apply(null, args); 14381 } 14382 } 14383 14384 function tick() { 14385 if (skipApply) { 14386 $browser.defer(callback); 14387 } else { 14388 $rootScope.$evalAsync(callback); 14389 } 14390 deferred.notify(iteration++); 14391 14392 if (count > 0 && iteration >= count) { 14393 deferred.resolve(iteration); 14394 clearIntervalFn(promise.$$intervalId); 14395 } 14396 14397 if (!skipApply) $rootScope.$apply(); 14398 } 14399 14400 promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply); 14401 14402 return promise; 14403 }; 14404 }; 14405 }]; 14406} 14407 14408/** 14409 * @ngdoc service 14410 * @name $jsonpCallbacks 14411 * @requires $window 14412 * @description 14413 * This service handles the lifecycle of callbacks to handle JSONP requests. 14414 * Override this service if you wish to customise where the callbacks are stored and 14415 * how they vary compared to the requested url. 14416 */ 14417var $jsonpCallbacksProvider = /** @this */ function() { 14418 this.$get = function() { 14419 var callbacks = angular.callbacks; 14420 var callbackMap = {}; 14421 14422 function createCallback(callbackId) { 14423 var callback = function(data) { 14424 callback.data = data; 14425 callback.called = true; 14426 }; 14427 callback.id = callbackId; 14428 return callback; 14429 } 14430 14431 return { 14432 /** 14433 * @ngdoc method 14434 * @name $jsonpCallbacks#createCallback 14435 * @param {string} url the url of the JSONP request 14436 * @returns {string} the callback path to send to the server as part of the JSONP request 14437 * @description 14438 * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback 14439 * to pass to the server, which will be used to call the callback with its payload in the JSONP response. 14440 */ 14441 createCallback: function(url) { 14442 var callbackId = '_' + (callbacks.$$counter++).toString(36); 14443 var callbackPath = 'angular.callbacks.' + callbackId; 14444 var callback = createCallback(callbackId); 14445 callbackMap[callbackPath] = callbacks[callbackId] = callback; 14446 return callbackPath; 14447 }, 14448 /** 14449 * @ngdoc method 14450 * @name $jsonpCallbacks#wasCalled 14451 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 14452 * @returns {boolean} whether the callback has been called, as a result of the JSONP response 14453 * @description 14454 * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the 14455 * callback that was passed in the request. 14456 */ 14457 wasCalled: function(callbackPath) { 14458 return callbackMap[callbackPath].called; 14459 }, 14460 /** 14461 * @ngdoc method 14462 * @name $jsonpCallbacks#getResponse 14463 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 14464 * @returns {*} the data received from the response via the registered callback 14465 * @description 14466 * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback 14467 * in the JSONP response. 14468 */ 14469 getResponse: function(callbackPath) { 14470 return callbackMap[callbackPath].data; 14471 }, 14472 /** 14473 * @ngdoc method 14474 * @name $jsonpCallbacks#removeCallback 14475 * @param {string} callbackPath the path to the callback that was sent in the JSONP request 14476 * @description 14477 * {@link $httpBackend} calls this method to remove the callback after the JSONP request has 14478 * completed or timed-out. 14479 */ 14480 removeCallback: function(callbackPath) { 14481 var callback = callbackMap[callbackPath]; 14482 delete callbacks[callback.id]; 14483 delete callbackMap[callbackPath]; 14484 } 14485 }; 14486 }; 14487}; 14488 14489/** 14490 * @ngdoc service 14491 * @name $locale 14492 * 14493 * @description 14494 * $locale service provides localization rules for various AngularJS components. As of right now the 14495 * only public api is: 14496 * 14497 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) 14498 */ 14499 14500/* global stripHash: true */ 14501 14502var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, 14503 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 14504var $locationMinErr = minErr('$location'); 14505 14506 14507/** 14508 * Encode path using encodeUriSegment, ignoring forward slashes 14509 * 14510 * @param {string} path Path to encode 14511 * @returns {string} 14512 */ 14513function encodePath(path) { 14514 var segments = path.split('/'), 14515 i = segments.length; 14516 14517 while (i--) { 14518 // decode forward slashes to prevent them from being double encoded 14519 segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/')); 14520 } 14521 14522 return segments.join('/'); 14523} 14524 14525function decodePath(path, html5Mode) { 14526 var segments = path.split('/'), 14527 i = segments.length; 14528 14529 while (i--) { 14530 segments[i] = decodeURIComponent(segments[i]); 14531 if (html5Mode) { 14532 // encode forward slashes to prevent them from being mistaken for path separators 14533 segments[i] = segments[i].replace(/\//g, '%2F'); 14534 } 14535 } 14536 14537 return segments.join('/'); 14538} 14539 14540function normalizePath(pathValue, searchValue, hashValue) { 14541 var search = toKeyValue(searchValue), 14542 hash = hashValue ? '#' + encodeUriSegment(hashValue) : '', 14543 path = encodePath(pathValue); 14544 14545 return path + (search ? '?' + search : '') + hash; 14546} 14547 14548function parseAbsoluteUrl(absoluteUrl, locationObj) { 14549 var parsedUrl = urlResolve(absoluteUrl); 14550 14551 locationObj.$$protocol = parsedUrl.protocol; 14552 locationObj.$$host = parsedUrl.hostname; 14553 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 14554} 14555 14556var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/; 14557function parseAppUrl(url, locationObj, html5Mode) { 14558 14559 if (DOUBLE_SLASH_REGEX.test(url)) { 14560 throw $locationMinErr('badpath', 'Invalid url "{0}".', url); 14561 } 14562 14563 var prefixed = (url.charAt(0) !== '/'); 14564 if (prefixed) { 14565 url = '/' + url; 14566 } 14567 var match = urlResolve(url); 14568 var path = prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname; 14569 locationObj.$$path = decodePath(path, html5Mode); 14570 locationObj.$$search = parseKeyValue(match.search); 14571 locationObj.$$hash = decodeURIComponent(match.hash); 14572 14573 // make sure path starts with '/'; 14574 if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') { 14575 locationObj.$$path = '/' + locationObj.$$path; 14576 } 14577} 14578 14579function startsWith(str, search) { 14580 return str.slice(0, search.length) === search; 14581} 14582 14583/** 14584 * 14585 * @param {string} base 14586 * @param {string} url 14587 * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with 14588 * the expected string. 14589 */ 14590function stripBaseUrl(base, url) { 14591 if (startsWith(url, base)) { 14592 return url.substr(base.length); 14593 } 14594} 14595 14596function stripHash(url) { 14597 var index = url.indexOf('#'); 14598 return index === -1 ? url : url.substr(0, index); 14599} 14600 14601function stripFile(url) { 14602 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 14603} 14604 14605/* return the server only (scheme://host:port) */ 14606function serverBase(url) { 14607 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 14608} 14609 14610 14611/** 14612 * LocationHtml5Url represents a URL 14613 * This object is exposed as $location service when HTML5 mode is enabled and supported 14614 * 14615 * @constructor 14616 * @param {string} appBase application base URL 14617 * @param {string} appBaseNoFile application base URL stripped of any filename 14618 * @param {string} basePrefix URL path prefix 14619 */ 14620function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 14621 this.$$html5 = true; 14622 basePrefix = basePrefix || ''; 14623 parseAbsoluteUrl(appBase, this); 14624 14625 14626 /** 14627 * Parse given HTML5 (regular) URL string into properties 14628 * @param {string} url HTML5 URL 14629 * @private 14630 */ 14631 this.$$parse = function(url) { 14632 var pathUrl = stripBaseUrl(appBaseNoFile, url); 14633 if (!isString(pathUrl)) { 14634 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 14635 appBaseNoFile); 14636 } 14637 14638 parseAppUrl(pathUrl, this, true); 14639 14640 if (!this.$$path) { 14641 this.$$path = '/'; 14642 } 14643 14644 this.$$compose(); 14645 }; 14646 14647 this.$$normalizeUrl = function(url) { 14648 return appBaseNoFile + url.substr(1); // first char is always '/' 14649 }; 14650 14651 this.$$parseLinkUrl = function(url, relHref) { 14652 if (relHref && relHref[0] === '#') { 14653 // special case for links to hash fragments: 14654 // keep the old url and only replace the hash fragment 14655 this.hash(relHref.slice(1)); 14656 return true; 14657 } 14658 var appUrl, prevAppUrl; 14659 var rewrittenUrl; 14660 14661 14662 if (isDefined(appUrl = stripBaseUrl(appBase, url))) { 14663 prevAppUrl = appUrl; 14664 if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) { 14665 rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl); 14666 } else { 14667 rewrittenUrl = appBase + prevAppUrl; 14668 } 14669 } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) { 14670 rewrittenUrl = appBaseNoFile + appUrl; 14671 } else if (appBaseNoFile === url + '/') { 14672 rewrittenUrl = appBaseNoFile; 14673 } 14674 if (rewrittenUrl) { 14675 this.$$parse(rewrittenUrl); 14676 } 14677 return !!rewrittenUrl; 14678 }; 14679} 14680 14681 14682/** 14683 * LocationHashbangUrl represents URL 14684 * This object is exposed as $location service when developer doesn't opt into html5 mode. 14685 * It also serves as the base class for html5 mode fallback on legacy browsers. 14686 * 14687 * @constructor 14688 * @param {string} appBase application base URL 14689 * @param {string} appBaseNoFile application base URL stripped of any filename 14690 * @param {string} hashPrefix hashbang prefix 14691 */ 14692function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 14693 14694 parseAbsoluteUrl(appBase, this); 14695 14696 14697 /** 14698 * Parse given hashbang URL into properties 14699 * @param {string} url Hashbang URL 14700 * @private 14701 */ 14702 this.$$parse = function(url) { 14703 var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url); 14704 var withoutHashUrl; 14705 14706 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 14707 14708 // The rest of the URL starts with a hash so we have 14709 // got either a hashbang path or a plain hash fragment 14710 withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl); 14711 if (isUndefined(withoutHashUrl)) { 14712 // There was no hashbang prefix so we just have a hash fragment 14713 withoutHashUrl = withoutBaseUrl; 14714 } 14715 14716 } else { 14717 // There was no hashbang path nor hash fragment: 14718 // If we are in HTML5 mode we use what is left as the path; 14719 // Otherwise we ignore what is left 14720 if (this.$$html5) { 14721 withoutHashUrl = withoutBaseUrl; 14722 } else { 14723 withoutHashUrl = ''; 14724 if (isUndefined(withoutBaseUrl)) { 14725 appBase = url; 14726 /** @type {?} */ (this).replace(); 14727 } 14728 } 14729 } 14730 14731 parseAppUrl(withoutHashUrl, this, false); 14732 14733 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 14734 14735 this.$$compose(); 14736 14737 /* 14738 * In Windows, on an anchor node on documents loaded from 14739 * the filesystem, the browser will return a pathname 14740 * prefixed with the drive name ('/C:/path') when a 14741 * pathname without a drive is set: 14742 * * a.setAttribute('href', '/foo') 14743 * * a.pathname === '/C:/foo' //true 14744 * 14745 * Inside of AngularJS, we're always using pathnames that 14746 * do not include drive names for routing. 14747 */ 14748 function removeWindowsDriveName(path, url, base) { 14749 /* 14750 Matches paths for file protocol on windows, 14751 such as /C:/foo/bar, and captures only /foo/bar. 14752 */ 14753 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 14754 14755 var firstPathSegmentMatch; 14756 14757 //Get the relative path from the input URL. 14758 if (startsWith(url, base)) { 14759 url = url.replace(base, ''); 14760 } 14761 14762 // The input URL intentionally contains a first path segment that ends with a colon. 14763 if (windowsFilePathExp.exec(url)) { 14764 return path; 14765 } 14766 14767 firstPathSegmentMatch = windowsFilePathExp.exec(path); 14768 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 14769 } 14770 }; 14771 14772 this.$$normalizeUrl = function(url) { 14773 return appBase + (url ? hashPrefix + url : ''); 14774 }; 14775 14776 this.$$parseLinkUrl = function(url, relHref) { 14777 if (stripHash(appBase) === stripHash(url)) { 14778 this.$$parse(url); 14779 return true; 14780 } 14781 return false; 14782 }; 14783} 14784 14785 14786/** 14787 * LocationHashbangUrl represents URL 14788 * This object is exposed as $location service when html5 history api is enabled but the browser 14789 * does not support it. 14790 * 14791 * @constructor 14792 * @param {string} appBase application base URL 14793 * @param {string} appBaseNoFile application base URL stripped of any filename 14794 * @param {string} hashPrefix hashbang prefix 14795 */ 14796function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 14797 this.$$html5 = true; 14798 LocationHashbangUrl.apply(this, arguments); 14799 14800 this.$$parseLinkUrl = function(url, relHref) { 14801 if (relHref && relHref[0] === '#') { 14802 // special case for links to hash fragments: 14803 // keep the old url and only replace the hash fragment 14804 this.hash(relHref.slice(1)); 14805 return true; 14806 } 14807 14808 var rewrittenUrl; 14809 var appUrl; 14810 14811 if (appBase === stripHash(url)) { 14812 rewrittenUrl = url; 14813 } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) { 14814 rewrittenUrl = appBase + hashPrefix + appUrl; 14815 } else if (appBaseNoFile === url + '/') { 14816 rewrittenUrl = appBaseNoFile; 14817 } 14818 if (rewrittenUrl) { 14819 this.$$parse(rewrittenUrl); 14820 } 14821 return !!rewrittenUrl; 14822 }; 14823 14824 this.$$normalizeUrl = function(url) { 14825 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 14826 return appBase + hashPrefix + url; 14827 }; 14828} 14829 14830 14831var locationPrototype = { 14832 14833 /** 14834 * Ensure absolute URL is initialized. 14835 * @private 14836 */ 14837 $$absUrl:'', 14838 14839 /** 14840 * Are we in html5 mode? 14841 * @private 14842 */ 14843 $$html5: false, 14844 14845 /** 14846 * Has any change been replacing? 14847 * @private 14848 */ 14849 $$replace: false, 14850 14851 /** 14852 * Compose url and update `url` and `absUrl` property 14853 * @private 14854 */ 14855 $$compose: function() { 14856 this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash); 14857 this.$$absUrl = this.$$normalizeUrl(this.$$url); 14858 this.$$urlUpdatedByLocation = true; 14859 }, 14860 14861 /** 14862 * @ngdoc method 14863 * @name $location#absUrl 14864 * 14865 * @description 14866 * This method is getter only. 14867 * 14868 * Return full URL representation with all segments encoded according to rules specified in 14869 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 14870 * 14871 * 14872 * ```js 14873 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14874 * var absUrl = $location.absUrl(); 14875 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 14876 * ``` 14877 * 14878 * @return {string} full URL 14879 */ 14880 absUrl: locationGetter('$$absUrl'), 14881 14882 /** 14883 * @ngdoc method 14884 * @name $location#url 14885 * 14886 * @description 14887 * This method is getter / setter. 14888 * 14889 * Return URL (e.g. `/path?a=b#hash`) when called without any parameter. 14890 * 14891 * Change path, search and hash, when called with parameter and return `$location`. 14892 * 14893 * 14894 * ```js 14895 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14896 * var url = $location.url(); 14897 * // => "/some/path?foo=bar&baz=xoxo" 14898 * ``` 14899 * 14900 * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`) 14901 * @return {string} url 14902 */ 14903 url: function(url) { 14904 if (isUndefined(url)) { 14905 return this.$$url; 14906 } 14907 14908 var match = PATH_MATCH.exec(url); 14909 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 14910 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 14911 this.hash(match[5] || ''); 14912 14913 return this; 14914 }, 14915 14916 /** 14917 * @ngdoc method 14918 * @name $location#protocol 14919 * 14920 * @description 14921 * This method is getter only. 14922 * 14923 * Return protocol of current URL. 14924 * 14925 * 14926 * ```js 14927 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14928 * var protocol = $location.protocol(); 14929 * // => "http" 14930 * ``` 14931 * 14932 * @return {string} protocol of current URL 14933 */ 14934 protocol: locationGetter('$$protocol'), 14935 14936 /** 14937 * @ngdoc method 14938 * @name $location#host 14939 * 14940 * @description 14941 * This method is getter only. 14942 * 14943 * Return host of current URL. 14944 * 14945 * Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 14946 * 14947 * 14948 * ```js 14949 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14950 * var host = $location.host(); 14951 * // => "example.com" 14952 * 14953 * // given URL http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 14954 * host = $location.host(); 14955 * // => "example.com" 14956 * host = location.host; 14957 * // => "example.com:8080" 14958 * ``` 14959 * 14960 * @return {string} host of current URL. 14961 */ 14962 host: locationGetter('$$host'), 14963 14964 /** 14965 * @ngdoc method 14966 * @name $location#port 14967 * 14968 * @description 14969 * This method is getter only. 14970 * 14971 * Return port of current URL. 14972 * 14973 * 14974 * ```js 14975 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 14976 * var port = $location.port(); 14977 * // => 80 14978 * ``` 14979 * 14980 * @return {Number} port 14981 */ 14982 port: locationGetter('$$port'), 14983 14984 /** 14985 * @ngdoc method 14986 * @name $location#path 14987 * 14988 * @description 14989 * This method is getter / setter. 14990 * 14991 * Return path of current URL when called without any parameter. 14992 * 14993 * Change path when called with parameter and return `$location`. 14994 * 14995 * Note: Path should always begin with forward slash (/), this method will add the forward slash 14996 * if it is missing. 14997 * 14998 * 14999 * ```js 15000 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 15001 * var path = $location.path(); 15002 * // => "/some/path" 15003 * ``` 15004 * 15005 * @param {(string|number)=} path New path 15006 * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter 15007 */ 15008 path: locationGetterSetter('$$path', function(path) { 15009 path = path !== null ? path.toString() : ''; 15010 return path.charAt(0) === '/' ? path : '/' + path; 15011 }), 15012 15013 /** 15014 * @ngdoc method 15015 * @name $location#search 15016 * 15017 * @description 15018 * This method is getter / setter. 15019 * 15020 * Return search part (as object) of current URL when called without any parameter. 15021 * 15022 * Change search part when called with parameter and return `$location`. 15023 * 15024 * 15025 * ```js 15026 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo 15027 * var searchObject = $location.search(); 15028 * // => {foo: 'bar', baz: 'xoxo'} 15029 * 15030 * // set foo to 'yipee' 15031 * $location.search('foo', 'yipee'); 15032 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 15033 * ``` 15034 * 15035 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 15036 * hash object. 15037 * 15038 * When called with a single argument the method acts as a setter, setting the `search` component 15039 * of `$location` to the specified value. 15040 * 15041 * If the argument is a hash object containing an array of values, these values will be encoded 15042 * as duplicate search parameters in the URL. 15043 * 15044 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 15045 * will override only a single search property. 15046 * 15047 * If `paramValue` is an array, it will override the property of the `search` component of 15048 * `$location` specified via the first argument. 15049 * 15050 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 15051 * 15052 * If `paramValue` is `true`, the property specified via the first argument will be added with no 15053 * value nor trailing equal sign. 15054 * 15055 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 15056 * one or more arguments returns `$location` object itself. 15057 */ 15058 search: function(search, paramValue) { 15059 switch (arguments.length) { 15060 case 0: 15061 return this.$$search; 15062 case 1: 15063 if (isString(search) || isNumber(search)) { 15064 search = search.toString(); 15065 this.$$search = parseKeyValue(search); 15066 } else if (isObject(search)) { 15067 search = copy(search, {}); 15068 // remove object undefined or null properties 15069 forEach(search, function(value, key) { 15070 if (value == null) delete search[key]; 15071 }); 15072 15073 this.$$search = search; 15074 } else { 15075 throw $locationMinErr('isrcharg', 15076 'The first argument of the `$location#search()` call must be a string or an object.'); 15077 } 15078 break; 15079 default: 15080 if (isUndefined(paramValue) || paramValue === null) { 15081 delete this.$$search[search]; 15082 } else { 15083 this.$$search[search] = paramValue; 15084 } 15085 } 15086 15087 this.$$compose(); 15088 return this; 15089 }, 15090 15091 /** 15092 * @ngdoc method 15093 * @name $location#hash 15094 * 15095 * @description 15096 * This method is getter / setter. 15097 * 15098 * Returns the hash fragment when called without any parameters. 15099 * 15100 * Changes the hash fragment when called with a parameter and returns `$location`. 15101 * 15102 * 15103 * ```js 15104 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 15105 * var hash = $location.hash(); 15106 * // => "hashValue" 15107 * ``` 15108 * 15109 * @param {(string|number)=} hash New hash fragment 15110 * @return {string} hash 15111 */ 15112 hash: locationGetterSetter('$$hash', function(hash) { 15113 return hash !== null ? hash.toString() : ''; 15114 }), 15115 15116 /** 15117 * @ngdoc method 15118 * @name $location#replace 15119 * 15120 * @description 15121 * If called, all changes to $location during the current `$digest` will replace the current history 15122 * record, instead of adding a new one. 15123 */ 15124 replace: function() { 15125 this.$$replace = true; 15126 return this; 15127 } 15128}; 15129 15130forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 15131 Location.prototype = Object.create(locationPrototype); 15132 15133 /** 15134 * @ngdoc method 15135 * @name $location#state 15136 * 15137 * @description 15138 * This method is getter / setter. 15139 * 15140 * Return the history state object when called without any parameter. 15141 * 15142 * Change the history state object when called with one parameter and return `$location`. 15143 * The state object is later passed to `pushState` or `replaceState`. 15144 * 15145 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 15146 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 15147 * older browsers (like IE9 or Android < 4.0), don't use this method. 15148 * 15149 * @param {object=} state State object for pushState or replaceState 15150 * @return {object} state 15151 */ 15152 Location.prototype.state = function(state) { 15153 if (!arguments.length) { 15154 return this.$$state; 15155 } 15156 15157 if (Location !== LocationHtml5Url || !this.$$html5) { 15158 throw $locationMinErr('nostate', 'History API state support is available only ' + 15159 'in HTML5 mode and only in browsers supporting HTML5 History API'); 15160 } 15161 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 15162 // but we're changing the $$state reference to $browser.state() during the $digest 15163 // so the modification window is narrow. 15164 this.$$state = isUndefined(state) ? null : state; 15165 this.$$urlUpdatedByLocation = true; 15166 15167 return this; 15168 }; 15169}); 15170 15171 15172function locationGetter(property) { 15173 return /** @this */ function() { 15174 return this[property]; 15175 }; 15176} 15177 15178 15179function locationGetterSetter(property, preprocess) { 15180 return /** @this */ function(value) { 15181 if (isUndefined(value)) { 15182 return this[property]; 15183 } 15184 15185 this[property] = preprocess(value); 15186 this.$$compose(); 15187 15188 return this; 15189 }; 15190} 15191 15192 15193/** 15194 * @ngdoc service 15195 * @name $location 15196 * 15197 * @requires $rootElement 15198 * 15199 * @description 15200 * The $location service parses the URL in the browser address bar (based on the 15201 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 15202 * available to your application. Changes to the URL in the address bar are reflected into 15203 * $location service and changes to $location are reflected into the browser address bar. 15204 * 15205 * **The $location service:** 15206 * 15207 * - Exposes the current URL in the browser address bar, so you can 15208 * - Watch and observe the URL. 15209 * - Change the URL. 15210 * - Synchronizes the URL with the browser when the user 15211 * - Changes the address bar. 15212 * - Clicks the back or forward button (or clicks a History link). 15213 * - Clicks on a link. 15214 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 15215 * 15216 * For more information see {@link guide/$location Developer Guide: Using $location} 15217 */ 15218 15219/** 15220 * @ngdoc provider 15221 * @name $locationProvider 15222 * @this 15223 * 15224 * @description 15225 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 15226 */ 15227function $LocationProvider() { 15228 var hashPrefix = '!', 15229 html5Mode = { 15230 enabled: false, 15231 requireBase: true, 15232 rewriteLinks: true 15233 }; 15234 15235 /** 15236 * @ngdoc method 15237 * @name $locationProvider#hashPrefix 15238 * @description 15239 * The default value for the prefix is `'!'`. 15240 * @param {string=} prefix Prefix for hash part (containing path and search) 15241 * @returns {*} current value if used as getter or itself (chaining) if used as setter 15242 */ 15243 this.hashPrefix = function(prefix) { 15244 if (isDefined(prefix)) { 15245 hashPrefix = prefix; 15246 return this; 15247 } else { 15248 return hashPrefix; 15249 } 15250 }; 15251 15252 /** 15253 * @ngdoc method 15254 * @name $locationProvider#html5Mode 15255 * @description 15256 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 15257 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 15258 * properties: 15259 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to 15260 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 15261 * support `pushState`. 15262 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 15263 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 15264 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 15265 * See the {@link guide/$location $location guide for more information} 15266 * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled, 15267 * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will 15268 * only happen on links with an attribute that matches the given string. For example, if set 15269 * to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links. 15270 * Note that [attribute name normalization](guide/directive#normalization) does not apply 15271 * here, so `'internalLink'` will **not** match `'internal-link'`. 15272 * 15273 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 15274 */ 15275 this.html5Mode = function(mode) { 15276 if (isBoolean(mode)) { 15277 html5Mode.enabled = mode; 15278 return this; 15279 } else if (isObject(mode)) { 15280 15281 if (isBoolean(mode.enabled)) { 15282 html5Mode.enabled = mode.enabled; 15283 } 15284 15285 if (isBoolean(mode.requireBase)) { 15286 html5Mode.requireBase = mode.requireBase; 15287 } 15288 15289 if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) { 15290 html5Mode.rewriteLinks = mode.rewriteLinks; 15291 } 15292 15293 return this; 15294 } else { 15295 return html5Mode; 15296 } 15297 }; 15298 15299 /** 15300 * @ngdoc event 15301 * @name $location#$locationChangeStart 15302 * @eventType broadcast on root scope 15303 * @description 15304 * Broadcasted before a URL will change. 15305 * 15306 * This change can be prevented by calling 15307 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 15308 * details about event object. Upon successful change 15309 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 15310 * 15311 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 15312 * the browser supports the HTML5 History API. 15313 * 15314 * @param {Object} angularEvent Synthetic event object. 15315 * @param {string} newUrl New URL 15316 * @param {string=} oldUrl URL that was before it was changed. 15317 * @param {string=} newState New history state object 15318 * @param {string=} oldState History state object that was before it was changed. 15319 */ 15320 15321 /** 15322 * @ngdoc event 15323 * @name $location#$locationChangeSuccess 15324 * @eventType broadcast on root scope 15325 * @description 15326 * Broadcasted after a URL was changed. 15327 * 15328 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 15329 * the browser supports the HTML5 History API. 15330 * 15331 * @param {Object} angularEvent Synthetic event object. 15332 * @param {string} newUrl New URL 15333 * @param {string=} oldUrl URL that was before it was changed. 15334 * @param {string=} newState New history state object 15335 * @param {string=} oldState History state object that was before it was changed. 15336 */ 15337 15338 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 15339 function($rootScope, $browser, $sniffer, $rootElement, $window) { 15340 var $location, 15341 LocationMode, 15342 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 15343 initialUrl = $browser.url(), 15344 appBase; 15345 15346 if (html5Mode.enabled) { 15347 if (!baseHref && html5Mode.requireBase) { 15348 throw $locationMinErr('nobase', 15349 '$location in HTML5 mode requires a <base> tag to be present!'); 15350 } 15351 appBase = serverBase(initialUrl) + (baseHref || '/'); 15352 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 15353 } else { 15354 appBase = stripHash(initialUrl); 15355 LocationMode = LocationHashbangUrl; 15356 } 15357 var appBaseNoFile = stripFile(appBase); 15358 15359 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 15360 $location.$$parseLinkUrl(initialUrl, initialUrl); 15361 15362 $location.$$state = $browser.state(); 15363 15364 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 15365 15366 // Determine if two URLs are equal despite potentially having different encoding/normalizing 15367 // such as $location.absUrl() vs $browser.url() 15368 // See https://github.com/angular/angular.js/issues/16592 15369 function urlsEqual(a, b) { 15370 return a === b || urlResolve(a).href === urlResolve(b).href; 15371 } 15372 15373 function setBrowserUrlWithFallback(url, replace, state) { 15374 var oldUrl = $location.url(); 15375 var oldState = $location.$$state; 15376 try { 15377 $browser.url(url, replace, state); 15378 15379 // Make sure $location.state() returns referentially identical (not just deeply equal) 15380 // state object; this makes possible quick checking if the state changed in the digest 15381 // loop. Checking deep equality would be too expensive. 15382 $location.$$state = $browser.state(); 15383 } catch (e) { 15384 // Restore old values if pushState fails 15385 $location.url(oldUrl); 15386 $location.$$state = oldState; 15387 15388 throw e; 15389 } 15390 } 15391 15392 $rootElement.on('click', function(event) { 15393 var rewriteLinks = html5Mode.rewriteLinks; 15394 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 15395 // currently we open nice url link and redirect then 15396 15397 if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return; 15398 15399 var elm = jqLite(event.target); 15400 15401 // traverse the DOM up to find first A tag 15402 while (nodeName_(elm[0]) !== 'a') { 15403 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 15404 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 15405 } 15406 15407 if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return; 15408 15409 var absHref = elm.prop('href'); 15410 // get the actual href attribute - see 15411 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 15412 var relHref = elm.attr('href') || elm.attr('xlink:href'); 15413 15414 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 15415 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 15416 // an animation. 15417 absHref = urlResolve(absHref.animVal).href; 15418 } 15419 15420 // Ignore when url is started with javascript: or mailto: 15421 if (IGNORE_URI_REGEXP.test(absHref)) return; 15422 15423 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 15424 if ($location.$$parseLinkUrl(absHref, relHref)) { 15425 // We do a preventDefault for all urls that are part of the AngularJS application, 15426 // in html5mode and also without, so that we are able to abort navigation without 15427 // getting double entries in the location history. 15428 event.preventDefault(); 15429 // update location manually 15430 if ($location.absUrl() !== $browser.url()) { 15431 $rootScope.$apply(); 15432 } 15433 } 15434 } 15435 }); 15436 15437 15438 // rewrite hashbang url <> html5 url 15439 if ($location.absUrl() !== initialUrl) { 15440 $browser.url($location.absUrl(), true); 15441 } 15442 15443 var initializing = true; 15444 15445 // update $location when $browser url changes 15446 $browser.onUrlChange(function(newUrl, newState) { 15447 15448 if (!startsWith(newUrl, appBaseNoFile)) { 15449 // If we are navigating outside of the app then force a reload 15450 $window.location.href = newUrl; 15451 return; 15452 } 15453 15454 $rootScope.$evalAsync(function() { 15455 var oldUrl = $location.absUrl(); 15456 var oldState = $location.$$state; 15457 var defaultPrevented; 15458 $location.$$parse(newUrl); 15459 $location.$$state = newState; 15460 15461 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 15462 newState, oldState).defaultPrevented; 15463 15464 // if the location was changed by a `$locationChangeStart` handler then stop 15465 // processing this location change 15466 if ($location.absUrl() !== newUrl) return; 15467 15468 if (defaultPrevented) { 15469 $location.$$parse(oldUrl); 15470 $location.$$state = oldState; 15471 setBrowserUrlWithFallback(oldUrl, false, oldState); 15472 } else { 15473 initializing = false; 15474 afterLocationChange(oldUrl, oldState); 15475 } 15476 }); 15477 if (!$rootScope.$$phase) $rootScope.$digest(); 15478 }); 15479 15480 // update browser 15481 $rootScope.$watch(function $locationWatch() { 15482 if (initializing || $location.$$urlUpdatedByLocation) { 15483 $location.$$urlUpdatedByLocation = false; 15484 15485 var oldUrl = $browser.url(); 15486 var newUrl = $location.absUrl(); 15487 var oldState = $browser.state(); 15488 var currentReplace = $location.$$replace; 15489 var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) || 15490 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 15491 15492 if (initializing || urlOrStateChanged) { 15493 initializing = false; 15494 15495 $rootScope.$evalAsync(function() { 15496 var newUrl = $location.absUrl(); 15497 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 15498 $location.$$state, oldState).defaultPrevented; 15499 15500 // if the location was changed by a `$locationChangeStart` handler then stop 15501 // processing this location change 15502 if ($location.absUrl() !== newUrl) return; 15503 15504 if (defaultPrevented) { 15505 $location.$$parse(oldUrl); 15506 $location.$$state = oldState; 15507 } else { 15508 if (urlOrStateChanged) { 15509 setBrowserUrlWithFallback(newUrl, currentReplace, 15510 oldState === $location.$$state ? null : $location.$$state); 15511 } 15512 afterLocationChange(oldUrl, oldState); 15513 } 15514 }); 15515 } 15516 } 15517 15518 $location.$$replace = false; 15519 15520 // we don't need to return anything because $evalAsync will make the digest loop dirty when 15521 // there is a change 15522 }); 15523 15524 return $location; 15525 15526 function afterLocationChange(oldUrl, oldState) { 15527 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 15528 $location.$$state, oldState); 15529 } 15530}]; 15531} 15532 15533/** 15534 * @ngdoc service 15535 * @name $log 15536 * @requires $window 15537 * 15538 * @description 15539 * Simple service for logging. Default implementation safely writes the message 15540 * into the browser's console (if present). 15541 * 15542 * The main purpose of this service is to simplify debugging and troubleshooting. 15543 * 15544 * To reveal the location of the calls to `$log` in the JavaScript console, 15545 * you can "blackbox" the AngularJS source in your browser: 15546 * 15547 * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source). 15548 * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing). 15549 * 15550 * Note: Not all browsers support blackboxing. 15551 * 15552 * The default is to log `debug` messages. You can use 15553 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 15554 * 15555 * @example 15556 <example module="logExample" name="log-service"> 15557 <file name="script.js"> 15558 angular.module('logExample', []) 15559 .controller('LogController', ['$scope', '$log', function($scope, $log) { 15560 $scope.$log = $log; 15561 $scope.message = 'Hello World!'; 15562 }]); 15563 </file> 15564 <file name="index.html"> 15565 <div ng-controller="LogController"> 15566 <p>Reload this page with open console, enter text and hit the log button...</p> 15567 <label>Message: 15568 <input type="text" ng-model="message" /></label> 15569 <button ng-click="$log.log(message)">log</button> 15570 <button ng-click="$log.warn(message)">warn</button> 15571 <button ng-click="$log.info(message)">info</button> 15572 <button ng-click="$log.error(message)">error</button> 15573 <button ng-click="$log.debug(message)">debug</button> 15574 </div> 15575 </file> 15576 </example> 15577 */ 15578 15579/** 15580 * @ngdoc provider 15581 * @name $logProvider 15582 * @this 15583 * 15584 * @description 15585 * Use the `$logProvider` to configure how the application logs messages 15586 */ 15587function $LogProvider() { 15588 var debug = true, 15589 self = this; 15590 15591 /** 15592 * @ngdoc method 15593 * @name $logProvider#debugEnabled 15594 * @description 15595 * @param {boolean=} flag enable or disable debug level messages 15596 * @returns {*} current value if used as getter or itself (chaining) if used as setter 15597 */ 15598 this.debugEnabled = function(flag) { 15599 if (isDefined(flag)) { 15600 debug = flag; 15601 return this; 15602 } else { 15603 return debug; 15604 } 15605 }; 15606 15607 this.$get = ['$window', function($window) { 15608 // Support: IE 9-11, Edge 12-14+ 15609 // IE/Edge display errors in such a way that it requires the user to click in 4 places 15610 // to see the stack trace. There is no way to feature-detect it so there's a chance 15611 // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't 15612 // break apps. Other browsers display errors in a sensible way and some of them map stack 15613 // traces along source maps if available so it makes sense to let browsers display it 15614 // as they want. 15615 var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent); 15616 15617 return { 15618 /** 15619 * @ngdoc method 15620 * @name $log#log 15621 * 15622 * @description 15623 * Write a log message 15624 */ 15625 log: consoleLog('log'), 15626 15627 /** 15628 * @ngdoc method 15629 * @name $log#info 15630 * 15631 * @description 15632 * Write an information message 15633 */ 15634 info: consoleLog('info'), 15635 15636 /** 15637 * @ngdoc method 15638 * @name $log#warn 15639 * 15640 * @description 15641 * Write a warning message 15642 */ 15643 warn: consoleLog('warn'), 15644 15645 /** 15646 * @ngdoc method 15647 * @name $log#error 15648 * 15649 * @description 15650 * Write an error message 15651 */ 15652 error: consoleLog('error'), 15653 15654 /** 15655 * @ngdoc method 15656 * @name $log#debug 15657 * 15658 * @description 15659 * Write a debug message 15660 */ 15661 debug: (function() { 15662 var fn = consoleLog('debug'); 15663 15664 return function() { 15665 if (debug) { 15666 fn.apply(self, arguments); 15667 } 15668 }; 15669 })() 15670 }; 15671 15672 function formatError(arg) { 15673 if (isError(arg)) { 15674 if (arg.stack && formatStackTrace) { 15675 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 15676 ? 'Error: ' + arg.message + '\n' + arg.stack 15677 : arg.stack; 15678 } else if (arg.sourceURL) { 15679 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 15680 } 15681 } 15682 return arg; 15683 } 15684 15685 function consoleLog(type) { 15686 var console = $window.console || {}, 15687 logFn = console[type] || console.log || noop; 15688 15689 return function() { 15690 var args = []; 15691 forEach(arguments, function(arg) { 15692 args.push(formatError(arg)); 15693 }); 15694 // Support: IE 9 only 15695 // console methods don't inherit from Function.prototype in IE 9 so we can't 15696 // call `logFn.apply(console, args)` directly. 15697 return Function.prototype.apply.call(logFn, console, args); 15698 }; 15699 } 15700 }]; 15701} 15702 15703/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 15704 * Any commits to this file should be reviewed with security in mind. * 15705 * Changes to this file can potentially create security vulnerabilities. * 15706 * An approval from 2 Core members with history of modifying * 15707 * this file is required. * 15708 * * 15709 * Does the change somehow allow for arbitrary javascript to be executed? * 15710 * Or allows for someone to change the prototype of built-in objects? * 15711 * Or gives undesired access to variables likes document or window? * 15712 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 15713 15714var $parseMinErr = minErr('$parse'); 15715 15716var objectValueOf = {}.constructor.prototype.valueOf; 15717 15718// Sandboxing AngularJS Expressions 15719// ------------------------------ 15720// AngularJS expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by 15721// various means such as obtaining a reference to native JS functions like the Function constructor. 15722// 15723// As an example, consider the following AngularJS expression: 15724// 15725// {}.toString.constructor('alert("evil JS code")') 15726// 15727// It is important to realize that if you create an expression from a string that contains user provided 15728// content then it is possible that your application contains a security vulnerability to an XSS style attack. 15729// 15730// See https://docs.angularjs.org/guide/security 15731 15732 15733function getStringValue(name) { 15734 // Property names must be strings. This means that non-string objects cannot be used 15735 // as keys in an object. Any non-string object, including a number, is typecasted 15736 // into a string via the toString method. 15737 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 15738 // 15739 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 15740 // to a string. It's not always possible. If `name` is an object and its `toString` method is 15741 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 15742 // 15743 // TypeError: Cannot convert object to primitive value 15744 // 15745 // For performance reasons, we don't catch this error here and allow it to propagate up the call 15746 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 15747 // such a 'broken' object as a key. 15748 return name + ''; 15749} 15750 15751 15752var OPERATORS = createMap(); 15753forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 15754var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'}; 15755 15756 15757///////////////////////////////////////// 15758 15759 15760/** 15761 * @constructor 15762 */ 15763var Lexer = function Lexer(options) { 15764 this.options = options; 15765}; 15766 15767Lexer.prototype = { 15768 constructor: Lexer, 15769 15770 lex: function(text) { 15771 this.text = text; 15772 this.index = 0; 15773 this.tokens = []; 15774 15775 while (this.index < this.text.length) { 15776 var ch = this.text.charAt(this.index); 15777 if (ch === '"' || ch === '\'') { 15778 this.readString(ch); 15779 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 15780 this.readNumber(); 15781 } else if (this.isIdentifierStart(this.peekMultichar())) { 15782 this.readIdent(); 15783 } else if (this.is(ch, '(){}[].,;:?')) { 15784 this.tokens.push({index: this.index, text: ch}); 15785 this.index++; 15786 } else if (this.isWhitespace(ch)) { 15787 this.index++; 15788 } else { 15789 var ch2 = ch + this.peek(); 15790 var ch3 = ch2 + this.peek(2); 15791 var op1 = OPERATORS[ch]; 15792 var op2 = OPERATORS[ch2]; 15793 var op3 = OPERATORS[ch3]; 15794 if (op1 || op2 || op3) { 15795 var token = op3 ? ch3 : (op2 ? ch2 : ch); 15796 this.tokens.push({index: this.index, text: token, operator: true}); 15797 this.index += token.length; 15798 } else { 15799 this.throwError('Unexpected next character ', this.index, this.index + 1); 15800 } 15801 } 15802 } 15803 return this.tokens; 15804 }, 15805 15806 is: function(ch, chars) { 15807 return chars.indexOf(ch) !== -1; 15808 }, 15809 15810 peek: function(i) { 15811 var num = i || 1; 15812 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 15813 }, 15814 15815 isNumber: function(ch) { 15816 return ('0' <= ch && ch <= '9') && typeof ch === 'string'; 15817 }, 15818 15819 isWhitespace: function(ch) { 15820 // IE treats non-breaking space as \u00A0 15821 return (ch === ' ' || ch === '\r' || ch === '\t' || 15822 ch === '\n' || ch === '\v' || ch === '\u00A0'); 15823 }, 15824 15825 isIdentifierStart: function(ch) { 15826 return this.options.isIdentifierStart ? 15827 this.options.isIdentifierStart(ch, this.codePointAt(ch)) : 15828 this.isValidIdentifierStart(ch); 15829 }, 15830 15831 isValidIdentifierStart: function(ch) { 15832 return ('a' <= ch && ch <= 'z' || 15833 'A' <= ch && ch <= 'Z' || 15834 '_' === ch || ch === '$'); 15835 }, 15836 15837 isIdentifierContinue: function(ch) { 15838 return this.options.isIdentifierContinue ? 15839 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : 15840 this.isValidIdentifierContinue(ch); 15841 }, 15842 15843 isValidIdentifierContinue: function(ch, cp) { 15844 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); 15845 }, 15846 15847 codePointAt: function(ch) { 15848 if (ch.length === 1) return ch.charCodeAt(0); 15849 // eslint-disable-next-line no-bitwise 15850 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; 15851 }, 15852 15853 peekMultichar: function() { 15854 var ch = this.text.charAt(this.index); 15855 var peek = this.peek(); 15856 if (!peek) { 15857 return ch; 15858 } 15859 var cp1 = ch.charCodeAt(0); 15860 var cp2 = peek.charCodeAt(0); 15861 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { 15862 return ch + peek; 15863 } 15864 return ch; 15865 }, 15866 15867 isExpOperator: function(ch) { 15868 return (ch === '-' || ch === '+' || this.isNumber(ch)); 15869 }, 15870 15871 throwError: function(error, start, end) { 15872 end = end || this.index; 15873 var colStr = (isDefined(start) 15874 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 15875 : ' ' + end); 15876 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 15877 error, colStr, this.text); 15878 }, 15879 15880 readNumber: function() { 15881 var number = ''; 15882 var start = this.index; 15883 while (this.index < this.text.length) { 15884 var ch = lowercase(this.text.charAt(this.index)); 15885 if (ch === '.' || this.isNumber(ch)) { 15886 number += ch; 15887 } else { 15888 var peekCh = this.peek(); 15889 if (ch === 'e' && this.isExpOperator(peekCh)) { 15890 number += ch; 15891 } else if (this.isExpOperator(ch) && 15892 peekCh && this.isNumber(peekCh) && 15893 number.charAt(number.length - 1) === 'e') { 15894 number += ch; 15895 } else if (this.isExpOperator(ch) && 15896 (!peekCh || !this.isNumber(peekCh)) && 15897 number.charAt(number.length - 1) === 'e') { 15898 this.throwError('Invalid exponent'); 15899 } else { 15900 break; 15901 } 15902 } 15903 this.index++; 15904 } 15905 this.tokens.push({ 15906 index: start, 15907 text: number, 15908 constant: true, 15909 value: Number(number) 15910 }); 15911 }, 15912 15913 readIdent: function() { 15914 var start = this.index; 15915 this.index += this.peekMultichar().length; 15916 while (this.index < this.text.length) { 15917 var ch = this.peekMultichar(); 15918 if (!this.isIdentifierContinue(ch)) { 15919 break; 15920 } 15921 this.index += ch.length; 15922 } 15923 this.tokens.push({ 15924 index: start, 15925 text: this.text.slice(start, this.index), 15926 identifier: true 15927 }); 15928 }, 15929 15930 readString: function(quote) { 15931 var start = this.index; 15932 this.index++; 15933 var string = ''; 15934 var rawString = quote; 15935 var escape = false; 15936 while (this.index < this.text.length) { 15937 var ch = this.text.charAt(this.index); 15938 rawString += ch; 15939 if (escape) { 15940 if (ch === 'u') { 15941 var hex = this.text.substring(this.index + 1, this.index + 5); 15942 if (!hex.match(/[\da-f]{4}/i)) { 15943 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 15944 } 15945 this.index += 4; 15946 string += String.fromCharCode(parseInt(hex, 16)); 15947 } else { 15948 var rep = ESCAPE[ch]; 15949 string = string + (rep || ch); 15950 } 15951 escape = false; 15952 } else if (ch === '\\') { 15953 escape = true; 15954 } else if (ch === quote) { 15955 this.index++; 15956 this.tokens.push({ 15957 index: start, 15958 text: rawString, 15959 constant: true, 15960 value: string 15961 }); 15962 return; 15963 } else { 15964 string += ch; 15965 } 15966 this.index++; 15967 } 15968 this.throwError('Unterminated quote', start); 15969 } 15970}; 15971 15972var AST = function AST(lexer, options) { 15973 this.lexer = lexer; 15974 this.options = options; 15975}; 15976 15977AST.Program = 'Program'; 15978AST.ExpressionStatement = 'ExpressionStatement'; 15979AST.AssignmentExpression = 'AssignmentExpression'; 15980AST.ConditionalExpression = 'ConditionalExpression'; 15981AST.LogicalExpression = 'LogicalExpression'; 15982AST.BinaryExpression = 'BinaryExpression'; 15983AST.UnaryExpression = 'UnaryExpression'; 15984AST.CallExpression = 'CallExpression'; 15985AST.MemberExpression = 'MemberExpression'; 15986AST.Identifier = 'Identifier'; 15987AST.Literal = 'Literal'; 15988AST.ArrayExpression = 'ArrayExpression'; 15989AST.Property = 'Property'; 15990AST.ObjectExpression = 'ObjectExpression'; 15991AST.ThisExpression = 'ThisExpression'; 15992AST.LocalsExpression = 'LocalsExpression'; 15993 15994// Internal use only 15995AST.NGValueParameter = 'NGValueParameter'; 15996 15997AST.prototype = { 15998 ast: function(text) { 15999 this.text = text; 16000 this.tokens = this.lexer.lex(text); 16001 16002 var value = this.program(); 16003 16004 if (this.tokens.length !== 0) { 16005 this.throwError('is an unexpected token', this.tokens[0]); 16006 } 16007 16008 return value; 16009 }, 16010 16011 program: function() { 16012 var body = []; 16013 while (true) { 16014 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 16015 body.push(this.expressionStatement()); 16016 if (!this.expect(';')) { 16017 return { type: AST.Program, body: body}; 16018 } 16019 } 16020 }, 16021 16022 expressionStatement: function() { 16023 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 16024 }, 16025 16026 filterChain: function() { 16027 var left = this.expression(); 16028 while (this.expect('|')) { 16029 left = this.filter(left); 16030 } 16031 return left; 16032 }, 16033 16034 expression: function() { 16035 return this.assignment(); 16036 }, 16037 16038 assignment: function() { 16039 var result = this.ternary(); 16040 if (this.expect('=')) { 16041 if (!isAssignable(result)) { 16042 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 16043 } 16044 16045 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 16046 } 16047 return result; 16048 }, 16049 16050 ternary: function() { 16051 var test = this.logicalOR(); 16052 var alternate; 16053 var consequent; 16054 if (this.expect('?')) { 16055 alternate = this.expression(); 16056 if (this.consume(':')) { 16057 consequent = this.expression(); 16058 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 16059 } 16060 } 16061 return test; 16062 }, 16063 16064 logicalOR: function() { 16065 var left = this.logicalAND(); 16066 while (this.expect('||')) { 16067 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 16068 } 16069 return left; 16070 }, 16071 16072 logicalAND: function() { 16073 var left = this.equality(); 16074 while (this.expect('&&')) { 16075 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 16076 } 16077 return left; 16078 }, 16079 16080 equality: function() { 16081 var left = this.relational(); 16082 var token; 16083 while ((token = this.expect('==','!=','===','!=='))) { 16084 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 16085 } 16086 return left; 16087 }, 16088 16089 relational: function() { 16090 var left = this.additive(); 16091 var token; 16092 while ((token = this.expect('<', '>', '<=', '>='))) { 16093 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 16094 } 16095 return left; 16096 }, 16097 16098 additive: function() { 16099 var left = this.multiplicative(); 16100 var token; 16101 while ((token = this.expect('+','-'))) { 16102 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 16103 } 16104 return left; 16105 }, 16106 16107 multiplicative: function() { 16108 var left = this.unary(); 16109 var token; 16110 while ((token = this.expect('*','/','%'))) { 16111 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 16112 } 16113 return left; 16114 }, 16115 16116 unary: function() { 16117 var token; 16118 if ((token = this.expect('+', '-', '!'))) { 16119 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 16120 } else { 16121 return this.primary(); 16122 } 16123 }, 16124 16125 primary: function() { 16126 var primary; 16127 if (this.expect('(')) { 16128 primary = this.filterChain(); 16129 this.consume(')'); 16130 } else if (this.expect('[')) { 16131 primary = this.arrayDeclaration(); 16132 } else if (this.expect('{')) { 16133 primary = this.object(); 16134 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { 16135 primary = copy(this.selfReferential[this.consume().text]); 16136 } else if (this.options.literals.hasOwnProperty(this.peek().text)) { 16137 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; 16138 } else if (this.peek().identifier) { 16139 primary = this.identifier(); 16140 } else if (this.peek().constant) { 16141 primary = this.constant(); 16142 } else { 16143 this.throwError('not a primary expression', this.peek()); 16144 } 16145 16146 var next; 16147 while ((next = this.expect('(', '[', '.'))) { 16148 if (next.text === '(') { 16149 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 16150 this.consume(')'); 16151 } else if (next.text === '[') { 16152 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 16153 this.consume(']'); 16154 } else if (next.text === '.') { 16155 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 16156 } else { 16157 this.throwError('IMPOSSIBLE'); 16158 } 16159 } 16160 return primary; 16161 }, 16162 16163 filter: function(baseExpression) { 16164 var args = [baseExpression]; 16165 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 16166 16167 while (this.expect(':')) { 16168 args.push(this.expression()); 16169 } 16170 16171 return result; 16172 }, 16173 16174 parseArguments: function() { 16175 var args = []; 16176 if (this.peekToken().text !== ')') { 16177 do { 16178 args.push(this.filterChain()); 16179 } while (this.expect(',')); 16180 } 16181 return args; 16182 }, 16183 16184 identifier: function() { 16185 var token = this.consume(); 16186 if (!token.identifier) { 16187 this.throwError('is not a valid identifier', token); 16188 } 16189 return { type: AST.Identifier, name: token.text }; 16190 }, 16191 16192 constant: function() { 16193 // TODO check that it is a constant 16194 return { type: AST.Literal, value: this.consume().value }; 16195 }, 16196 16197 arrayDeclaration: function() { 16198 var elements = []; 16199 if (this.peekToken().text !== ']') { 16200 do { 16201 if (this.peek(']')) { 16202 // Support trailing commas per ES5.1. 16203 break; 16204 } 16205 elements.push(this.expression()); 16206 } while (this.expect(',')); 16207 } 16208 this.consume(']'); 16209 16210 return { type: AST.ArrayExpression, elements: elements }; 16211 }, 16212 16213 object: function() { 16214 var properties = [], property; 16215 if (this.peekToken().text !== '}') { 16216 do { 16217 if (this.peek('}')) { 16218 // Support trailing commas per ES5.1. 16219 break; 16220 } 16221 property = {type: AST.Property, kind: 'init'}; 16222 if (this.peek().constant) { 16223 property.key = this.constant(); 16224 property.computed = false; 16225 this.consume(':'); 16226 property.value = this.expression(); 16227 } else if (this.peek().identifier) { 16228 property.key = this.identifier(); 16229 property.computed = false; 16230 if (this.peek(':')) { 16231 this.consume(':'); 16232 property.value = this.expression(); 16233 } else { 16234 property.value = property.key; 16235 } 16236 } else if (this.peek('[')) { 16237 this.consume('['); 16238 property.key = this.expression(); 16239 this.consume(']'); 16240 property.computed = true; 16241 this.consume(':'); 16242 property.value = this.expression(); 16243 } else { 16244 this.throwError('invalid key', this.peek()); 16245 } 16246 properties.push(property); 16247 } while (this.expect(',')); 16248 } 16249 this.consume('}'); 16250 16251 return {type: AST.ObjectExpression, properties: properties }; 16252 }, 16253 16254 throwError: function(msg, token) { 16255 throw $parseMinErr('syntax', 16256 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 16257 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 16258 }, 16259 16260 consume: function(e1) { 16261 if (this.tokens.length === 0) { 16262 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 16263 } 16264 16265 var token = this.expect(e1); 16266 if (!token) { 16267 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 16268 } 16269 return token; 16270 }, 16271 16272 peekToken: function() { 16273 if (this.tokens.length === 0) { 16274 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 16275 } 16276 return this.tokens[0]; 16277 }, 16278 16279 peek: function(e1, e2, e3, e4) { 16280 return this.peekAhead(0, e1, e2, e3, e4); 16281 }, 16282 16283 peekAhead: function(i, e1, e2, e3, e4) { 16284 if (this.tokens.length > i) { 16285 var token = this.tokens[i]; 16286 var t = token.text; 16287 if (t === e1 || t === e2 || t === e3 || t === e4 || 16288 (!e1 && !e2 && !e3 && !e4)) { 16289 return token; 16290 } 16291 } 16292 return false; 16293 }, 16294 16295 expect: function(e1, e2, e3, e4) { 16296 var token = this.peek(e1, e2, e3, e4); 16297 if (token) { 16298 this.tokens.shift(); 16299 return token; 16300 } 16301 return false; 16302 }, 16303 16304 selfReferential: { 16305 'this': {type: AST.ThisExpression }, 16306 '$locals': {type: AST.LocalsExpression } 16307 } 16308}; 16309 16310function ifDefined(v, d) { 16311 return typeof v !== 'undefined' ? v : d; 16312} 16313 16314function plusFn(l, r) { 16315 if (typeof l === 'undefined') return r; 16316 if (typeof r === 'undefined') return l; 16317 return l + r; 16318} 16319 16320function isStateless($filter, filterName) { 16321 var fn = $filter(filterName); 16322 return !fn.$stateful; 16323} 16324 16325var PURITY_ABSOLUTE = 1; 16326var PURITY_RELATIVE = 2; 16327 16328// Detect nodes which could depend on non-shallow state of objects 16329function isPure(node, parentIsPure) { 16330 switch (node.type) { 16331 // Computed members might invoke a stateful toString() 16332 case AST.MemberExpression: 16333 if (node.computed) { 16334 return false; 16335 } 16336 break; 16337 16338 // Unary always convert to primative 16339 case AST.UnaryExpression: 16340 return PURITY_ABSOLUTE; 16341 16342 // The binary + operator can invoke a stateful toString(). 16343 case AST.BinaryExpression: 16344 return node.operator !== '+' ? PURITY_ABSOLUTE : false; 16345 16346 // Functions / filters probably read state from within objects 16347 case AST.CallExpression: 16348 return false; 16349 } 16350 16351 return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure; 16352} 16353 16354function findConstantAndWatchExpressions(ast, $filter, parentIsPure) { 16355 var allConstants; 16356 var argsToWatch; 16357 var isStatelessFilter; 16358 16359 var astIsPure = ast.isPure = isPure(ast, parentIsPure); 16360 16361 switch (ast.type) { 16362 case AST.Program: 16363 allConstants = true; 16364 forEach(ast.body, function(expr) { 16365 findConstantAndWatchExpressions(expr.expression, $filter, astIsPure); 16366 allConstants = allConstants && expr.expression.constant; 16367 }); 16368 ast.constant = allConstants; 16369 break; 16370 case AST.Literal: 16371 ast.constant = true; 16372 ast.toWatch = []; 16373 break; 16374 case AST.UnaryExpression: 16375 findConstantAndWatchExpressions(ast.argument, $filter, astIsPure); 16376 ast.constant = ast.argument.constant; 16377 ast.toWatch = ast.argument.toWatch; 16378 break; 16379 case AST.BinaryExpression: 16380 findConstantAndWatchExpressions(ast.left, $filter, astIsPure); 16381 findConstantAndWatchExpressions(ast.right, $filter, astIsPure); 16382 ast.constant = ast.left.constant && ast.right.constant; 16383 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 16384 break; 16385 case AST.LogicalExpression: 16386 findConstantAndWatchExpressions(ast.left, $filter, astIsPure); 16387 findConstantAndWatchExpressions(ast.right, $filter, astIsPure); 16388 ast.constant = ast.left.constant && ast.right.constant; 16389 ast.toWatch = ast.constant ? [] : [ast]; 16390 break; 16391 case AST.ConditionalExpression: 16392 findConstantAndWatchExpressions(ast.test, $filter, astIsPure); 16393 findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure); 16394 findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure); 16395 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 16396 ast.toWatch = ast.constant ? [] : [ast]; 16397 break; 16398 case AST.Identifier: 16399 ast.constant = false; 16400 ast.toWatch = [ast]; 16401 break; 16402 case AST.MemberExpression: 16403 findConstantAndWatchExpressions(ast.object, $filter, astIsPure); 16404 if (ast.computed) { 16405 findConstantAndWatchExpressions(ast.property, $filter, astIsPure); 16406 } 16407 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 16408 ast.toWatch = ast.constant ? [] : [ast]; 16409 break; 16410 case AST.CallExpression: 16411 isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false; 16412 allConstants = isStatelessFilter; 16413 argsToWatch = []; 16414 forEach(ast.arguments, function(expr) { 16415 findConstantAndWatchExpressions(expr, $filter, astIsPure); 16416 allConstants = allConstants && expr.constant; 16417 argsToWatch.push.apply(argsToWatch, expr.toWatch); 16418 }); 16419 ast.constant = allConstants; 16420 ast.toWatch = isStatelessFilter ? argsToWatch : [ast]; 16421 break; 16422 case AST.AssignmentExpression: 16423 findConstantAndWatchExpressions(ast.left, $filter, astIsPure); 16424 findConstantAndWatchExpressions(ast.right, $filter, astIsPure); 16425 ast.constant = ast.left.constant && ast.right.constant; 16426 ast.toWatch = [ast]; 16427 break; 16428 case AST.ArrayExpression: 16429 allConstants = true; 16430 argsToWatch = []; 16431 forEach(ast.elements, function(expr) { 16432 findConstantAndWatchExpressions(expr, $filter, astIsPure); 16433 allConstants = allConstants && expr.constant; 16434 argsToWatch.push.apply(argsToWatch, expr.toWatch); 16435 }); 16436 ast.constant = allConstants; 16437 ast.toWatch = argsToWatch; 16438 break; 16439 case AST.ObjectExpression: 16440 allConstants = true; 16441 argsToWatch = []; 16442 forEach(ast.properties, function(property) { 16443 findConstantAndWatchExpressions(property.value, $filter, astIsPure); 16444 allConstants = allConstants && property.value.constant; 16445 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 16446 if (property.computed) { 16447 //`{[key]: value}` implicitly does `key.toString()` which may be non-pure 16448 findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false); 16449 allConstants = allConstants && property.key.constant; 16450 argsToWatch.push.apply(argsToWatch, property.key.toWatch); 16451 } 16452 }); 16453 ast.constant = allConstants; 16454 ast.toWatch = argsToWatch; 16455 break; 16456 case AST.ThisExpression: 16457 ast.constant = false; 16458 ast.toWatch = []; 16459 break; 16460 case AST.LocalsExpression: 16461 ast.constant = false; 16462 ast.toWatch = []; 16463 break; 16464 } 16465} 16466 16467function getInputs(body) { 16468 if (body.length !== 1) return; 16469 var lastExpression = body[0].expression; 16470 var candidate = lastExpression.toWatch; 16471 if (candidate.length !== 1) return candidate; 16472 return candidate[0] !== lastExpression ? candidate : undefined; 16473} 16474 16475function isAssignable(ast) { 16476 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 16477} 16478 16479function assignableAST(ast) { 16480 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 16481 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 16482 } 16483} 16484 16485function isLiteral(ast) { 16486 return ast.body.length === 0 || 16487 ast.body.length === 1 && ( 16488 ast.body[0].expression.type === AST.Literal || 16489 ast.body[0].expression.type === AST.ArrayExpression || 16490 ast.body[0].expression.type === AST.ObjectExpression); 16491} 16492 16493function isConstant(ast) { 16494 return ast.constant; 16495} 16496 16497function ASTCompiler($filter) { 16498 this.$filter = $filter; 16499} 16500 16501ASTCompiler.prototype = { 16502 compile: function(ast) { 16503 var self = this; 16504 this.state = { 16505 nextId: 0, 16506 filters: {}, 16507 fn: {vars: [], body: [], own: {}}, 16508 assign: {vars: [], body: [], own: {}}, 16509 inputs: [] 16510 }; 16511 findConstantAndWatchExpressions(ast, self.$filter); 16512 var extra = ''; 16513 var assignable; 16514 this.stage = 'assign'; 16515 if ((assignable = assignableAST(ast))) { 16516 this.state.computing = 'assign'; 16517 var result = this.nextId(); 16518 this.recurse(assignable, result); 16519 this.return_(result); 16520 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 16521 } 16522 var toWatch = getInputs(ast.body); 16523 self.stage = 'inputs'; 16524 forEach(toWatch, function(watch, key) { 16525 var fnKey = 'fn' + key; 16526 self.state[fnKey] = {vars: [], body: [], own: {}}; 16527 self.state.computing = fnKey; 16528 var intoId = self.nextId(); 16529 self.recurse(watch, intoId); 16530 self.return_(intoId); 16531 self.state.inputs.push({name: fnKey, isPure: watch.isPure}); 16532 watch.watchId = key; 16533 }); 16534 this.state.computing = 'fn'; 16535 this.stage = 'main'; 16536 this.recurse(ast); 16537 var fnString = 16538 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 16539 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 16540 '"' + this.USE + ' ' + this.STRICT + '";\n' + 16541 this.filterPrefix() + 16542 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 16543 extra + 16544 this.watchFns() + 16545 'return fn;'; 16546 16547 // eslint-disable-next-line no-new-func 16548 var fn = (new Function('$filter', 16549 'getStringValue', 16550 'ifDefined', 16551 'plus', 16552 fnString))( 16553 this.$filter, 16554 getStringValue, 16555 ifDefined, 16556 plusFn); 16557 this.state = this.stage = undefined; 16558 return fn; 16559 }, 16560 16561 USE: 'use', 16562 16563 STRICT: 'strict', 16564 16565 watchFns: function() { 16566 var result = []; 16567 var inputs = this.state.inputs; 16568 var self = this; 16569 forEach(inputs, function(input) { 16570 result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's')); 16571 if (input.isPure) { 16572 result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';'); 16573 } 16574 }); 16575 if (inputs.length) { 16576 result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];'); 16577 } 16578 return result.join(''); 16579 }, 16580 16581 generateFunction: function(name, params) { 16582 return 'function(' + params + '){' + 16583 this.varsPrefix(name) + 16584 this.body(name) + 16585 '};'; 16586 }, 16587 16588 filterPrefix: function() { 16589 var parts = []; 16590 var self = this; 16591 forEach(this.state.filters, function(id, filter) { 16592 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 16593 }); 16594 if (parts.length) return 'var ' + parts.join(',') + ';'; 16595 return ''; 16596 }, 16597 16598 varsPrefix: function(section) { 16599 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 16600 }, 16601 16602 body: function(section) { 16603 return this.state[section].body.join(''); 16604 }, 16605 16606 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 16607 var left, right, self = this, args, expression, computed; 16608 recursionFn = recursionFn || noop; 16609 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 16610 intoId = intoId || this.nextId(); 16611 this.if_('i', 16612 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 16613 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 16614 ); 16615 return; 16616 } 16617 switch (ast.type) { 16618 case AST.Program: 16619 forEach(ast.body, function(expression, pos) { 16620 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 16621 if (pos !== ast.body.length - 1) { 16622 self.current().body.push(right, ';'); 16623 } else { 16624 self.return_(right); 16625 } 16626 }); 16627 break; 16628 case AST.Literal: 16629 expression = this.escape(ast.value); 16630 this.assign(intoId, expression); 16631 recursionFn(intoId || expression); 16632 break; 16633 case AST.UnaryExpression: 16634 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 16635 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 16636 this.assign(intoId, expression); 16637 recursionFn(expression); 16638 break; 16639 case AST.BinaryExpression: 16640 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 16641 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 16642 if (ast.operator === '+') { 16643 expression = this.plus(left, right); 16644 } else if (ast.operator === '-') { 16645 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 16646 } else { 16647 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 16648 } 16649 this.assign(intoId, expression); 16650 recursionFn(expression); 16651 break; 16652 case AST.LogicalExpression: 16653 intoId = intoId || this.nextId(); 16654 self.recurse(ast.left, intoId); 16655 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 16656 recursionFn(intoId); 16657 break; 16658 case AST.ConditionalExpression: 16659 intoId = intoId || this.nextId(); 16660 self.recurse(ast.test, intoId); 16661 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 16662 recursionFn(intoId); 16663 break; 16664 case AST.Identifier: 16665 intoId = intoId || this.nextId(); 16666 if (nameId) { 16667 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 16668 nameId.computed = false; 16669 nameId.name = ast.name; 16670 } 16671 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 16672 function() { 16673 self.if_(self.stage === 'inputs' || 's', function() { 16674 if (create && create !== 1) { 16675 self.if_( 16676 self.isNull(self.nonComputedMember('s', ast.name)), 16677 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 16678 } 16679 self.assign(intoId, self.nonComputedMember('s', ast.name)); 16680 }); 16681 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 16682 ); 16683 recursionFn(intoId); 16684 break; 16685 case AST.MemberExpression: 16686 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 16687 intoId = intoId || this.nextId(); 16688 self.recurse(ast.object, left, undefined, function() { 16689 self.if_(self.notNull(left), function() { 16690 if (ast.computed) { 16691 right = self.nextId(); 16692 self.recurse(ast.property, right); 16693 self.getStringValue(right); 16694 if (create && create !== 1) { 16695 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 16696 } 16697 expression = self.computedMember(left, right); 16698 self.assign(intoId, expression); 16699 if (nameId) { 16700 nameId.computed = true; 16701 nameId.name = right; 16702 } 16703 } else { 16704 if (create && create !== 1) { 16705 self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 16706 } 16707 expression = self.nonComputedMember(left, ast.property.name); 16708 self.assign(intoId, expression); 16709 if (nameId) { 16710 nameId.computed = false; 16711 nameId.name = ast.property.name; 16712 } 16713 } 16714 }, function() { 16715 self.assign(intoId, 'undefined'); 16716 }); 16717 recursionFn(intoId); 16718 }, !!create); 16719 break; 16720 case AST.CallExpression: 16721 intoId = intoId || this.nextId(); 16722 if (ast.filter) { 16723 right = self.filter(ast.callee.name); 16724 args = []; 16725 forEach(ast.arguments, function(expr) { 16726 var argument = self.nextId(); 16727 self.recurse(expr, argument); 16728 args.push(argument); 16729 }); 16730 expression = right + '(' + args.join(',') + ')'; 16731 self.assign(intoId, expression); 16732 recursionFn(intoId); 16733 } else { 16734 right = self.nextId(); 16735 left = {}; 16736 args = []; 16737 self.recurse(ast.callee, right, left, function() { 16738 self.if_(self.notNull(right), function() { 16739 forEach(ast.arguments, function(expr) { 16740 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { 16741 args.push(argument); 16742 }); 16743 }); 16744 if (left.name) { 16745 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 16746 } else { 16747 expression = right + '(' + args.join(',') + ')'; 16748 } 16749 self.assign(intoId, expression); 16750 }, function() { 16751 self.assign(intoId, 'undefined'); 16752 }); 16753 recursionFn(intoId); 16754 }); 16755 } 16756 break; 16757 case AST.AssignmentExpression: 16758 right = this.nextId(); 16759 left = {}; 16760 this.recurse(ast.left, undefined, left, function() { 16761 self.if_(self.notNull(left.context), function() { 16762 self.recurse(ast.right, right); 16763 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 16764 self.assign(intoId, expression); 16765 recursionFn(intoId || expression); 16766 }); 16767 }, 1); 16768 break; 16769 case AST.ArrayExpression: 16770 args = []; 16771 forEach(ast.elements, function(expr) { 16772 self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { 16773 args.push(argument); 16774 }); 16775 }); 16776 expression = '[' + args.join(',') + ']'; 16777 this.assign(intoId, expression); 16778 recursionFn(intoId || expression); 16779 break; 16780 case AST.ObjectExpression: 16781 args = []; 16782 computed = false; 16783 forEach(ast.properties, function(property) { 16784 if (property.computed) { 16785 computed = true; 16786 } 16787 }); 16788 if (computed) { 16789 intoId = intoId || this.nextId(); 16790 this.assign(intoId, '{}'); 16791 forEach(ast.properties, function(property) { 16792 if (property.computed) { 16793 left = self.nextId(); 16794 self.recurse(property.key, left); 16795 } else { 16796 left = property.key.type === AST.Identifier ? 16797 property.key.name : 16798 ('' + property.key.value); 16799 } 16800 right = self.nextId(); 16801 self.recurse(property.value, right); 16802 self.assign(self.member(intoId, left, property.computed), right); 16803 }); 16804 } else { 16805 forEach(ast.properties, function(property) { 16806 self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) { 16807 args.push(self.escape( 16808 property.key.type === AST.Identifier ? property.key.name : 16809 ('' + property.key.value)) + 16810 ':' + expr); 16811 }); 16812 }); 16813 expression = '{' + args.join(',') + '}'; 16814 this.assign(intoId, expression); 16815 } 16816 recursionFn(intoId || expression); 16817 break; 16818 case AST.ThisExpression: 16819 this.assign(intoId, 's'); 16820 recursionFn(intoId || 's'); 16821 break; 16822 case AST.LocalsExpression: 16823 this.assign(intoId, 'l'); 16824 recursionFn(intoId || 'l'); 16825 break; 16826 case AST.NGValueParameter: 16827 this.assign(intoId, 'v'); 16828 recursionFn(intoId || 'v'); 16829 break; 16830 } 16831 }, 16832 16833 getHasOwnProperty: function(element, property) { 16834 var key = element + '.' + property; 16835 var own = this.current().own; 16836 if (!own.hasOwnProperty(key)) { 16837 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 16838 } 16839 return own[key]; 16840 }, 16841 16842 assign: function(id, value) { 16843 if (!id) return; 16844 this.current().body.push(id, '=', value, ';'); 16845 return id; 16846 }, 16847 16848 filter: function(filterName) { 16849 if (!this.state.filters.hasOwnProperty(filterName)) { 16850 this.state.filters[filterName] = this.nextId(true); 16851 } 16852 return this.state.filters[filterName]; 16853 }, 16854 16855 ifDefined: function(id, defaultValue) { 16856 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 16857 }, 16858 16859 plus: function(left, right) { 16860 return 'plus(' + left + ',' + right + ')'; 16861 }, 16862 16863 return_: function(id) { 16864 this.current().body.push('return ', id, ';'); 16865 }, 16866 16867 if_: function(test, alternate, consequent) { 16868 if (test === true) { 16869 alternate(); 16870 } else { 16871 var body = this.current().body; 16872 body.push('if(', test, '){'); 16873 alternate(); 16874 body.push('}'); 16875 if (consequent) { 16876 body.push('else{'); 16877 consequent(); 16878 body.push('}'); 16879 } 16880 } 16881 }, 16882 16883 not: function(expression) { 16884 return '!(' + expression + ')'; 16885 }, 16886 16887 isNull: function(expression) { 16888 return expression + '==null'; 16889 }, 16890 16891 notNull: function(expression) { 16892 return expression + '!=null'; 16893 }, 16894 16895 nonComputedMember: function(left, right) { 16896 var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/; 16897 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; 16898 if (SAFE_IDENTIFIER.test(right)) { 16899 return left + '.' + right; 16900 } else { 16901 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; 16902 } 16903 }, 16904 16905 computedMember: function(left, right) { 16906 return left + '[' + right + ']'; 16907 }, 16908 16909 member: function(left, right, computed) { 16910 if (computed) return this.computedMember(left, right); 16911 return this.nonComputedMember(left, right); 16912 }, 16913 16914 getStringValue: function(item) { 16915 this.assign(item, 'getStringValue(' + item + ')'); 16916 }, 16917 16918 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 16919 var self = this; 16920 return function() { 16921 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 16922 }; 16923 }, 16924 16925 lazyAssign: function(id, value) { 16926 var self = this; 16927 return function() { 16928 self.assign(id, value); 16929 }; 16930 }, 16931 16932 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 16933 16934 stringEscapeFn: function(c) { 16935 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 16936 }, 16937 16938 escape: function(value) { 16939 if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\''; 16940 if (isNumber(value)) return value.toString(); 16941 if (value === true) return 'true'; 16942 if (value === false) return 'false'; 16943 if (value === null) return 'null'; 16944 if (typeof value === 'undefined') return 'undefined'; 16945 16946 throw $parseMinErr('esc', 'IMPOSSIBLE'); 16947 }, 16948 16949 nextId: function(skip, init) { 16950 var id = 'v' + (this.state.nextId++); 16951 if (!skip) { 16952 this.current().vars.push(id + (init ? '=' + init : '')); 16953 } 16954 return id; 16955 }, 16956 16957 current: function() { 16958 return this.state[this.state.computing]; 16959 } 16960}; 16961 16962 16963function ASTInterpreter($filter) { 16964 this.$filter = $filter; 16965} 16966 16967ASTInterpreter.prototype = { 16968 compile: function(ast) { 16969 var self = this; 16970 findConstantAndWatchExpressions(ast, self.$filter); 16971 var assignable; 16972 var assign; 16973 if ((assignable = assignableAST(ast))) { 16974 assign = this.recurse(assignable); 16975 } 16976 var toWatch = getInputs(ast.body); 16977 var inputs; 16978 if (toWatch) { 16979 inputs = []; 16980 forEach(toWatch, function(watch, key) { 16981 var input = self.recurse(watch); 16982 input.isPure = watch.isPure; 16983 watch.input = input; 16984 inputs.push(input); 16985 watch.watchId = key; 16986 }); 16987 } 16988 var expressions = []; 16989 forEach(ast.body, function(expression) { 16990 expressions.push(self.recurse(expression.expression)); 16991 }); 16992 var fn = ast.body.length === 0 ? noop : 16993 ast.body.length === 1 ? expressions[0] : 16994 function(scope, locals) { 16995 var lastValue; 16996 forEach(expressions, function(exp) { 16997 lastValue = exp(scope, locals); 16998 }); 16999 return lastValue; 17000 }; 17001 if (assign) { 17002 fn.assign = function(scope, value, locals) { 17003 return assign(scope, locals, value); 17004 }; 17005 } 17006 if (inputs) { 17007 fn.inputs = inputs; 17008 } 17009 return fn; 17010 }, 17011 17012 recurse: function(ast, context, create) { 17013 var left, right, self = this, args; 17014 if (ast.input) { 17015 return this.inputs(ast.input, ast.watchId); 17016 } 17017 switch (ast.type) { 17018 case AST.Literal: 17019 return this.value(ast.value, context); 17020 case AST.UnaryExpression: 17021 right = this.recurse(ast.argument); 17022 return this['unary' + ast.operator](right, context); 17023 case AST.BinaryExpression: 17024 left = this.recurse(ast.left); 17025 right = this.recurse(ast.right); 17026 return this['binary' + ast.operator](left, right, context); 17027 case AST.LogicalExpression: 17028 left = this.recurse(ast.left); 17029 right = this.recurse(ast.right); 17030 return this['binary' + ast.operator](left, right, context); 17031 case AST.ConditionalExpression: 17032 return this['ternary?:']( 17033 this.recurse(ast.test), 17034 this.recurse(ast.alternate), 17035 this.recurse(ast.consequent), 17036 context 17037 ); 17038 case AST.Identifier: 17039 return self.identifier(ast.name, context, create); 17040 case AST.MemberExpression: 17041 left = this.recurse(ast.object, false, !!create); 17042 if (!ast.computed) { 17043 right = ast.property.name; 17044 } 17045 if (ast.computed) right = this.recurse(ast.property); 17046 return ast.computed ? 17047 this.computedMember(left, right, context, create) : 17048 this.nonComputedMember(left, right, context, create); 17049 case AST.CallExpression: 17050 args = []; 17051 forEach(ast.arguments, function(expr) { 17052 args.push(self.recurse(expr)); 17053 }); 17054 if (ast.filter) right = this.$filter(ast.callee.name); 17055 if (!ast.filter) right = this.recurse(ast.callee, true); 17056 return ast.filter ? 17057 function(scope, locals, assign, inputs) { 17058 var values = []; 17059 for (var i = 0; i < args.length; ++i) { 17060 values.push(args[i](scope, locals, assign, inputs)); 17061 } 17062 var value = right.apply(undefined, values, inputs); 17063 return context ? {context: undefined, name: undefined, value: value} : value; 17064 } : 17065 function(scope, locals, assign, inputs) { 17066 var rhs = right(scope, locals, assign, inputs); 17067 var value; 17068 if (rhs.value != null) { 17069 var values = []; 17070 for (var i = 0; i < args.length; ++i) { 17071 values.push(args[i](scope, locals, assign, inputs)); 17072 } 17073 value = rhs.value.apply(rhs.context, values); 17074 } 17075 return context ? {value: value} : value; 17076 }; 17077 case AST.AssignmentExpression: 17078 left = this.recurse(ast.left, true, 1); 17079 right = this.recurse(ast.right); 17080 return function(scope, locals, assign, inputs) { 17081 var lhs = left(scope, locals, assign, inputs); 17082 var rhs = right(scope, locals, assign, inputs); 17083 lhs.context[lhs.name] = rhs; 17084 return context ? {value: rhs} : rhs; 17085 }; 17086 case AST.ArrayExpression: 17087 args = []; 17088 forEach(ast.elements, function(expr) { 17089 args.push(self.recurse(expr)); 17090 }); 17091 return function(scope, locals, assign, inputs) { 17092 var value = []; 17093 for (var i = 0; i < args.length; ++i) { 17094 value.push(args[i](scope, locals, assign, inputs)); 17095 } 17096 return context ? {value: value} : value; 17097 }; 17098 case AST.ObjectExpression: 17099 args = []; 17100 forEach(ast.properties, function(property) { 17101 if (property.computed) { 17102 args.push({key: self.recurse(property.key), 17103 computed: true, 17104 value: self.recurse(property.value) 17105 }); 17106 } else { 17107 args.push({key: property.key.type === AST.Identifier ? 17108 property.key.name : 17109 ('' + property.key.value), 17110 computed: false, 17111 value: self.recurse(property.value) 17112 }); 17113 } 17114 }); 17115 return function(scope, locals, assign, inputs) { 17116 var value = {}; 17117 for (var i = 0; i < args.length; ++i) { 17118 if (args[i].computed) { 17119 value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs); 17120 } else { 17121 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 17122 } 17123 } 17124 return context ? {value: value} : value; 17125 }; 17126 case AST.ThisExpression: 17127 return function(scope) { 17128 return context ? {value: scope} : scope; 17129 }; 17130 case AST.LocalsExpression: 17131 return function(scope, locals) { 17132 return context ? {value: locals} : locals; 17133 }; 17134 case AST.NGValueParameter: 17135 return function(scope, locals, assign) { 17136 return context ? {value: assign} : assign; 17137 }; 17138 } 17139 }, 17140 17141 'unary+': function(argument, context) { 17142 return function(scope, locals, assign, inputs) { 17143 var arg = argument(scope, locals, assign, inputs); 17144 if (isDefined(arg)) { 17145 arg = +arg; 17146 } else { 17147 arg = 0; 17148 } 17149 return context ? {value: arg} : arg; 17150 }; 17151 }, 17152 'unary-': function(argument, context) { 17153 return function(scope, locals, assign, inputs) { 17154 var arg = argument(scope, locals, assign, inputs); 17155 if (isDefined(arg)) { 17156 arg = -arg; 17157 } else { 17158 arg = -0; 17159 } 17160 return context ? {value: arg} : arg; 17161 }; 17162 }, 17163 'unary!': function(argument, context) { 17164 return function(scope, locals, assign, inputs) { 17165 var arg = !argument(scope, locals, assign, inputs); 17166 return context ? {value: arg} : arg; 17167 }; 17168 }, 17169 'binary+': function(left, right, context) { 17170 return function(scope, locals, assign, inputs) { 17171 var lhs = left(scope, locals, assign, inputs); 17172 var rhs = right(scope, locals, assign, inputs); 17173 var arg = plusFn(lhs, rhs); 17174 return context ? {value: arg} : arg; 17175 }; 17176 }, 17177 'binary-': function(left, right, context) { 17178 return function(scope, locals, assign, inputs) { 17179 var lhs = left(scope, locals, assign, inputs); 17180 var rhs = right(scope, locals, assign, inputs); 17181 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 17182 return context ? {value: arg} : arg; 17183 }; 17184 }, 17185 'binary*': function(left, right, context) { 17186 return function(scope, locals, assign, inputs) { 17187 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 17188 return context ? {value: arg} : arg; 17189 }; 17190 }, 17191 'binary/': function(left, right, context) { 17192 return function(scope, locals, assign, inputs) { 17193 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 17194 return context ? {value: arg} : arg; 17195 }; 17196 }, 17197 'binary%': function(left, right, context) { 17198 return function(scope, locals, assign, inputs) { 17199 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 17200 return context ? {value: arg} : arg; 17201 }; 17202 }, 17203 'binary===': function(left, right, context) { 17204 return function(scope, locals, assign, inputs) { 17205 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 17206 return context ? {value: arg} : arg; 17207 }; 17208 }, 17209 'binary!==': function(left, right, context) { 17210 return function(scope, locals, assign, inputs) { 17211 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 17212 return context ? {value: arg} : arg; 17213 }; 17214 }, 17215 'binary==': function(left, right, context) { 17216 return function(scope, locals, assign, inputs) { 17217 // eslint-disable-next-line eqeqeq 17218 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 17219 return context ? {value: arg} : arg; 17220 }; 17221 }, 17222 'binary!=': function(left, right, context) { 17223 return function(scope, locals, assign, inputs) { 17224 // eslint-disable-next-line eqeqeq 17225 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 17226 return context ? {value: arg} : arg; 17227 }; 17228 }, 17229 'binary<': function(left, right, context) { 17230 return function(scope, locals, assign, inputs) { 17231 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 17232 return context ? {value: arg} : arg; 17233 }; 17234 }, 17235 'binary>': function(left, right, context) { 17236 return function(scope, locals, assign, inputs) { 17237 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 17238 return context ? {value: arg} : arg; 17239 }; 17240 }, 17241 'binary<=': function(left, right, context) { 17242 return function(scope, locals, assign, inputs) { 17243 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 17244 return context ? {value: arg} : arg; 17245 }; 17246 }, 17247 'binary>=': function(left, right, context) { 17248 return function(scope, locals, assign, inputs) { 17249 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 17250 return context ? {value: arg} : arg; 17251 }; 17252 }, 17253 'binary&&': function(left, right, context) { 17254 return function(scope, locals, assign, inputs) { 17255 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 17256 return context ? {value: arg} : arg; 17257 }; 17258 }, 17259 'binary||': function(left, right, context) { 17260 return function(scope, locals, assign, inputs) { 17261 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 17262 return context ? {value: arg} : arg; 17263 }; 17264 }, 17265 'ternary?:': function(test, alternate, consequent, context) { 17266 return function(scope, locals, assign, inputs) { 17267 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 17268 return context ? {value: arg} : arg; 17269 }; 17270 }, 17271 value: function(value, context) { 17272 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 17273 }, 17274 identifier: function(name, context, create) { 17275 return function(scope, locals, assign, inputs) { 17276 var base = locals && (name in locals) ? locals : scope; 17277 if (create && create !== 1 && base && base[name] == null) { 17278 base[name] = {}; 17279 } 17280 var value = base ? base[name] : undefined; 17281 if (context) { 17282 return {context: base, name: name, value: value}; 17283 } else { 17284 return value; 17285 } 17286 }; 17287 }, 17288 computedMember: function(left, right, context, create) { 17289 return function(scope, locals, assign, inputs) { 17290 var lhs = left(scope, locals, assign, inputs); 17291 var rhs; 17292 var value; 17293 if (lhs != null) { 17294 rhs = right(scope, locals, assign, inputs); 17295 rhs = getStringValue(rhs); 17296 if (create && create !== 1) { 17297 if (lhs && !(lhs[rhs])) { 17298 lhs[rhs] = {}; 17299 } 17300 } 17301 value = lhs[rhs]; 17302 } 17303 if (context) { 17304 return {context: lhs, name: rhs, value: value}; 17305 } else { 17306 return value; 17307 } 17308 }; 17309 }, 17310 nonComputedMember: function(left, right, context, create) { 17311 return function(scope, locals, assign, inputs) { 17312 var lhs = left(scope, locals, assign, inputs); 17313 if (create && create !== 1) { 17314 if (lhs && lhs[right] == null) { 17315 lhs[right] = {}; 17316 } 17317 } 17318 var value = lhs != null ? lhs[right] : undefined; 17319 if (context) { 17320 return {context: lhs, name: right, value: value}; 17321 } else { 17322 return value; 17323 } 17324 }; 17325 }, 17326 inputs: function(input, watchId) { 17327 return function(scope, value, locals, inputs) { 17328 if (inputs) return inputs[watchId]; 17329 return input(scope, value, locals); 17330 }; 17331 } 17332}; 17333 17334/** 17335 * @constructor 17336 */ 17337function Parser(lexer, $filter, options) { 17338 this.ast = new AST(lexer, options); 17339 this.astCompiler = options.csp ? new ASTInterpreter($filter) : 17340 new ASTCompiler($filter); 17341} 17342 17343Parser.prototype = { 17344 constructor: Parser, 17345 17346 parse: function(text) { 17347 var ast = this.getAst(text); 17348 var fn = this.astCompiler.compile(ast.ast); 17349 fn.literal = isLiteral(ast.ast); 17350 fn.constant = isConstant(ast.ast); 17351 fn.oneTime = ast.oneTime; 17352 return fn; 17353 }, 17354 17355 getAst: function(exp) { 17356 var oneTime = false; 17357 exp = exp.trim(); 17358 17359 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 17360 oneTime = true; 17361 exp = exp.substring(2); 17362 } 17363 return { 17364 ast: this.ast.ast(exp), 17365 oneTime: oneTime 17366 }; 17367 } 17368}; 17369 17370function getValueOf(value) { 17371 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 17372} 17373 17374/////////////////////////////////// 17375 17376/** 17377 * @ngdoc service 17378 * @name $parse 17379 * @kind function 17380 * 17381 * @description 17382 * 17383 * Converts AngularJS {@link guide/expression expression} into a function. 17384 * 17385 * ```js 17386 * var getter = $parse('user.name'); 17387 * var setter = getter.assign; 17388 * var context = {user:{name:'AngularJS'}}; 17389 * var locals = {user:{name:'local'}}; 17390 * 17391 * expect(getter(context)).toEqual('AngularJS'); 17392 * setter(context, 'newValue'); 17393 * expect(context.user.name).toEqual('newValue'); 17394 * expect(getter(context, locals)).toEqual('local'); 17395 * ``` 17396 * 17397 * 17398 * @param {string} expression String expression to compile. 17399 * @returns {function(context, locals)} a function which represents the compiled expression: 17400 * 17401 * * `context` – `{object}` – an object against which any expressions embedded in the strings 17402 * are evaluated against (typically a scope object). 17403 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 17404 * `context`. 17405 * 17406 * The returned function also has the following properties: 17407 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript 17408 * literal. 17409 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript 17410 * constant literals. 17411 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be 17412 * set to a function to change its value on the given context. 17413 * 17414 */ 17415 17416 17417/** 17418 * @ngdoc provider 17419 * @name $parseProvider 17420 * @this 17421 * 17422 * @description 17423 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 17424 * service. 17425 */ 17426function $ParseProvider() { 17427 var cache = createMap(); 17428 var literals = { 17429 'true': true, 17430 'false': false, 17431 'null': null, 17432 'undefined': undefined 17433 }; 17434 var identStart, identContinue; 17435 17436 /** 17437 * @ngdoc method 17438 * @name $parseProvider#addLiteral 17439 * @description 17440 * 17441 * Configure $parse service to add literal values that will be present as literal at expressions. 17442 * 17443 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. 17444 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. 17445 * 17446 **/ 17447 this.addLiteral = function(literalName, literalValue) { 17448 literals[literalName] = literalValue; 17449 }; 17450 17451 /** 17452 * @ngdoc method 17453 * @name $parseProvider#setIdentifierFns 17454 * 17455 * @description 17456 * 17457 * Allows defining the set of characters that are allowed in AngularJS expressions. The function 17458 * `identifierStart` will get called to know if a given character is a valid character to be the 17459 * first character for an identifier. The function `identifierContinue` will get called to know if 17460 * a given character is a valid character to be a follow-up identifier character. The functions 17461 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be 17462 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in 17463 * mind that the `string` parameter can be two characters long depending on the character 17464 * representation. It is expected for the function to return `true` or `false`, whether that 17465 * character is allowed or not. 17466 * 17467 * Since this function will be called extensively, keep the implementation of these functions fast, 17468 * as the performance of these functions have a direct impact on the expressions parsing speed. 17469 * 17470 * @param {function=} identifierStart The function that will decide whether the given character is 17471 * a valid identifier start character. 17472 * @param {function=} identifierContinue The function that will decide whether the given character is 17473 * a valid identifier continue character. 17474 */ 17475 this.setIdentifierFns = function(identifierStart, identifierContinue) { 17476 identStart = identifierStart; 17477 identContinue = identifierContinue; 17478 return this; 17479 }; 17480 17481 this.$get = ['$filter', function($filter) { 17482 var noUnsafeEval = csp().noUnsafeEval; 17483 var $parseOptions = { 17484 csp: noUnsafeEval, 17485 literals: copy(literals), 17486 isIdentifierStart: isFunction(identStart) && identStart, 17487 isIdentifierContinue: isFunction(identContinue) && identContinue 17488 }; 17489 $parse.$$getAst = $$getAst; 17490 return $parse; 17491 17492 function $parse(exp, interceptorFn) { 17493 var parsedExpression, cacheKey; 17494 17495 switch (typeof exp) { 17496 case 'string': 17497 exp = exp.trim(); 17498 cacheKey = exp; 17499 17500 parsedExpression = cache[cacheKey]; 17501 17502 if (!parsedExpression) { 17503 var lexer = new Lexer($parseOptions); 17504 var parser = new Parser(lexer, $filter, $parseOptions); 17505 parsedExpression = parser.parse(exp); 17506 17507 cache[cacheKey] = addWatchDelegate(parsedExpression); 17508 } 17509 return addInterceptor(parsedExpression, interceptorFn); 17510 17511 case 'function': 17512 return addInterceptor(exp, interceptorFn); 17513 17514 default: 17515 return addInterceptor(noop, interceptorFn); 17516 } 17517 } 17518 17519 function $$getAst(exp) { 17520 var lexer = new Lexer($parseOptions); 17521 var parser = new Parser(lexer, $filter, $parseOptions); 17522 return parser.getAst(exp).ast; 17523 } 17524 17525 function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) { 17526 17527 if (newValue == null || oldValueOfValue == null) { // null/undefined 17528 return newValue === oldValueOfValue; 17529 } 17530 17531 if (typeof newValue === 'object') { 17532 17533 // attempt to convert the value to a primitive type 17534 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 17535 // be cheaply dirty-checked 17536 newValue = getValueOf(newValue); 17537 17538 if (typeof newValue === 'object' && !compareObjectIdentity) { 17539 // objects/arrays are not supported - deep-watching them would be too expensive 17540 return false; 17541 } 17542 17543 // fall-through to the primitive equality check 17544 } 17545 17546 //Primitive or NaN 17547 // eslint-disable-next-line no-self-compare 17548 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 17549 } 17550 17551 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 17552 var inputExpressions = parsedExpression.inputs; 17553 var lastResult; 17554 17555 if (inputExpressions.length === 1) { 17556 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 17557 inputExpressions = inputExpressions[0]; 17558 return scope.$watch(function expressionInputWatch(scope) { 17559 var newInputValue = inputExpressions(scope); 17560 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) { 17561 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 17562 oldInputValueOf = newInputValue && getValueOf(newInputValue); 17563 } 17564 return lastResult; 17565 }, listener, objectEquality, prettyPrintExpression); 17566 } 17567 17568 var oldInputValueOfValues = []; 17569 var oldInputValues = []; 17570 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 17571 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 17572 oldInputValues[i] = null; 17573 } 17574 17575 return scope.$watch(function expressionInputsWatch(scope) { 17576 var changed = false; 17577 17578 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 17579 var newInputValue = inputExpressions[i](scope); 17580 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) { 17581 oldInputValues[i] = newInputValue; 17582 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 17583 } 17584 } 17585 17586 if (changed) { 17587 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 17588 } 17589 17590 return lastResult; 17591 }, listener, objectEquality, prettyPrintExpression); 17592 } 17593 17594 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 17595 var isDone = parsedExpression.literal ? isAllDefined : isDefined; 17596 var unwatch, lastValue; 17597 17598 var exp = parsedExpression.$$intercepted || parsedExpression; 17599 var post = parsedExpression.$$interceptor || identity; 17600 17601 var useInputs = parsedExpression.inputs && !exp.inputs; 17602 17603 // Propagate the literal/inputs/constant attributes 17604 // ... but not oneTime since we are handling it 17605 oneTimeWatch.literal = parsedExpression.literal; 17606 oneTimeWatch.constant = parsedExpression.constant; 17607 oneTimeWatch.inputs = parsedExpression.inputs; 17608 17609 // Allow other delegates to run on this wrapped expression 17610 addWatchDelegate(oneTimeWatch); 17611 17612 unwatch = scope.$watch(oneTimeWatch, listener, objectEquality, prettyPrintExpression); 17613 17614 return unwatch; 17615 17616 function unwatchIfDone() { 17617 if (isDone(lastValue)) { 17618 unwatch(); 17619 } 17620 } 17621 17622 function oneTimeWatch(scope, locals, assign, inputs) { 17623 lastValue = useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs); 17624 if (isDone(lastValue)) { 17625 scope.$$postDigest(unwatchIfDone); 17626 } 17627 return post(lastValue); 17628 } 17629 } 17630 17631 function isAllDefined(value) { 17632 var allDefined = true; 17633 forEach(value, function(val) { 17634 if (!isDefined(val)) allDefined = false; 17635 }); 17636 return allDefined; 17637 } 17638 17639 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 17640 var unwatch = scope.$watch(function constantWatch(scope) { 17641 unwatch(); 17642 return parsedExpression(scope); 17643 }, listener, objectEquality); 17644 return unwatch; 17645 } 17646 17647 function addWatchDelegate(parsedExpression) { 17648 if (parsedExpression.constant) { 17649 parsedExpression.$$watchDelegate = constantWatchDelegate; 17650 } else if (parsedExpression.oneTime) { 17651 parsedExpression.$$watchDelegate = oneTimeWatchDelegate; 17652 } else if (parsedExpression.inputs) { 17653 parsedExpression.$$watchDelegate = inputsWatchDelegate; 17654 } 17655 17656 return parsedExpression; 17657 } 17658 17659 function chainInterceptors(first, second) { 17660 function chainedInterceptor(value) { 17661 return second(first(value)); 17662 } 17663 chainedInterceptor.$stateful = first.$stateful || second.$stateful; 17664 chainedInterceptor.$$pure = first.$$pure && second.$$pure; 17665 17666 return chainedInterceptor; 17667 } 17668 17669 function addInterceptor(parsedExpression, interceptorFn) { 17670 if (!interceptorFn) return parsedExpression; 17671 17672 // Extract any existing interceptors out of the parsedExpression 17673 // to ensure the original parsedExpression is always the $$intercepted 17674 if (parsedExpression.$$interceptor) { 17675 interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn); 17676 parsedExpression = parsedExpression.$$intercepted; 17677 } 17678 17679 var useInputs = false; 17680 17681 var fn = function interceptedExpression(scope, locals, assign, inputs) { 17682 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 17683 return interceptorFn(value); 17684 }; 17685 17686 // Maintain references to the interceptor/intercepted 17687 fn.$$intercepted = parsedExpression; 17688 fn.$$interceptor = interceptorFn; 17689 17690 // Propagate the literal/oneTime/constant attributes 17691 fn.literal = parsedExpression.literal; 17692 fn.oneTime = parsedExpression.oneTime; 17693 fn.constant = parsedExpression.constant; 17694 17695 // Treat the interceptor like filters. 17696 // If it is not $stateful then only watch its inputs. 17697 // If the expression itself has no inputs then use the full expression as an input. 17698 if (!interceptorFn.$stateful) { 17699 useInputs = !parsedExpression.inputs; 17700 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 17701 17702 if (!interceptorFn.$$pure) { 17703 fn.inputs = fn.inputs.map(function(e) { 17704 // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a 17705 // non-pure interceptor function. 17706 if (e.isPure === PURITY_RELATIVE) { 17707 return function depurifier(s) { return e(s); }; 17708 } 17709 return e; 17710 }); 17711 } 17712 } 17713 17714 return addWatchDelegate(fn); 17715 } 17716 }]; 17717} 17718 17719/** 17720 * @ngdoc service 17721 * @name $q 17722 * @requires $rootScope 17723 * 17724 * @description 17725 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 17726 * when they are done processing. 17727 * 17728 * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred 17729 * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 17730 * 17731 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 17732 * implementations, and the other which resembles ES6 (ES2015) promises to some degree. 17733 * 17734 * ## $q constructor 17735 * 17736 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 17737 * function as the first argument. This is similar to the native Promise implementation from ES6, 17738 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 17739 * 17740 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are 17741 * available yet. 17742 * 17743 * It can be used like so: 17744 * 17745 * ```js 17746 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 17747 * // are available in the current lexical scope (they could have been injected or passed in). 17748 * 17749 * function asyncGreet(name) { 17750 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 17751 * return $q(function(resolve, reject) { 17752 * setTimeout(function() { 17753 * if (okToGreet(name)) { 17754 * resolve('Hello, ' + name + '!'); 17755 * } else { 17756 * reject('Greeting ' + name + ' is not allowed.'); 17757 * } 17758 * }, 1000); 17759 * }); 17760 * } 17761 * 17762 * var promise = asyncGreet('Robin Hood'); 17763 * promise.then(function(greeting) { 17764 * alert('Success: ' + greeting); 17765 * }, function(reason) { 17766 * alert('Failed: ' + reason); 17767 * }); 17768 * ``` 17769 * 17770 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 17771 * 17772 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 17773 * 17774 * However, the more traditional CommonJS-style usage is still available, and documented below. 17775 * 17776 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 17777 * interface for interacting with an object that represents the result of an action that is 17778 * performed asynchronously, and may or may not be finished at any given point in time. 17779 * 17780 * From the perspective of dealing with error handling, deferred and promise APIs are to 17781 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 17782 * 17783 * ```js 17784 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 17785 * // are available in the current lexical scope (they could have been injected or passed in). 17786 * 17787 * function asyncGreet(name) { 17788 * var deferred = $q.defer(); 17789 * 17790 * setTimeout(function() { 17791 * deferred.notify('About to greet ' + name + '.'); 17792 * 17793 * if (okToGreet(name)) { 17794 * deferred.resolve('Hello, ' + name + '!'); 17795 * } else { 17796 * deferred.reject('Greeting ' + name + ' is not allowed.'); 17797 * } 17798 * }, 1000); 17799 * 17800 * return deferred.promise; 17801 * } 17802 * 17803 * var promise = asyncGreet('Robin Hood'); 17804 * promise.then(function(greeting) { 17805 * alert('Success: ' + greeting); 17806 * }, function(reason) { 17807 * alert('Failed: ' + reason); 17808 * }, function(update) { 17809 * alert('Got notification: ' + update); 17810 * }); 17811 * ``` 17812 * 17813 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 17814 * comes in the way of guarantees that promise and deferred APIs make, see 17815 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 17816 * 17817 * Additionally the promise api allows for composition that is very hard to do with the 17818 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 17819 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 17820 * section on serial or parallel joining of promises. 17821 * 17822 * ## The Deferred API 17823 * 17824 * A new instance of deferred is constructed by calling `$q.defer()`. 17825 * 17826 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 17827 * that can be used for signaling the successful or unsuccessful completion, as well as the status 17828 * of the task. 17829 * 17830 * **Methods** 17831 * 17832 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection 17833 * constructed via `$q.reject`, the promise will be rejected instead. 17834 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to 17835 * resolving it with a rejection constructed via `$q.reject`. 17836 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 17837 * multiple times before the promise is either resolved or rejected. 17838 * 17839 * **Properties** 17840 * 17841 * - promise – `{Promise}` – promise object associated with this deferred. 17842 * 17843 * 17844 * ## The Promise API 17845 * 17846 * A new promise instance is created when a deferred instance is created and can be retrieved by 17847 * calling `deferred.promise`. 17848 * 17849 * The purpose of the promise object is to allow for interested parties to get access to the result 17850 * of the deferred task when it completes. 17851 * 17852 * **Methods** 17853 * 17854 * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or 17855 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 17856 * as soon as the result is available. The callbacks are called with a single argument: the result 17857 * or rejection reason. Additionally, the notify callback may be called zero or more times to 17858 * provide a progress indication, before the promise is resolved or rejected. 17859 * 17860 * This method *returns a new promise* which is resolved or rejected via the return value of the 17861 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved 17862 * with the value which is resolved in that promise using 17863 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 17864 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 17865 * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback 17866 * arguments are optional. 17867 * 17868 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` 17869 * 17870 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, 17871 * but to do so without modifying the final value. This is useful to release resources or do some 17872 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 17873 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 17874 * more information. 17875 * 17876 * ## Chaining promises 17877 * 17878 * Because calling the `then` method of a promise returns a new derived promise, it is easily 17879 * possible to create a chain of promises: 17880 * 17881 * ```js 17882 * promiseB = promiseA.then(function(result) { 17883 * return result + 1; 17884 * }); 17885 * 17886 * // promiseB will be resolved immediately after promiseA is resolved and its value 17887 * // will be the result of promiseA incremented by 1 17888 * ``` 17889 * 17890 * It is possible to create chains of any length and since a promise can be resolved with another 17891 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 17892 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 17893 * $http's response interceptors. 17894 * 17895 * 17896 * ## Differences between Kris Kowal's Q and $q 17897 * 17898 * There are two main differences: 17899 * 17900 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 17901 * mechanism in AngularJS, which means faster propagation of resolution or rejection into your 17902 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 17903 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 17904 * all the important functionality needed for common async tasks. 17905 * 17906 * ## Testing 17907 * 17908 * ```js 17909 * it('should simulate promise', inject(function($q, $rootScope) { 17910 * var deferred = $q.defer(); 17911 * var promise = deferred.promise; 17912 * var resolvedValue; 17913 * 17914 * promise.then(function(value) { resolvedValue = value; }); 17915 * expect(resolvedValue).toBeUndefined(); 17916 * 17917 * // Simulate resolving of promise 17918 * deferred.resolve(123); 17919 * // Note that the 'then' function does not get called synchronously. 17920 * // This is because we want the promise API to always be async, whether or not 17921 * // it got called synchronously or asynchronously. 17922 * expect(resolvedValue).toBeUndefined(); 17923 * 17924 * // Propagate promise resolution to 'then' functions using $apply(). 17925 * $rootScope.$apply(); 17926 * expect(resolvedValue).toEqual(123); 17927 * })); 17928 * ``` 17929 * 17930 * @param {function(function, function)} resolver Function which is responsible for resolving or 17931 * rejecting the newly created promise. The first parameter is a function which resolves the 17932 * promise, the second parameter is a function which rejects the promise. 17933 * 17934 * @returns {Promise} The newly created promise. 17935 */ 17936/** 17937 * @ngdoc provider 17938 * @name $qProvider 17939 * @this 17940 * 17941 * @description 17942 */ 17943function $QProvider() { 17944 var errorOnUnhandledRejections = true; 17945 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 17946 return qFactory(function(callback) { 17947 $rootScope.$evalAsync(callback); 17948 }, $exceptionHandler, errorOnUnhandledRejections); 17949 }]; 17950 17951 /** 17952 * @ngdoc method 17953 * @name $qProvider#errorOnUnhandledRejections 17954 * @kind function 17955 * 17956 * @description 17957 * Retrieves or overrides whether to generate an error when a rejected promise is not handled. 17958 * This feature is enabled by default. 17959 * 17960 * @param {boolean=} value Whether to generate an error when a rejected promise is not handled. 17961 * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for 17962 * chaining otherwise. 17963 */ 17964 this.errorOnUnhandledRejections = function(value) { 17965 if (isDefined(value)) { 17966 errorOnUnhandledRejections = value; 17967 return this; 17968 } else { 17969 return errorOnUnhandledRejections; 17970 } 17971 }; 17972} 17973 17974/** @this */ 17975function $$QProvider() { 17976 var errorOnUnhandledRejections = true; 17977 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 17978 return qFactory(function(callback) { 17979 $browser.defer(callback); 17980 }, $exceptionHandler, errorOnUnhandledRejections); 17981 }]; 17982 17983 this.errorOnUnhandledRejections = function(value) { 17984 if (isDefined(value)) { 17985 errorOnUnhandledRejections = value; 17986 return this; 17987 } else { 17988 return errorOnUnhandledRejections; 17989 } 17990 }; 17991} 17992 17993/** 17994 * Constructs a promise manager. 17995 * 17996 * @param {function(function)} nextTick Function for executing functions in the next turn. 17997 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 17998 * debugging purposes. 17999 * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled 18000 * promises rejections. 18001 * @returns {object} Promise manager. 18002 */ 18003function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { 18004 var $qMinErr = minErr('$q', TypeError); 18005 var queueSize = 0; 18006 var checkQueue = []; 18007 18008 /** 18009 * @ngdoc method 18010 * @name ng.$q#defer 18011 * @kind function 18012 * 18013 * @description 18014 * Creates a `Deferred` object which represents a task which will finish in the future. 18015 * 18016 * @returns {Deferred} Returns a new instance of deferred. 18017 */ 18018 function defer() { 18019 return new Deferred(); 18020 } 18021 18022 function Deferred() { 18023 var promise = this.promise = new Promise(); 18024 //Non prototype methods necessary to support unbound execution :/ 18025 this.resolve = function(val) { resolvePromise(promise, val); }; 18026 this.reject = function(reason) { rejectPromise(promise, reason); }; 18027 this.notify = function(progress) { notifyPromise(promise, progress); }; 18028 } 18029 18030 18031 function Promise() { 18032 this.$$state = { status: 0 }; 18033 } 18034 18035 extend(Promise.prototype, { 18036 then: function(onFulfilled, onRejected, progressBack) { 18037 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 18038 return this; 18039 } 18040 var result = new Promise(); 18041 18042 this.$$state.pending = this.$$state.pending || []; 18043 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 18044 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 18045 18046 return result; 18047 }, 18048 18049 'catch': function(callback) { 18050 return this.then(null, callback); 18051 }, 18052 18053 'finally': function(callback, progressBack) { 18054 return this.then(function(value) { 18055 return handleCallback(value, resolve, callback); 18056 }, function(error) { 18057 return handleCallback(error, reject, callback); 18058 }, progressBack); 18059 } 18060 }); 18061 18062 function processQueue(state) { 18063 var fn, promise, pending; 18064 18065 pending = state.pending; 18066 state.processScheduled = false; 18067 state.pending = undefined; 18068 try { 18069 for (var i = 0, ii = pending.length; i < ii; ++i) { 18070 markQStateExceptionHandled(state); 18071 promise = pending[i][0]; 18072 fn = pending[i][state.status]; 18073 try { 18074 if (isFunction(fn)) { 18075 resolvePromise(promise, fn(state.value)); 18076 } else if (state.status === 1) { 18077 resolvePromise(promise, state.value); 18078 } else { 18079 rejectPromise(promise, state.value); 18080 } 18081 } catch (e) { 18082 rejectPromise(promise, e); 18083 // This error is explicitly marked for being passed to the $exceptionHandler 18084 if (e && e.$$passToExceptionHandler === true) { 18085 exceptionHandler(e); 18086 } 18087 } 18088 } 18089 } finally { 18090 --queueSize; 18091 if (errorOnUnhandledRejections && queueSize === 0) { 18092 nextTick(processChecks); 18093 } 18094 } 18095 } 18096 18097 function processChecks() { 18098 // eslint-disable-next-line no-unmodified-loop-condition 18099 while (!queueSize && checkQueue.length) { 18100 var toCheck = checkQueue.shift(); 18101 if (!isStateExceptionHandled(toCheck)) { 18102 markQStateExceptionHandled(toCheck); 18103 var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value); 18104 if (isError(toCheck.value)) { 18105 exceptionHandler(toCheck.value, errorMessage); 18106 } else { 18107 exceptionHandler(errorMessage); 18108 } 18109 } 18110 } 18111 } 18112 18113 function scheduleProcessQueue(state) { 18114 if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) { 18115 if (queueSize === 0 && checkQueue.length === 0) { 18116 nextTick(processChecks); 18117 } 18118 checkQueue.push(state); 18119 } 18120 if (state.processScheduled || !state.pending) return; 18121 state.processScheduled = true; 18122 ++queueSize; 18123 nextTick(function() { processQueue(state); }); 18124 } 18125 18126 function resolvePromise(promise, val) { 18127 if (promise.$$state.status) return; 18128 if (val === promise) { 18129 $$reject(promise, $qMinErr( 18130 'qcycle', 18131 'Expected promise to be resolved with value other than itself \'{0}\'', 18132 val)); 18133 } else { 18134 $$resolve(promise, val); 18135 } 18136 18137 } 18138 18139 function $$resolve(promise, val) { 18140 var then; 18141 var done = false; 18142 try { 18143 if (isObject(val) || isFunction(val)) then = val.then; 18144 if (isFunction(then)) { 18145 promise.$$state.status = -1; 18146 then.call(val, doResolve, doReject, doNotify); 18147 } else { 18148 promise.$$state.value = val; 18149 promise.$$state.status = 1; 18150 scheduleProcessQueue(promise.$$state); 18151 } 18152 } catch (e) { 18153 doReject(e); 18154 } 18155 18156 function doResolve(val) { 18157 if (done) return; 18158 done = true; 18159 $$resolve(promise, val); 18160 } 18161 function doReject(val) { 18162 if (done) return; 18163 done = true; 18164 $$reject(promise, val); 18165 } 18166 function doNotify(progress) { 18167 notifyPromise(promise, progress); 18168 } 18169 } 18170 18171 function rejectPromise(promise, reason) { 18172 if (promise.$$state.status) return; 18173 $$reject(promise, reason); 18174 } 18175 18176 function $$reject(promise, reason) { 18177 promise.$$state.value = reason; 18178 promise.$$state.status = 2; 18179 scheduleProcessQueue(promise.$$state); 18180 } 18181 18182 function notifyPromise(promise, progress) { 18183 var callbacks = promise.$$state.pending; 18184 18185 if ((promise.$$state.status <= 0) && callbacks && callbacks.length) { 18186 nextTick(function() { 18187 var callback, result; 18188 for (var i = 0, ii = callbacks.length; i < ii; i++) { 18189 result = callbacks[i][0]; 18190 callback = callbacks[i][3]; 18191 try { 18192 notifyPromise(result, isFunction(callback) ? callback(progress) : progress); 18193 } catch (e) { 18194 exceptionHandler(e); 18195 } 18196 } 18197 }); 18198 } 18199 } 18200 18201 /** 18202 * @ngdoc method 18203 * @name $q#reject 18204 * @kind function 18205 * 18206 * @description 18207 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 18208 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 18209 * a promise chain, you don't need to worry about it. 18210 * 18211 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 18212 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 18213 * a promise error callback and you want to forward the error to the promise derived from the 18214 * current promise, you have to "rethrow" the error by returning a rejection constructed via 18215 * `reject`. 18216 * 18217 * ```js 18218 * promiseB = promiseA.then(function(result) { 18219 * // success: do something and resolve promiseB 18220 * // with the old or a new result 18221 * return result; 18222 * }, function(reason) { 18223 * // error: handle the error if possible and 18224 * // resolve promiseB with newPromiseOrValue, 18225 * // otherwise forward the rejection to promiseB 18226 * if (canHandle(reason)) { 18227 * // handle the error and recover 18228 * return newPromiseOrValue; 18229 * } 18230 * return $q.reject(reason); 18231 * }); 18232 * ``` 18233 * 18234 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 18235 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 18236 */ 18237 function reject(reason) { 18238 var result = new Promise(); 18239 rejectPromise(result, reason); 18240 return result; 18241 } 18242 18243 function handleCallback(value, resolver, callback) { 18244 var callbackOutput = null; 18245 try { 18246 if (isFunction(callback)) callbackOutput = callback(); 18247 } catch (e) { 18248 return reject(e); 18249 } 18250 if (isPromiseLike(callbackOutput)) { 18251 return callbackOutput.then(function() { 18252 return resolver(value); 18253 }, reject); 18254 } else { 18255 return resolver(value); 18256 } 18257 } 18258 18259 /** 18260 * @ngdoc method 18261 * @name $q#when 18262 * @kind function 18263 * 18264 * @description 18265 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 18266 * This is useful when you are dealing with an object that might or might not be a promise, or if 18267 * the promise comes from a source that can't be trusted. 18268 * 18269 * @param {*} value Value or a promise 18270 * @param {Function=} successCallback 18271 * @param {Function=} errorCallback 18272 * @param {Function=} progressCallback 18273 * @returns {Promise} Returns a promise of the passed value or promise 18274 */ 18275 18276 18277 function when(value, callback, errback, progressBack) { 18278 var result = new Promise(); 18279 resolvePromise(result, value); 18280 return result.then(callback, errback, progressBack); 18281 } 18282 18283 /** 18284 * @ngdoc method 18285 * @name $q#resolve 18286 * @kind function 18287 * 18288 * @description 18289 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 18290 * 18291 * @param {*} value Value or a promise 18292 * @param {Function=} successCallback 18293 * @param {Function=} errorCallback 18294 * @param {Function=} progressCallback 18295 * @returns {Promise} Returns a promise of the passed value or promise 18296 */ 18297 var resolve = when; 18298 18299 /** 18300 * @ngdoc method 18301 * @name $q#all 18302 * @kind function 18303 * 18304 * @description 18305 * Combines multiple promises into a single promise that is resolved when all of the input 18306 * promises are resolved. 18307 * 18308 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 18309 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 18310 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 18311 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 18312 * with the same rejection value. 18313 */ 18314 18315 function all(promises) { 18316 var result = new Promise(), 18317 counter = 0, 18318 results = isArray(promises) ? [] : {}; 18319 18320 forEach(promises, function(promise, key) { 18321 counter++; 18322 when(promise).then(function(value) { 18323 results[key] = value; 18324 if (!(--counter)) resolvePromise(result, results); 18325 }, function(reason) { 18326 rejectPromise(result, reason); 18327 }); 18328 }); 18329 18330 if (counter === 0) { 18331 resolvePromise(result, results); 18332 } 18333 18334 return result; 18335 } 18336 18337 /** 18338 * @ngdoc method 18339 * @name $q#race 18340 * @kind function 18341 * 18342 * @description 18343 * Returns a promise that resolves or rejects as soon as one of those promises 18344 * resolves or rejects, with the value or reason from that promise. 18345 * 18346 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 18347 * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises` 18348 * resolves or rejects, with the value or reason from that promise. 18349 */ 18350 18351 function race(promises) { 18352 var deferred = defer(); 18353 18354 forEach(promises, function(promise) { 18355 when(promise).then(deferred.resolve, deferred.reject); 18356 }); 18357 18358 return deferred.promise; 18359 } 18360 18361 function $Q(resolver) { 18362 if (!isFunction(resolver)) { 18363 throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); 18364 } 18365 18366 var promise = new Promise(); 18367 18368 function resolveFn(value) { 18369 resolvePromise(promise, value); 18370 } 18371 18372 function rejectFn(reason) { 18373 rejectPromise(promise, reason); 18374 } 18375 18376 resolver(resolveFn, rejectFn); 18377 18378 return promise; 18379 } 18380 18381 // Let's make the instanceof operator work for promises, so that 18382 // `new $q(fn) instanceof $q` would evaluate to true. 18383 $Q.prototype = Promise.prototype; 18384 18385 $Q.defer = defer; 18386 $Q.reject = reject; 18387 $Q.when = when; 18388 $Q.resolve = resolve; 18389 $Q.all = all; 18390 $Q.race = race; 18391 18392 return $Q; 18393} 18394 18395function isStateExceptionHandled(state) { 18396 return !!state.pur; 18397} 18398function markQStateExceptionHandled(state) { 18399 state.pur = true; 18400} 18401function markQExceptionHandled(q) { 18402 // Built-in `$q` promises will always have a `$$state` property. This check is to allow 18403 // overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises). 18404 // (Currently, this is the only method that might be called with a promise, even if it is not 18405 // created by the built-in `$q`.) 18406 if (q.$$state) { 18407 markQStateExceptionHandled(q.$$state); 18408 } 18409} 18410 18411/** @this */ 18412function $$RAFProvider() { //rAF 18413 this.$get = ['$window', '$timeout', function($window, $timeout) { 18414 var requestAnimationFrame = $window.requestAnimationFrame || 18415 $window.webkitRequestAnimationFrame; 18416 18417 var cancelAnimationFrame = $window.cancelAnimationFrame || 18418 $window.webkitCancelAnimationFrame || 18419 $window.webkitCancelRequestAnimationFrame; 18420 18421 var rafSupported = !!requestAnimationFrame; 18422 var raf = rafSupported 18423 ? function(fn) { 18424 var id = requestAnimationFrame(fn); 18425 return function() { 18426 cancelAnimationFrame(id); 18427 }; 18428 } 18429 : function(fn) { 18430 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 18431 return function() { 18432 $timeout.cancel(timer); 18433 }; 18434 }; 18435 18436 raf.supported = rafSupported; 18437 18438 return raf; 18439 }]; 18440} 18441 18442/** 18443 * DESIGN NOTES 18444 * 18445 * The design decisions behind the scope are heavily favored for speed and memory consumption. 18446 * 18447 * The typical use of scope is to watch the expressions, which most of the time return the same 18448 * value as last time so we optimize the operation. 18449 * 18450 * Closures construction is expensive in terms of speed as well as memory: 18451 * - No closures, instead use prototypical inheritance for API 18452 * - Internal state needs to be stored on scope directly, which means that private state is 18453 * exposed as $$____ properties 18454 * 18455 * Loop operations are optimized by using while(count--) { ... } 18456 * - This means that in order to keep the same order of execution as addition we have to add 18457 * items to the array at the beginning (unshift) instead of at the end (push) 18458 * 18459 * Child scopes are created and removed often 18460 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 18461 * 18462 * There are fewer watches than observers. This is why you don't want the observer to be implemented 18463 * in the same way as watch. Watch requires return of the initialization function which is expensive 18464 * to construct. 18465 */ 18466 18467 18468/** 18469 * @ngdoc provider 18470 * @name $rootScopeProvider 18471 * @description 18472 * 18473 * Provider for the $rootScope service. 18474 */ 18475 18476/** 18477 * @ngdoc method 18478 * @name $rootScopeProvider#digestTtl 18479 * @description 18480 * 18481 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 18482 * assuming that the model is unstable. 18483 * 18484 * The current default is 10 iterations. 18485 * 18486 * In complex applications it's possible that the dependencies between `$watch`s will result in 18487 * several digest iterations. However if an application needs more than the default 10 digest 18488 * iterations for its model to stabilize then you should investigate what is causing the model to 18489 * continuously change during the digest. 18490 * 18491 * Increasing the TTL could have performance implications, so you should not change it without 18492 * proper justification. 18493 * 18494 * @param {number} limit The number of digest iterations. 18495 */ 18496 18497 18498/** 18499 * @ngdoc service 18500 * @name $rootScope 18501 * @this 18502 * 18503 * @description 18504 * 18505 * Every application has a single root {@link ng.$rootScope.Scope scope}. 18506 * All other scopes are descendant scopes of the root scope. Scopes provide separation 18507 * between the model and the view, via a mechanism for watching the model for changes. 18508 * They also provide event emission/broadcast and subscription facility. See the 18509 * {@link guide/scope developer guide on scopes}. 18510 */ 18511function $RootScopeProvider() { 18512 var TTL = 10; 18513 var $rootScopeMinErr = minErr('$rootScope'); 18514 var lastDirtyWatch = null; 18515 var applyAsyncId = null; 18516 18517 this.digestTtl = function(value) { 18518 if (arguments.length) { 18519 TTL = value; 18520 } 18521 return TTL; 18522 }; 18523 18524 function createChildScopeClass(parent) { 18525 function ChildScope() { 18526 this.$$watchers = this.$$nextSibling = 18527 this.$$childHead = this.$$childTail = null; 18528 this.$$listeners = {}; 18529 this.$$listenerCount = {}; 18530 this.$$watchersCount = 0; 18531 this.$id = nextUid(); 18532 this.$$ChildScope = null; 18533 this.$$suspended = false; 18534 } 18535 ChildScope.prototype = parent; 18536 return ChildScope; 18537 } 18538 18539 this.$get = ['$exceptionHandler', '$parse', '$browser', 18540 function($exceptionHandler, $parse, $browser) { 18541 18542 function destroyChildScope($event) { 18543 $event.currentScope.$$destroyed = true; 18544 } 18545 18546 function cleanUpScope($scope) { 18547 18548 // Support: IE 9 only 18549 if (msie === 9) { 18550 // There is a memory leak in IE9 if all child scopes are not disconnected 18551 // completely when a scope is destroyed. So this code will recurse up through 18552 // all this scopes children 18553 // 18554 // See issue https://github.com/angular/angular.js/issues/10706 18555 if ($scope.$$childHead) { 18556 cleanUpScope($scope.$$childHead); 18557 } 18558 if ($scope.$$nextSibling) { 18559 cleanUpScope($scope.$$nextSibling); 18560 } 18561 } 18562 18563 // The code below works around IE9 and V8's memory leaks 18564 // 18565 // See: 18566 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 18567 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 18568 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 18569 18570 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 18571 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 18572 } 18573 18574 /** 18575 * @ngdoc type 18576 * @name $rootScope.Scope 18577 * 18578 * @description 18579 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 18580 * {@link auto.$injector $injector}. Child scopes are created using the 18581 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 18582 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 18583 * an in-depth introduction and usage examples. 18584 * 18585 * 18586 * ## Inheritance 18587 * A scope can inherit from a parent scope, as in this example: 18588 * ```js 18589 var parent = $rootScope; 18590 var child = parent.$new(); 18591 18592 parent.salutation = "Hello"; 18593 expect(child.salutation).toEqual('Hello'); 18594 18595 child.salutation = "Welcome"; 18596 expect(child.salutation).toEqual('Welcome'); 18597 expect(parent.salutation).toEqual('Hello'); 18598 * ``` 18599 * 18600 * When interacting with `Scope` in tests, additional helper methods are available on the 18601 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 18602 * details. 18603 * 18604 * 18605 * @param {Object.<string, function()>=} providers Map of service factory which need to be 18606 * provided for the current scope. Defaults to {@link ng}. 18607 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 18608 * append/override services provided by `providers`. This is handy 18609 * when unit-testing and having the need to override a default 18610 * service. 18611 * @returns {Object} Newly created scope. 18612 * 18613 */ 18614 function Scope() { 18615 this.$id = nextUid(); 18616 this.$$phase = this.$parent = this.$$watchers = 18617 this.$$nextSibling = this.$$prevSibling = 18618 this.$$childHead = this.$$childTail = null; 18619 this.$root = this; 18620 this.$$destroyed = false; 18621 this.$$suspended = false; 18622 this.$$listeners = {}; 18623 this.$$listenerCount = {}; 18624 this.$$watchersCount = 0; 18625 this.$$isolateBindings = null; 18626 } 18627 18628 /** 18629 * @ngdoc property 18630 * @name $rootScope.Scope#$id 18631 * 18632 * @description 18633 * Unique scope ID (monotonically increasing) useful for debugging. 18634 */ 18635 18636 /** 18637 * @ngdoc property 18638 * @name $rootScope.Scope#$parent 18639 * 18640 * @description 18641 * Reference to the parent scope. 18642 */ 18643 18644 /** 18645 * @ngdoc property 18646 * @name $rootScope.Scope#$root 18647 * 18648 * @description 18649 * Reference to the root scope. 18650 */ 18651 18652 Scope.prototype = { 18653 constructor: Scope, 18654 /** 18655 * @ngdoc method 18656 * @name $rootScope.Scope#$new 18657 * @kind function 18658 * 18659 * @description 18660 * Creates a new child {@link ng.$rootScope.Scope scope}. 18661 * 18662 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 18663 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 18664 * 18665 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 18666 * desired for the scope and its child scopes to be permanently detached from the parent and 18667 * thus stop participating in model change detection and listener notification by invoking. 18668 * 18669 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 18670 * parent scope. The scope is isolated, as it can not see parent scope properties. 18671 * When creating widgets, it is useful for the widget to not accidentally read parent 18672 * state. 18673 * 18674 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 18675 * of the newly created scope. Defaults to `this` scope if not provided. 18676 * This is used when creating a transclude scope to correctly place it 18677 * in the scope hierarchy while maintaining the correct prototypical 18678 * inheritance. 18679 * 18680 * @returns {Object} The newly created child scope. 18681 * 18682 */ 18683 $new: function(isolate, parent) { 18684 var child; 18685 18686 parent = parent || this; 18687 18688 if (isolate) { 18689 child = new Scope(); 18690 child.$root = this.$root; 18691 } else { 18692 // Only create a child scope class if somebody asks for one, 18693 // but cache it to allow the VM to optimize lookups. 18694 if (!this.$$ChildScope) { 18695 this.$$ChildScope = createChildScopeClass(this); 18696 } 18697 child = new this.$$ChildScope(); 18698 } 18699 child.$parent = parent; 18700 child.$$prevSibling = parent.$$childTail; 18701 if (parent.$$childHead) { 18702 parent.$$childTail.$$nextSibling = child; 18703 parent.$$childTail = child; 18704 } else { 18705 parent.$$childHead = parent.$$childTail = child; 18706 } 18707 18708 // When the new scope is not isolated or we inherit from `this`, and 18709 // the parent scope is destroyed, the property `$$destroyed` is inherited 18710 // prototypically. In all other cases, this property needs to be set 18711 // when the parent scope is destroyed. 18712 // The listener needs to be added after the parent is set 18713 if (isolate || parent !== this) child.$on('$destroy', destroyChildScope); 18714 18715 return child; 18716 }, 18717 18718 /** 18719 * @ngdoc method 18720 * @name $rootScope.Scope#$watch 18721 * @kind function 18722 * 18723 * @description 18724 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 18725 * 18726 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 18727 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 18728 * its value when executed multiple times with the same input because it may be executed multiple 18729 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 18730 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).) 18731 * - The `listener` is called only when the value from the current `watchExpression` and the 18732 * previous call to `watchExpression` are not equal (with the exception of the initial run, 18733 * see below). Inequality is determined according to reference inequality, 18734 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 18735 * via the `!==` Javascript operator, unless `objectEquality == true` 18736 * (see next point) 18737 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 18738 * according to the {@link angular.equals} function. To save the value of the object for 18739 * later comparison, the {@link angular.copy} function is used. This therefore means that 18740 * watching complex objects will have adverse memory and performance implications. 18741 * - This should not be used to watch for changes in objects that are (or contain) 18742 * [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}. 18743 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 18744 * This is achieved by rerunning the watchers until no changes are detected. The rerun 18745 * iteration limit is 10 to prevent an infinite loop deadlock. 18746 * 18747 * 18748 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 18749 * you can register a `watchExpression` function with no `listener`. (Be prepared for 18750 * multiple calls to your `watchExpression` because it will execute multiple times in a 18751 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 18752 * 18753 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 18754 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 18755 * watcher. In rare cases, this is undesirable because the listener is called when the result 18756 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 18757 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 18758 * listener was called due to initialization. 18759 * 18760 * 18761 * 18762 * @example 18763 * ```js 18764 // let's assume that scope was dependency injected as the $rootScope 18765 var scope = $rootScope; 18766 scope.name = 'misko'; 18767 scope.counter = 0; 18768 18769 expect(scope.counter).toEqual(0); 18770 scope.$watch('name', function(newValue, oldValue) { 18771 scope.counter = scope.counter + 1; 18772 }); 18773 expect(scope.counter).toEqual(0); 18774 18775 scope.$digest(); 18776 // the listener is always called during the first $digest loop after it was registered 18777 expect(scope.counter).toEqual(1); 18778 18779 scope.$digest(); 18780 // but now it will not be called unless the value changes 18781 expect(scope.counter).toEqual(1); 18782 18783 scope.name = 'adam'; 18784 scope.$digest(); 18785 expect(scope.counter).toEqual(2); 18786 18787 18788 18789 // Using a function as a watchExpression 18790 var food; 18791 scope.foodCounter = 0; 18792 expect(scope.foodCounter).toEqual(0); 18793 scope.$watch( 18794 // This function returns the value being watched. It is called for each turn of the $digest loop 18795 function() { return food; }, 18796 // This is the change listener, called when the value returned from the above function changes 18797 function(newValue, oldValue) { 18798 if ( newValue !== oldValue ) { 18799 // Only increment the counter if the value changed 18800 scope.foodCounter = scope.foodCounter + 1; 18801 } 18802 } 18803 ); 18804 // No digest has been run so the counter will be zero 18805 expect(scope.foodCounter).toEqual(0); 18806 18807 // Run the digest but since food has not changed count will still be zero 18808 scope.$digest(); 18809 expect(scope.foodCounter).toEqual(0); 18810 18811 // Update food and run digest. Now the counter will increment 18812 food = 'cheeseburger'; 18813 scope.$digest(); 18814 expect(scope.foodCounter).toEqual(1); 18815 18816 * ``` 18817 * 18818 * 18819 * 18820 * @param {(function()|string)} watchExpression Expression that is evaluated on each 18821 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 18822 * a call to the `listener`. 18823 * 18824 * - `string`: Evaluated as {@link guide/expression expression} 18825 * - `function(scope)`: called with current `scope` as a parameter. 18826 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 18827 * of `watchExpression` changes. 18828 * 18829 * - `newVal` contains the current value of the `watchExpression` 18830 * - `oldVal` contains the previous value of the `watchExpression` 18831 * - `scope` refers to the current scope 18832 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 18833 * comparing for reference equality. 18834 * @returns {function()} Returns a deregistration function for this listener. 18835 */ 18836 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 18837 var get = $parse(watchExp); 18838 var fn = isFunction(listener) ? listener : noop; 18839 18840 if (get.$$watchDelegate) { 18841 return get.$$watchDelegate(this, fn, objectEquality, get, watchExp); 18842 } 18843 var scope = this, 18844 array = scope.$$watchers, 18845 watcher = { 18846 fn: fn, 18847 last: initWatchVal, 18848 get: get, 18849 exp: prettyPrintExpression || watchExp, 18850 eq: !!objectEquality 18851 }; 18852 18853 lastDirtyWatch = null; 18854 18855 if (!array) { 18856 array = scope.$$watchers = []; 18857 array.$$digestWatchIndex = -1; 18858 } 18859 // we use unshift since we use a while loop in $digest for speed. 18860 // the while loop reads in reverse order. 18861 array.unshift(watcher); 18862 array.$$digestWatchIndex++; 18863 incrementWatchersCount(this, 1); 18864 18865 return function deregisterWatch() { 18866 var index = arrayRemove(array, watcher); 18867 if (index >= 0) { 18868 incrementWatchersCount(scope, -1); 18869 if (index < array.$$digestWatchIndex) { 18870 array.$$digestWatchIndex--; 18871 } 18872 } 18873 lastDirtyWatch = null; 18874 }; 18875 }, 18876 18877 /** 18878 * @ngdoc method 18879 * @name $rootScope.Scope#$watchGroup 18880 * @kind function 18881 * 18882 * @description 18883 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 18884 * If any one expression in the collection changes the `listener` is executed. 18885 * 18886 * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return 18887 * values are examined for changes on every call to `$digest`. 18888 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 18889 * 18890 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 18891 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 18892 * 18893 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 18894 * expression in `watchExpressions` changes 18895 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 18896 * those of `watchExpression` 18897 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 18898 * those of `watchExpression` 18899 * The `scope` refers to the current scope. 18900 * @returns {function()} Returns a de-registration function for all listeners. 18901 */ 18902 $watchGroup: function(watchExpressions, listener) { 18903 var oldValues = new Array(watchExpressions.length); 18904 var newValues = new Array(watchExpressions.length); 18905 var deregisterFns = []; 18906 var self = this; 18907 var changeReactionScheduled = false; 18908 var firstRun = true; 18909 18910 if (!watchExpressions.length) { 18911 // No expressions means we call the listener ASAP 18912 var shouldCall = true; 18913 self.$evalAsync(function() { 18914 if (shouldCall) listener(newValues, newValues, self); 18915 }); 18916 return function deregisterWatchGroup() { 18917 shouldCall = false; 18918 }; 18919 } 18920 18921 if (watchExpressions.length === 1) { 18922 // Special case size of one 18923 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 18924 newValues[0] = value; 18925 oldValues[0] = oldValue; 18926 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 18927 }); 18928 } 18929 18930 forEach(watchExpressions, function(expr, i) { 18931 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value) { 18932 newValues[i] = value; 18933 if (!changeReactionScheduled) { 18934 changeReactionScheduled = true; 18935 self.$evalAsync(watchGroupAction); 18936 } 18937 }); 18938 deregisterFns.push(unwatchFn); 18939 }); 18940 18941 function watchGroupAction() { 18942 changeReactionScheduled = false; 18943 18944 try { 18945 if (firstRun) { 18946 firstRun = false; 18947 listener(newValues, newValues, self); 18948 } else { 18949 listener(newValues, oldValues, self); 18950 } 18951 } finally { 18952 for (var i = 0; i < watchExpressions.length; i++) { 18953 oldValues[i] = newValues[i]; 18954 } 18955 } 18956 } 18957 18958 return function deregisterWatchGroup() { 18959 while (deregisterFns.length) { 18960 deregisterFns.shift()(); 18961 } 18962 }; 18963 }, 18964 18965 18966 /** 18967 * @ngdoc method 18968 * @name $rootScope.Scope#$watchCollection 18969 * @kind function 18970 * 18971 * @description 18972 * Shallow watches the properties of an object and fires whenever any of the properties change 18973 * (for arrays, this implies watching the array items; for object maps, this implies watching 18974 * the properties). If a change is detected, the `listener` callback is fired. 18975 * 18976 * - The `obj` collection is observed via standard $watch operation and is examined on every 18977 * call to $digest() to see if any items have been added, removed, or moved. 18978 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 18979 * adding, removing, and moving items belonging to an object or array. 18980 * 18981 * 18982 * @example 18983 * ```js 18984 $scope.names = ['igor', 'matias', 'misko', 'james']; 18985 $scope.dataCount = 4; 18986 18987 $scope.$watchCollection('names', function(newNames, oldNames) { 18988 $scope.dataCount = newNames.length; 18989 }); 18990 18991 expect($scope.dataCount).toEqual(4); 18992 $scope.$digest(); 18993 18994 //still at 4 ... no changes 18995 expect($scope.dataCount).toEqual(4); 18996 18997 $scope.names.pop(); 18998 $scope.$digest(); 18999 19000 //now there's been a change 19001 expect($scope.dataCount).toEqual(3); 19002 * ``` 19003 * 19004 * 19005 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 19006 * expression value should evaluate to an object or an array which is observed on each 19007 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 19008 * collection will trigger a call to the `listener`. 19009 * 19010 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 19011 * when a change is detected. 19012 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 19013 * - The `oldCollection` object is a copy of the former collection data. 19014 * Due to performance considerations, the`oldCollection` value is computed only if the 19015 * `listener` function declares two or more arguments. 19016 * - The `scope` argument refers to the current scope. 19017 * 19018 * @returns {function()} Returns a de-registration function for this listener. When the 19019 * de-registration function is executed, the internal watch operation is terminated. 19020 */ 19021 $watchCollection: function(obj, listener) { 19022 // Mark the interceptor as 19023 // ... $$pure when literal since the instance will change when any input changes 19024 $watchCollectionInterceptor.$$pure = $parse(obj).literal; 19025 // ... $stateful when non-literal since we must read the state of the collection 19026 $watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure; 19027 19028 var self = this; 19029 // the current value, updated on each dirty-check run 19030 var newValue; 19031 // a shallow copy of the newValue from the last dirty-check run, 19032 // updated to match newValue during dirty-check run 19033 var oldValue; 19034 // a shallow copy of the newValue from when the last change happened 19035 var veryOldValue; 19036 // only track veryOldValue if the listener is asking for it 19037 var trackVeryOldValue = (listener.length > 1); 19038 var changeDetected = 0; 19039 var changeDetector = $parse(obj, $watchCollectionInterceptor); 19040 var internalArray = []; 19041 var internalObject = {}; 19042 var initRun = true; 19043 var oldLength = 0; 19044 19045 function $watchCollectionInterceptor(_value) { 19046 newValue = _value; 19047 var newLength, key, bothNaN, newItem, oldItem; 19048 19049 // If the new value is undefined, then return undefined as the watch may be a one-time watch 19050 if (isUndefined(newValue)) return; 19051 19052 if (!isObject(newValue)) { // if primitive 19053 if (oldValue !== newValue) { 19054 oldValue = newValue; 19055 changeDetected++; 19056 } 19057 } else if (isArrayLike(newValue)) { 19058 if (oldValue !== internalArray) { 19059 // we are transitioning from something which was not an array into array. 19060 oldValue = internalArray; 19061 oldLength = oldValue.length = 0; 19062 changeDetected++; 19063 } 19064 19065 newLength = newValue.length; 19066 19067 if (oldLength !== newLength) { 19068 // if lengths do not match we need to trigger change notification 19069 changeDetected++; 19070 oldValue.length = oldLength = newLength; 19071 } 19072 // copy the items to oldValue and look for changes. 19073 for (var i = 0; i < newLength; i++) { 19074 oldItem = oldValue[i]; 19075 newItem = newValue[i]; 19076 19077 // eslint-disable-next-line no-self-compare 19078 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 19079 if (!bothNaN && (oldItem !== newItem)) { 19080 changeDetected++; 19081 oldValue[i] = newItem; 19082 } 19083 } 19084 } else { 19085 if (oldValue !== internalObject) { 19086 // we are transitioning from something which was not an object into object. 19087 oldValue = internalObject = {}; 19088 oldLength = 0; 19089 changeDetected++; 19090 } 19091 // copy the items to oldValue and look for changes. 19092 newLength = 0; 19093 for (key in newValue) { 19094 if (hasOwnProperty.call(newValue, key)) { 19095 newLength++; 19096 newItem = newValue[key]; 19097 oldItem = oldValue[key]; 19098 19099 if (key in oldValue) { 19100 // eslint-disable-next-line no-self-compare 19101 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 19102 if (!bothNaN && (oldItem !== newItem)) { 19103 changeDetected++; 19104 oldValue[key] = newItem; 19105 } 19106 } else { 19107 oldLength++; 19108 oldValue[key] = newItem; 19109 changeDetected++; 19110 } 19111 } 19112 } 19113 if (oldLength > newLength) { 19114 // we used to have more keys, need to find them and destroy them. 19115 changeDetected++; 19116 for (key in oldValue) { 19117 if (!hasOwnProperty.call(newValue, key)) { 19118 oldLength--; 19119 delete oldValue[key]; 19120 } 19121 } 19122 } 19123 } 19124 return changeDetected; 19125 } 19126 19127 function $watchCollectionAction() { 19128 if (initRun) { 19129 initRun = false; 19130 listener(newValue, newValue, self); 19131 } else { 19132 listener(newValue, veryOldValue, self); 19133 } 19134 19135 // make a copy for the next time a collection is changed 19136 if (trackVeryOldValue) { 19137 if (!isObject(newValue)) { 19138 //primitive 19139 veryOldValue = newValue; 19140 } else if (isArrayLike(newValue)) { 19141 veryOldValue = new Array(newValue.length); 19142 for (var i = 0; i < newValue.length; i++) { 19143 veryOldValue[i] = newValue[i]; 19144 } 19145 } else { // if object 19146 veryOldValue = {}; 19147 for (var key in newValue) { 19148 if (hasOwnProperty.call(newValue, key)) { 19149 veryOldValue[key] = newValue[key]; 19150 } 19151 } 19152 } 19153 } 19154 } 19155 19156 return this.$watch(changeDetector, $watchCollectionAction); 19157 }, 19158 19159 /** 19160 * @ngdoc method 19161 * @name $rootScope.Scope#$digest 19162 * @kind function 19163 * 19164 * @description 19165 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 19166 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 19167 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 19168 * until no more listeners are firing. This means that it is possible to get into an infinite 19169 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 19170 * iterations exceeds 10. 19171 * 19172 * Usually, you don't call `$digest()` directly in 19173 * {@link ng.directive:ngController controllers} or in 19174 * {@link ng.$compileProvider#directive directives}. 19175 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 19176 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 19177 * 19178 * If you want to be notified whenever `$digest()` is called, 19179 * you can register a `watchExpression` function with 19180 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 19181 * 19182 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 19183 * 19184 * @example 19185 * ```js 19186 var scope = ...; 19187 scope.name = 'misko'; 19188 scope.counter = 0; 19189 19190 expect(scope.counter).toEqual(0); 19191 scope.$watch('name', function(newValue, oldValue) { 19192 scope.counter = scope.counter + 1; 19193 }); 19194 expect(scope.counter).toEqual(0); 19195 19196 scope.$digest(); 19197 // the listener is always called during the first $digest loop after it was registered 19198 expect(scope.counter).toEqual(1); 19199 19200 scope.$digest(); 19201 // but now it will not be called unless the value changes 19202 expect(scope.counter).toEqual(1); 19203 19204 scope.name = 'adam'; 19205 scope.$digest(); 19206 expect(scope.counter).toEqual(2); 19207 * ``` 19208 * 19209 */ 19210 $digest: function() { 19211 var watch, value, last, fn, get, 19212 watchers, 19213 dirty, ttl = TTL, 19214 next, current, target = asyncQueue.length ? $rootScope : this, 19215 watchLog = [], 19216 logIdx, asyncTask; 19217 19218 beginPhase('$digest'); 19219 // Check for changes to browser url that happened in sync before the call to $digest 19220 $browser.$$checkUrlChange(); 19221 19222 if (this === $rootScope && applyAsyncId !== null) { 19223 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 19224 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 19225 $browser.defer.cancel(applyAsyncId); 19226 flushApplyAsync(); 19227 } 19228 19229 lastDirtyWatch = null; 19230 19231 do { // "while dirty" loop 19232 dirty = false; 19233 current = target; 19234 19235 // It's safe for asyncQueuePosition to be a local variable here because this loop can't 19236 // be reentered recursively. Calling $digest from a function passed to $evalAsync would 19237 // lead to a '$digest already in progress' error. 19238 for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) { 19239 try { 19240 asyncTask = asyncQueue[asyncQueuePosition]; 19241 fn = asyncTask.fn; 19242 fn(asyncTask.scope, asyncTask.locals); 19243 } catch (e) { 19244 $exceptionHandler(e); 19245 } 19246 lastDirtyWatch = null; 19247 } 19248 asyncQueue.length = 0; 19249 19250 traverseScopesLoop: 19251 do { // "traverse the scopes" loop 19252 if ((watchers = !current.$$suspended && current.$$watchers)) { 19253 // process our watches 19254 watchers.$$digestWatchIndex = watchers.length; 19255 while (watchers.$$digestWatchIndex--) { 19256 try { 19257 watch = watchers[watchers.$$digestWatchIndex]; 19258 // Most common watches are on primitives, in which case we can short 19259 // circuit it with === operator, only when === fails do we use .equals 19260 if (watch) { 19261 get = watch.get; 19262 if ((value = get(current)) !== (last = watch.last) && 19263 !(watch.eq 19264 ? equals(value, last) 19265 : (isNumberNaN(value) && isNumberNaN(last)))) { 19266 dirty = true; 19267 lastDirtyWatch = watch; 19268 watch.last = watch.eq ? copy(value, null) : value; 19269 fn = watch.fn; 19270 fn(value, ((last === initWatchVal) ? value : last), current); 19271 if (ttl < 5) { 19272 logIdx = 4 - ttl; 19273 if (!watchLog[logIdx]) watchLog[logIdx] = []; 19274 watchLog[logIdx].push({ 19275 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 19276 newVal: value, 19277 oldVal: last 19278 }); 19279 } 19280 } else if (watch === lastDirtyWatch) { 19281 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 19282 // have already been tested. 19283 dirty = false; 19284 break traverseScopesLoop; 19285 } 19286 } 19287 } catch (e) { 19288 $exceptionHandler(e); 19289 } 19290 } 19291 } 19292 19293 // Insanity Warning: scope depth-first traversal 19294 // yes, this code is a bit crazy, but it works and we have tests to prove it! 19295 // this piece should be kept in sync with the traversal in $broadcast 19296 // (though it differs due to having the extra check for $$suspended and does not 19297 // check $$listenerCount) 19298 if (!(next = ((!current.$$suspended && current.$$watchersCount && current.$$childHead) || 19299 (current !== target && current.$$nextSibling)))) { 19300 while (current !== target && !(next = current.$$nextSibling)) { 19301 current = current.$parent; 19302 } 19303 } 19304 } while ((current = next)); 19305 19306 // `break traverseScopesLoop;` takes us to here 19307 19308 if ((dirty || asyncQueue.length) && !(ttl--)) { 19309 clearPhase(); 19310 throw $rootScopeMinErr('infdig', 19311 '{0} $digest() iterations reached. Aborting!\n' + 19312 'Watchers fired in the last 5 iterations: {1}', 19313 TTL, watchLog); 19314 } 19315 19316 } while (dirty || asyncQueue.length); 19317 19318 clearPhase(); 19319 19320 // postDigestQueuePosition isn't local here because this loop can be reentered recursively. 19321 while (postDigestQueuePosition < postDigestQueue.length) { 19322 try { 19323 postDigestQueue[postDigestQueuePosition++](); 19324 } catch (e) { 19325 $exceptionHandler(e); 19326 } 19327 } 19328 postDigestQueue.length = postDigestQueuePosition = 0; 19329 19330 // Check for changes to browser url that happened during the $digest 19331 // (for which no event is fired; e.g. via `history.pushState()`) 19332 $browser.$$checkUrlChange(); 19333 }, 19334 19335 /** 19336 * @ngdoc method 19337 * @name $rootScope.Scope#$suspend 19338 * @kind function 19339 * 19340 * @description 19341 * Suspend watchers of this scope subtree so that they will not be invoked during digest. 19342 * 19343 * This can be used to optimize your application when you know that running those watchers 19344 * is redundant. 19345 * 19346 * **Warning** 19347 * 19348 * Suspending scopes from the digest cycle can have unwanted and difficult to debug results. 19349 * Only use this approach if you are confident that you know what you are doing and have 19350 * ample tests to ensure that bindings get updated as you expect. 19351 * 19352 * Some of the things to consider are: 19353 * 19354 * * Any external event on a directive/component will not trigger a digest while the hosting 19355 * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`. 19356 * * Transcluded content exists on a scope that inherits from outside a directive but exists 19357 * as a child of the directive's containing scope. If the containing scope is suspended the 19358 * transcluded scope will also be suspended, even if the scope from which the transcluded 19359 * scope inherits is not suspended. 19360 * * Multiple directives trying to manage the suspended status of a scope can confuse each other: 19361 * * A call to `$suspend()` on an already suspended scope is a no-op. 19362 * * A call to `$resume()` on a non-suspended scope is a no-op. 19363 * * If two directives suspend a scope, then one of them resumes the scope, the scope will no 19364 * longer be suspended. This could result in the other directive believing a scope to be 19365 * suspended when it is not. 19366 * * If a parent scope is suspended then all its descendants will be also excluded from future 19367 * digests whether or not they have been suspended themselves. Note that this also applies to 19368 * isolate child scopes. 19369 * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers 19370 * for that scope and its descendants. When digesting we only check whether the current scope is 19371 * locally suspended, rather than checking whether it has a suspended ancestor. 19372 * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be 19373 * included in future digests until all its ancestors have been resumed. 19374 * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()` 19375 * against the `$rootScope` and so will still trigger a global digest even if the promise was 19376 * initiated by a component that lives on a suspended scope. 19377 */ 19378 $suspend: function() { 19379 this.$$suspended = true; 19380 }, 19381 19382 /** 19383 * @ngdoc method 19384 * @name $rootScope.Scope#$isSuspended 19385 * @kind function 19386 * 19387 * @description 19388 * Call this method to determine if this scope has been explicitly suspended. It will not 19389 * tell you whether an ancestor has been suspended. 19390 * To determine if this scope will be excluded from a digest triggered at the $rootScope, 19391 * for example, you must check all its ancestors: 19392 * 19393 * ``` 19394 * function isExcludedFromDigest(scope) { 19395 * while(scope) { 19396 * if (scope.$isSuspended()) return true; 19397 * scope = scope.$parent; 19398 * } 19399 * return false; 19400 * ``` 19401 * 19402 * Be aware that a scope may not be included in digests if it has a suspended ancestor, 19403 * even if `$isSuspended()` returns false. 19404 * 19405 * @returns true if the current scope has been suspended. 19406 */ 19407 $isSuspended: function() { 19408 return this.$$suspended; 19409 }, 19410 19411 /** 19412 * @ngdoc method 19413 * @name $rootScope.Scope#$resume 19414 * @kind function 19415 * 19416 * @description 19417 * Resume watchers of this scope subtree in case it was suspended. 19418 * 19419 * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach. 19420 */ 19421 $resume: function() { 19422 this.$$suspended = false; 19423 }, 19424 19425 /** 19426 * @ngdoc event 19427 * @name $rootScope.Scope#$destroy 19428 * @eventType broadcast on scope being destroyed 19429 * 19430 * @description 19431 * Broadcasted when a scope and its children are being destroyed. 19432 * 19433 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 19434 * clean up DOM bindings before an element is removed from the DOM. 19435 */ 19436 19437 /** 19438 * @ngdoc method 19439 * @name $rootScope.Scope#$destroy 19440 * @kind function 19441 * 19442 * @description 19443 * Removes the current scope (and all of its children) from the parent scope. Removal implies 19444 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 19445 * propagate to the current scope and its children. Removal also implies that the current 19446 * scope is eligible for garbage collection. 19447 * 19448 * The `$destroy()` is usually used by directives such as 19449 * {@link ng.directive:ngRepeat ngRepeat} for managing the 19450 * unrolling of the loop. 19451 * 19452 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 19453 * Application code can register a `$destroy` event handler that will give it a chance to 19454 * perform any necessary cleanup. 19455 * 19456 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 19457 * clean up DOM bindings before an element is removed from the DOM. 19458 */ 19459 $destroy: function() { 19460 // We can't destroy a scope that has been already destroyed. 19461 if (this.$$destroyed) return; 19462 var parent = this.$parent; 19463 19464 this.$broadcast('$destroy'); 19465 this.$$destroyed = true; 19466 19467 if (this === $rootScope) { 19468 //Remove handlers attached to window when $rootScope is removed 19469 $browser.$$applicationDestroyed(); 19470 } 19471 19472 incrementWatchersCount(this, -this.$$watchersCount); 19473 for (var eventName in this.$$listenerCount) { 19474 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 19475 } 19476 19477 // sever all the references to parent scopes (after this cleanup, the current scope should 19478 // not be retained by any of our references and should be eligible for garbage collection) 19479 if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling; 19480 if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling; 19481 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 19482 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 19483 19484 // Disable listeners, watchers and apply/digest methods 19485 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 19486 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 19487 this.$$listeners = {}; 19488 19489 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 19490 this.$$nextSibling = null; 19491 cleanUpScope(this); 19492 }, 19493 19494 /** 19495 * @ngdoc method 19496 * @name $rootScope.Scope#$eval 19497 * @kind function 19498 * 19499 * @description 19500 * Executes the `expression` on the current scope and returns the result. Any exceptions in 19501 * the expression are propagated (uncaught). This is useful when evaluating AngularJS 19502 * expressions. 19503 * 19504 * @example 19505 * ```js 19506 var scope = ng.$rootScope.Scope(); 19507 scope.a = 1; 19508 scope.b = 2; 19509 19510 expect(scope.$eval('a+b')).toEqual(3); 19511 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 19512 * ``` 19513 * 19514 * @param {(string|function())=} expression An AngularJS expression to be executed. 19515 * 19516 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19517 * - `function(scope)`: execute the function with the current `scope` parameter. 19518 * 19519 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 19520 * @returns {*} The result of evaluating the expression. 19521 */ 19522 $eval: function(expr, locals) { 19523 return $parse(expr)(this, locals); 19524 }, 19525 19526 /** 19527 * @ngdoc method 19528 * @name $rootScope.Scope#$evalAsync 19529 * @kind function 19530 * 19531 * @description 19532 * Executes the expression on the current scope at a later point in time. 19533 * 19534 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 19535 * that: 19536 * 19537 * - it will execute after the function that scheduled the evaluation (preferably before DOM 19538 * rendering). 19539 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 19540 * `expression` execution. 19541 * 19542 * Any exceptions from the execution of the expression are forwarded to the 19543 * {@link ng.$exceptionHandler $exceptionHandler} service. 19544 * 19545 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 19546 * will be scheduled. However, it is encouraged to always call code that changes the model 19547 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 19548 * 19549 * @param {(string|function())=} expression An AngularJS expression to be executed. 19550 * 19551 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19552 * - `function(scope)`: execute the function with the current `scope` parameter. 19553 * 19554 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 19555 */ 19556 $evalAsync: function(expr, locals) { 19557 // if we are outside of an $digest loop and this is the first time we are scheduling async 19558 // task also schedule async auto-flush 19559 if (!$rootScope.$$phase && !asyncQueue.length) { 19560 $browser.defer(function() { 19561 if (asyncQueue.length) { 19562 $rootScope.$digest(); 19563 } 19564 }, null, '$evalAsync'); 19565 } 19566 19567 asyncQueue.push({scope: this, fn: $parse(expr), locals: locals}); 19568 }, 19569 19570 $$postDigest: function(fn) { 19571 postDigestQueue.push(fn); 19572 }, 19573 19574 /** 19575 * @ngdoc method 19576 * @name $rootScope.Scope#$apply 19577 * @kind function 19578 * 19579 * @description 19580 * `$apply()` is used to execute an expression in AngularJS from outside of the AngularJS 19581 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 19582 * Because we are calling into the AngularJS framework we need to perform proper scope life 19583 * cycle of {@link ng.$exceptionHandler exception handling}, 19584 * {@link ng.$rootScope.Scope#$digest executing watches}. 19585 * 19586 * **Life cycle: Pseudo-Code of `$apply()`** 19587 * 19588 * ```js 19589 function $apply(expr) { 19590 try { 19591 return $eval(expr); 19592 } catch (e) { 19593 $exceptionHandler(e); 19594 } finally { 19595 $root.$digest(); 19596 } 19597 } 19598 * ``` 19599 * 19600 * 19601 * Scope's `$apply()` method transitions through the following stages: 19602 * 19603 * 1. The {@link guide/expression expression} is executed using the 19604 * {@link ng.$rootScope.Scope#$eval $eval()} method. 19605 * 2. Any exceptions from the execution of the expression are forwarded to the 19606 * {@link ng.$exceptionHandler $exceptionHandler} service. 19607 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 19608 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 19609 * 19610 * 19611 * @param {(string|function())=} exp An AngularJS expression to be executed. 19612 * 19613 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19614 * - `function(scope)`: execute the function with current `scope` parameter. 19615 * 19616 * @returns {*} The result of evaluating the expression. 19617 */ 19618 $apply: function(expr) { 19619 try { 19620 beginPhase('$apply'); 19621 try { 19622 return this.$eval(expr); 19623 } finally { 19624 clearPhase(); 19625 } 19626 } catch (e) { 19627 $exceptionHandler(e); 19628 } finally { 19629 try { 19630 $rootScope.$digest(); 19631 } catch (e) { 19632 $exceptionHandler(e); 19633 // eslint-disable-next-line no-unsafe-finally 19634 throw e; 19635 } 19636 } 19637 }, 19638 19639 /** 19640 * @ngdoc method 19641 * @name $rootScope.Scope#$applyAsync 19642 * @kind function 19643 * 19644 * @description 19645 * Schedule the invocation of $apply to occur at a later time. The actual time difference 19646 * varies across browsers, but is typically around ~10 milliseconds. 19647 * 19648 * This can be used to queue up multiple expressions which need to be evaluated in the same 19649 * digest. 19650 * 19651 * @param {(string|function())=} exp An AngularJS expression to be executed. 19652 * 19653 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19654 * - `function(scope)`: execute the function with current `scope` parameter. 19655 */ 19656 $applyAsync: function(expr) { 19657 var scope = this; 19658 if (expr) { 19659 applyAsyncQueue.push($applyAsyncExpression); 19660 } 19661 expr = $parse(expr); 19662 scheduleApplyAsync(); 19663 19664 function $applyAsyncExpression() { 19665 scope.$eval(expr); 19666 } 19667 }, 19668 19669 /** 19670 * @ngdoc method 19671 * @name $rootScope.Scope#$on 19672 * @kind function 19673 * 19674 * @description 19675 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 19676 * discussion of event life cycle. 19677 * 19678 * The event listener function format is: `function(event, args...)`. The `event` object 19679 * passed into the listener has the following attributes: 19680 * 19681 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 19682 * `$broadcast`-ed. 19683 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 19684 * event propagates through the scope hierarchy, this property is set to null. 19685 * - `name` - `{string}`: name of the event. 19686 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 19687 * further event propagation (available only for events that were `$emit`-ed). 19688 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 19689 * to true. 19690 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 19691 * 19692 * @param {string} name Event name to listen on. 19693 * @param {function(event, ...args)} listener Function to call when the event is emitted. 19694 * @returns {function()} Returns a deregistration function for this listener. 19695 */ 19696 $on: function(name, listener) { 19697 var namedListeners = this.$$listeners[name]; 19698 if (!namedListeners) { 19699 this.$$listeners[name] = namedListeners = []; 19700 } 19701 namedListeners.push(listener); 19702 19703 var current = this; 19704 do { 19705 if (!current.$$listenerCount[name]) { 19706 current.$$listenerCount[name] = 0; 19707 } 19708 current.$$listenerCount[name]++; 19709 } while ((current = current.$parent)); 19710 19711 var self = this; 19712 return function() { 19713 var indexOfListener = namedListeners.indexOf(listener); 19714 if (indexOfListener !== -1) { 19715 // Use delete in the hope of the browser deallocating the memory for the array entry, 19716 // while not shifting the array indexes of other listeners. 19717 // See issue https://github.com/angular/angular.js/issues/16135 19718 delete namedListeners[indexOfListener]; 19719 decrementListenerCount(self, 1, name); 19720 } 19721 }; 19722 }, 19723 19724 19725 /** 19726 * @ngdoc method 19727 * @name $rootScope.Scope#$emit 19728 * @kind function 19729 * 19730 * @description 19731 * Dispatches an event `name` upwards through the scope hierarchy notifying the 19732 * registered {@link ng.$rootScope.Scope#$on} listeners. 19733 * 19734 * The event life cycle starts at the scope on which `$emit` was called. All 19735 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 19736 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 19737 * registered listeners along the way. The event will stop propagating if one of the listeners 19738 * cancels it. 19739 * 19740 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 19741 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 19742 * 19743 * @param {string} name Event name to emit. 19744 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 19745 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 19746 */ 19747 $emit: function(name, args) { 19748 var empty = [], 19749 namedListeners, 19750 scope = this, 19751 stopPropagation = false, 19752 event = { 19753 name: name, 19754 targetScope: scope, 19755 stopPropagation: function() {stopPropagation = true;}, 19756 preventDefault: function() { 19757 event.defaultPrevented = true; 19758 }, 19759 defaultPrevented: false 19760 }, 19761 listenerArgs = concat([event], arguments, 1), 19762 i, length; 19763 19764 do { 19765 namedListeners = scope.$$listeners[name] || empty; 19766 event.currentScope = scope; 19767 for (i = 0, length = namedListeners.length; i < length; i++) { 19768 19769 // if listeners were deregistered, defragment the array 19770 if (!namedListeners[i]) { 19771 namedListeners.splice(i, 1); 19772 i--; 19773 length--; 19774 continue; 19775 } 19776 try { 19777 //allow all listeners attached to the current scope to run 19778 namedListeners[i].apply(null, listenerArgs); 19779 } catch (e) { 19780 $exceptionHandler(e); 19781 } 19782 } 19783 //if any listener on the current scope stops propagation, prevent bubbling 19784 if (stopPropagation) { 19785 break; 19786 } 19787 //traverse upwards 19788 scope = scope.$parent; 19789 } while (scope); 19790 19791 event.currentScope = null; 19792 19793 return event; 19794 }, 19795 19796 19797 /** 19798 * @ngdoc method 19799 * @name $rootScope.Scope#$broadcast 19800 * @kind function 19801 * 19802 * @description 19803 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 19804 * registered {@link ng.$rootScope.Scope#$on} listeners. 19805 * 19806 * The event life cycle starts at the scope on which `$broadcast` was called. All 19807 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 19808 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 19809 * scope and calls all registered listeners along the way. The event cannot be canceled. 19810 * 19811 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 19812 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 19813 * 19814 * @param {string} name Event name to broadcast. 19815 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 19816 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 19817 */ 19818 $broadcast: function(name, args) { 19819 var target = this, 19820 current = target, 19821 next = target, 19822 event = { 19823 name: name, 19824 targetScope: target, 19825 preventDefault: function() { 19826 event.defaultPrevented = true; 19827 }, 19828 defaultPrevented: false 19829 }; 19830 19831 if (!target.$$listenerCount[name]) return event; 19832 19833 var listenerArgs = concat([event], arguments, 1), 19834 listeners, i, length; 19835 19836 //down while you can, then up and next sibling or up and next sibling until back at root 19837 while ((current = next)) { 19838 event.currentScope = current; 19839 listeners = current.$$listeners[name] || []; 19840 for (i = 0, length = listeners.length; i < length; i++) { 19841 // if listeners were deregistered, defragment the array 19842 if (!listeners[i]) { 19843 listeners.splice(i, 1); 19844 i--; 19845 length--; 19846 continue; 19847 } 19848 19849 try { 19850 listeners[i].apply(null, listenerArgs); 19851 } catch (e) { 19852 $exceptionHandler(e); 19853 } 19854 } 19855 19856 // Insanity Warning: scope depth-first traversal 19857 // yes, this code is a bit crazy, but it works and we have tests to prove it! 19858 // this piece should be kept in sync with the traversal in $digest 19859 // (though it differs due to having the extra check for $$listenerCount and 19860 // does not check $$suspended) 19861 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 19862 (current !== target && current.$$nextSibling)))) { 19863 while (current !== target && !(next = current.$$nextSibling)) { 19864 current = current.$parent; 19865 } 19866 } 19867 } 19868 19869 event.currentScope = null; 19870 return event; 19871 } 19872 }; 19873 19874 var $rootScope = new Scope(); 19875 19876 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 19877 var asyncQueue = $rootScope.$$asyncQueue = []; 19878 var postDigestQueue = $rootScope.$$postDigestQueue = []; 19879 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 19880 19881 var postDigestQueuePosition = 0; 19882 19883 return $rootScope; 19884 19885 19886 function beginPhase(phase) { 19887 if ($rootScope.$$phase) { 19888 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 19889 } 19890 19891 $rootScope.$$phase = phase; 19892 } 19893 19894 function clearPhase() { 19895 $rootScope.$$phase = null; 19896 } 19897 19898 function incrementWatchersCount(current, count) { 19899 do { 19900 current.$$watchersCount += count; 19901 } while ((current = current.$parent)); 19902 } 19903 19904 function decrementListenerCount(current, count, name) { 19905 do { 19906 current.$$listenerCount[name] -= count; 19907 19908 if (current.$$listenerCount[name] === 0) { 19909 delete current.$$listenerCount[name]; 19910 } 19911 } while ((current = current.$parent)); 19912 } 19913 19914 /** 19915 * function used as an initial value for watchers. 19916 * because it's unique we can easily tell it apart from other values 19917 */ 19918 function initWatchVal() {} 19919 19920 function flushApplyAsync() { 19921 while (applyAsyncQueue.length) { 19922 try { 19923 applyAsyncQueue.shift()(); 19924 } catch (e) { 19925 $exceptionHandler(e); 19926 } 19927 } 19928 applyAsyncId = null; 19929 } 19930 19931 function scheduleApplyAsync() { 19932 if (applyAsyncId === null) { 19933 applyAsyncId = $browser.defer(function() { 19934 $rootScope.$apply(flushApplyAsync); 19935 }, null, '$applyAsync'); 19936 } 19937 } 19938 }]; 19939} 19940 19941/** 19942 * @ngdoc service 19943 * @name $rootElement 19944 * 19945 * @description 19946 * The root element of AngularJS application. This is either the element where {@link 19947 * ng.directive:ngApp ngApp} was declared or the element passed into 19948 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 19949 * location where the application's {@link auto.$injector $injector} service gets 19950 * published, and can be retrieved using `$rootElement.injector()`. 19951 */ 19952 19953 19954// the implementation is in angular.bootstrap 19955 19956/** 19957 * @this 19958 * @description 19959 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 19960 */ 19961function $$SanitizeUriProvider() { 19962 19963 var aHrefSanitizationTrustedUrlList = /^\s*(https?|s?ftp|mailto|tel|file):/, 19964 imgSrcSanitizationTrustedUrlList = /^\s*((https?|ftp|file|blob):|data:image\/)/; 19965 19966 /** 19967 * @description 19968 * Retrieves or overrides the default regular expression that is used for determining trusted safe 19969 * urls during a[href] sanitization. 19970 * 19971 * The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links. 19972 * 19973 * Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring 19974 * the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)` 19975 * which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL. 19976 * 19977 * If the URL matches the `aHrefSanitizationTrustedUrlList` regular expression, it is returned unchanged. 19978 * 19979 * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written 19980 * to the DOM it is inactive and potentially malicious code will not be executed. 19981 * 19982 * @param {RegExp=} regexp New regexp to trust urls with. 19983 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 19984 * chaining otherwise. 19985 */ 19986 this.aHrefSanitizationTrustedUrlList = function(regexp) { 19987 if (isDefined(regexp)) { 19988 aHrefSanitizationTrustedUrlList = regexp; 19989 return this; 19990 } 19991 return aHrefSanitizationTrustedUrlList; 19992 }; 19993 19994 19995 /** 19996 * @description 19997 * Retrieves or overrides the default regular expression that is used for determining trusted safe 19998 * urls during img[src] sanitization. 19999 * 20000 * The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links. 20001 * 20002 * Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring 20003 * the $sce.MEDIA_URL security context. When interpolation occurs a call is made to 20004 * `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize 20005 * the potentially malicious URL. 20006 * 20007 * If the URL matches the `imgSrcSanitizationTrustedUrlList` regular expression, it is returned 20008 * unchanged. 20009 * 20010 * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written 20011 * to the DOM it is inactive and potentially malicious code will not be executed. 20012 * 20013 * @param {RegExp=} regexp New regexp to trust urls with. 20014 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 20015 * chaining otherwise. 20016 */ 20017 this.imgSrcSanitizationTrustedUrlList = function(regexp) { 20018 if (isDefined(regexp)) { 20019 imgSrcSanitizationTrustedUrlList = regexp; 20020 return this; 20021 } 20022 return imgSrcSanitizationTrustedUrlList; 20023 }; 20024 20025 this.$get = function() { 20026 return function sanitizeUri(uri, isMediaUrl) { 20027 // if (!uri) return uri; 20028 var regex = isMediaUrl ? imgSrcSanitizationTrustedUrlList : aHrefSanitizationTrustedUrlList; 20029 var normalizedVal = urlResolve(uri && uri.trim()).href; 20030 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 20031 return 'unsafe:' + normalizedVal; 20032 } 20033 return uri; 20034 }; 20035 }; 20036} 20037 20038/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20039 * Any commits to this file should be reviewed with security in mind. * 20040 * Changes to this file can potentially create security vulnerabilities. * 20041 * An approval from 2 Core members with history of modifying * 20042 * this file is required. * 20043 * * 20044 * Does the change somehow allow for arbitrary javascript to be executed? * 20045 * Or allows for someone to change the prototype of built-in objects? * 20046 * Or gives undesired access to variables likes document or window? * 20047 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 20048 20049/* exported $SceProvider, $SceDelegateProvider */ 20050 20051var $sceMinErr = minErr('$sce'); 20052 20053var SCE_CONTEXTS = { 20054 // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding). 20055 HTML: 'html', 20056 20057 // Style statements or stylesheets. Currently unused in AngularJS. 20058 CSS: 'css', 20059 20060 // An URL used in a context where it refers to the source of media, which are not expected to be run 20061 // as scripts, such as an image, audio, video, etc. 20062 MEDIA_URL: 'mediaUrl', 20063 20064 // An URL used in a context where it does not refer to a resource that loads code. 20065 // A value that can be trusted as a URL can also trusted as a MEDIA_URL. 20066 URL: 'url', 20067 20068 // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as 20069 // code. (e.g. ng-include, script src binding, templateUrl) 20070 // A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL. 20071 RESOURCE_URL: 'resourceUrl', 20072 20073 // Script. Currently unused in AngularJS. 20074 JS: 'js' 20075}; 20076 20077// Helper functions follow. 20078 20079var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g; 20080 20081function snakeToCamel(name) { 20082 return name 20083 .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace); 20084} 20085 20086function adjustMatcher(matcher) { 20087 if (matcher === 'self') { 20088 return matcher; 20089 } else if (isString(matcher)) { 20090 // Strings match exactly except for 2 wildcards - '*' and '**'. 20091 // '*' matches any character except those from the set ':/.?&'. 20092 // '**' matches any character (like .* in a RegExp). 20093 // More than 2 *'s raises an error as it's ill defined. 20094 if (matcher.indexOf('***') > -1) { 20095 throw $sceMinErr('iwcard', 20096 'Illegal sequence *** in string matcher. String: {0}', matcher); 20097 } 20098 matcher = escapeForRegexp(matcher). 20099 replace(/\\\*\\\*/g, '.*'). 20100 replace(/\\\*/g, '[^:/.?&;]*'); 20101 return new RegExp('^' + matcher + '$'); 20102 } else if (isRegExp(matcher)) { 20103 // The only other type of matcher allowed is a Regexp. 20104 // Match entire URL / disallow partial matches. 20105 // Flags are reset (i.e. no global, ignoreCase or multiline) 20106 return new RegExp('^' + matcher.source + '$'); 20107 } else { 20108 throw $sceMinErr('imatcher', 20109 'Matchers may only be "self", string patterns or RegExp objects'); 20110 } 20111} 20112 20113 20114function adjustMatchers(matchers) { 20115 var adjustedMatchers = []; 20116 if (isDefined(matchers)) { 20117 forEach(matchers, function(matcher) { 20118 adjustedMatchers.push(adjustMatcher(matcher)); 20119 }); 20120 } 20121 return adjustedMatchers; 20122} 20123 20124 20125/** 20126 * @ngdoc service 20127 * @name $sceDelegate 20128 * @kind function 20129 * 20130 * @description 20131 * 20132 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 20133 * Contextual Escaping (SCE)} services to AngularJS. 20134 * 20135 * For an overview of this service and the functionnality it provides in AngularJS, see the main 20136 * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how 20137 * SCE works in their application, which shouldn't be needed in most cases. 20138 * 20139 * <div class="alert alert-danger"> 20140 * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or 20141 * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners, 20142 * changes to this service will also influence users, so be extra careful and document your changes. 20143 * </div> 20144 * 20145 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 20146 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 20147 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 20148 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 20149 * work because `$sce` delegates to `$sceDelegate` for these operations. 20150 * 20151 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 20152 * 20153 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 20154 * can override it completely to change the behavior of `$sce`, the common case would 20155 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 20156 * your own trusted and banned resource lists for trusting URLs used for loading AngularJS resources 20157 * such as templates. Refer {@link ng.$sceDelegateProvider#trustedResourceUrlList 20158 * $sceDelegateProvider.trustedResourceUrlList} and {@link 20159 * ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList} 20160 */ 20161 20162/** 20163 * @ngdoc provider 20164 * @name $sceDelegateProvider 20165 * @this 20166 * 20167 * @description 20168 * 20169 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 20170 * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}. 20171 * 20172 * The `$sceDelegateProvider` allows one to get/set the `trustedResourceUrlList` and 20173 * `bannedResourceUrlList` used to ensure that the URLs used for sourcing AngularJS templates and 20174 * other script-running URLs are safe (all places that use the `$sce.RESOURCE_URL` context). See 20175 * {@link ng.$sceDelegateProvider#trustedResourceUrlList 20176 * $sceDelegateProvider.trustedResourceUrlList} and 20177 * {@link ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList}, 20178 * 20179 * For the general details about this service in AngularJS, read the main page for {@link ng.$sce 20180 * Strict Contextual Escaping (SCE)}. 20181 * 20182 * **Example**: Consider the following case. <a name="example"></a> 20183 * 20184 * - your app is hosted at url `http://myapp.example.com/` 20185 * - but some of your templates are hosted on other domains you control such as 20186 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 20187 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 20188 * 20189 * Here is what a secure configuration for this scenario might look like: 20190 * 20191 * ``` 20192 * angular.module('myApp', []).config(function($sceDelegateProvider) { 20193 * $sceDelegateProvider.trustedResourceUrlList([ 20194 * // Allow same origin resource loads. 20195 * 'self', 20196 * // Allow loading from our assets domain. Notice the difference between * and **. 20197 * 'http://srv*.assets.example.com/**' 20198 * ]); 20199 * 20200 * // The banned resource URL list overrides the trusted resource URL list so the open redirect 20201 * // here is blocked. 20202 * $sceDelegateProvider.bannedResourceUrlList([ 20203 * 'http://myapp.example.com/clickThru**' 20204 * ]); 20205 * }); 20206 * ``` 20207 * Note that an empty trusted resource URL list will block every resource URL from being loaded, and will require 20208 * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates 20209 * requested by {@link ng.$templateRequest $templateRequest} that are present in 20210 * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism 20211 * to populate your templates in that cache at config time, then it is a good idea to remove 'self' 20212 * from the trusted resource URL lsit. This helps to mitigate the security impact of certain types 20213 * of issues, like for instance attacker-controlled `ng-includes`. 20214 */ 20215 20216function $SceDelegateProvider() { 20217 this.SCE_CONTEXTS = SCE_CONTEXTS; 20218 20219 // Resource URLs can also be trusted by policy. 20220 var trustedResourceUrlList = ['self'], 20221 bannedResourceUrlList = []; 20222 20223 /** 20224 * @ngdoc method 20225 * @name $sceDelegateProvider#trustedResourceUrlList 20226 * @kind function 20227 * 20228 * @param {Array=} trustedResourceUrlList When provided, replaces the trustedResourceUrlList with 20229 * the value provided. This must be an array or null. A snapshot of this array is used so 20230 * further changes to the array are ignored. 20231 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 20232 * allowed in this array. 20233 * 20234 * @return {Array} The currently set trusted resource URL array. 20235 * 20236 * @description 20237 * Sets/Gets the list trusted of resource URLs. 20238 * 20239 * The **default value** when no `trustedResourceUrlList` has been explicitly set is `['self']` 20240 * allowing only same origin resource requests. 20241 * 20242 * <div class="alert alert-warning"> 20243 * **Note:** the default `trustedResourceUrlList` of 'self' is not recommended if your app shares 20244 * its origin with other apps! It is a good idea to limit it to only your application's directory. 20245 * </div> 20246 */ 20247 this.trustedResourceUrlList = function(value) { 20248 if (arguments.length) { 20249 trustedResourceUrlList = adjustMatchers(value); 20250 } 20251 return trustedResourceUrlList; 20252 }; 20253 20254 /** 20255 * @ngdoc method 20256 * @name $sceDelegateProvider#resourceUrlWhitelist 20257 * @kind function 20258 * 20259 * @deprecated 20260 * sinceVersion="1.8.1" 20261 * 20262 * This method is deprecated. Use {@link $sceDelegateProvider#trustedResourceUrlList 20263 * trustedResourceUrlList} instead. 20264 */ 20265 Object.defineProperty(this, 'resourceUrlWhitelist', { 20266 get: function() { 20267 return this.trustedResourceUrlList; 20268 }, 20269 set: function(value) { 20270 this.trustedResourceUrlList = value; 20271 } 20272 }); 20273 20274 /** 20275 * @ngdoc method 20276 * @name $sceDelegateProvider#bannedResourceUrlList 20277 * @kind function 20278 * 20279 * @param {Array=} bannedResourceUrlList When provided, replaces the `bannedResourceUrlList` with 20280 * the value provided. This must be an array or null. A snapshot of this array is used so 20281 * further changes to the array are ignored.</p><p> 20282 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 20283 * allowed in this array.</p><p> 20284 * The typical usage for the `bannedResourceUrlList` is to **block 20285 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 20286 * these would otherwise be trusted but actually return content from the redirected domain. 20287 * </p><p> 20288 * Finally, **the banned resource URL list overrides the trusted resource URL list** and has 20289 * the final say. 20290 * 20291 * @return {Array} The currently set `bannedResourceUrlList` array. 20292 * 20293 * @description 20294 * Sets/Gets the `bannedResourceUrlList` of trusted resource URLs. 20295 * 20296 * The **default value** when no trusted resource URL list has been explicitly set is the empty 20297 * array (i.e. there is no `bannedResourceUrlList`.) 20298 */ 20299 this.bannedResourceUrlList = function(value) { 20300 if (arguments.length) { 20301 bannedResourceUrlList = adjustMatchers(value); 20302 } 20303 return bannedResourceUrlList; 20304 }; 20305 20306 /** 20307 * @ngdoc method 20308 * @name $sceDelegateProvider#resourceUrlBlacklist 20309 * @kind function 20310 * 20311 * @deprecated 20312 * sinceVersion="1.8.1" 20313 * 20314 * This method is deprecated. Use {@link $sceDelegateProvider#bannedResourceUrlList 20315 * bannedResourceUrlList} instead. 20316 */ 20317 Object.defineProperty(this, 'resourceUrlBlacklist', { 20318 get: function() { 20319 return this.bannedResourceUrlList; 20320 }, 20321 set: function(value) { 20322 this.bannedResourceUrlList = value; 20323 } 20324 }); 20325 20326 this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) { 20327 20328 var htmlSanitizer = function htmlSanitizer(html) { 20329 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 20330 }; 20331 20332 if ($injector.has('$sanitize')) { 20333 htmlSanitizer = $injector.get('$sanitize'); 20334 } 20335 20336 20337 function matchUrl(matcher, parsedUrl) { 20338 if (matcher === 'self') { 20339 return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl); 20340 } else { 20341 // definitely a regex. See adjustMatchers() 20342 return !!matcher.exec(parsedUrl.href); 20343 } 20344 } 20345 20346 function isResourceUrlAllowedByPolicy(url) { 20347 var parsedUrl = urlResolve(url.toString()); 20348 var i, n, allowed = false; 20349 // Ensure that at least one item from the trusted resource URL list allows this url. 20350 for (i = 0, n = trustedResourceUrlList.length; i < n; i++) { 20351 if (matchUrl(trustedResourceUrlList[i], parsedUrl)) { 20352 allowed = true; 20353 break; 20354 } 20355 } 20356 if (allowed) { 20357 // Ensure that no item from the banned resource URL list has blocked this url. 20358 for (i = 0, n = bannedResourceUrlList.length; i < n; i++) { 20359 if (matchUrl(bannedResourceUrlList[i], parsedUrl)) { 20360 allowed = false; 20361 break; 20362 } 20363 } 20364 } 20365 return allowed; 20366 } 20367 20368 function generateHolderType(Base) { 20369 var holderType = function TrustedValueHolderType(trustedValue) { 20370 this.$$unwrapTrustedValue = function() { 20371 return trustedValue; 20372 }; 20373 }; 20374 if (Base) { 20375 holderType.prototype = new Base(); 20376 } 20377 holderType.prototype.valueOf = function sceValueOf() { 20378 return this.$$unwrapTrustedValue(); 20379 }; 20380 holderType.prototype.toString = function sceToString() { 20381 return this.$$unwrapTrustedValue().toString(); 20382 }; 20383 return holderType; 20384 } 20385 20386 var trustedValueHolderBase = generateHolderType(), 20387 byType = {}; 20388 20389 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 20390 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 20391 byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase); 20392 byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]); 20393 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 20394 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 20395 20396 /** 20397 * @ngdoc method 20398 * @name $sceDelegate#trustAs 20399 * 20400 * @description 20401 * Returns a trusted representation of the parameter for the specified context. This trusted 20402 * object will later on be used as-is, without any security check, by bindings or directives 20403 * that require this security context. 20404 * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass 20405 * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as 20406 * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the 20407 * sanitizer loaded, passing the value itself will render all the HTML that does not pose a 20408 * security risk. 20409 * 20410 * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those 20411 * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual 20412 * escaping. 20413 * 20414 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`, 20415 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`. 20416 * 20417 * @param {*} value The value that should be considered trusted. 20418 * @return {*} A trusted representation of value, that can be used in the given context. 20419 */ 20420 function trustAs(type, trustedValue) { 20421 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 20422 if (!Constructor) { 20423 throw $sceMinErr('icontext', 20424 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 20425 type, trustedValue); 20426 } 20427 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 20428 return trustedValue; 20429 } 20430 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 20431 // mutable objects, we ensure here that the value passed in is actually a string. 20432 if (typeof trustedValue !== 'string') { 20433 throw $sceMinErr('itype', 20434 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 20435 type); 20436 } 20437 return new Constructor(trustedValue); 20438 } 20439 20440 /** 20441 * @ngdoc method 20442 * @name $sceDelegate#valueOf 20443 * 20444 * @description 20445 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 20446 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 20447 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 20448 * 20449 * If the passed parameter is not a value that had been returned by {@link 20450 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is. 20451 * 20452 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 20453 * call or anything else. 20454 * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 20455 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 20456 * `value` unchanged. 20457 */ 20458 function valueOf(maybeTrusted) { 20459 if (maybeTrusted instanceof trustedValueHolderBase) { 20460 return maybeTrusted.$$unwrapTrustedValue(); 20461 } else { 20462 return maybeTrusted; 20463 } 20464 } 20465 20466 /** 20467 * @ngdoc method 20468 * @name $sceDelegate#getTrusted 20469 * 20470 * @description 20471 * Given an object and a security context in which to assign it, returns a value that's safe to 20472 * use in this context, which was represented by the parameter. To do so, this function either 20473 * unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs 20474 * `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on 20475 * the context and sanitizer availablility. 20476 * 20477 * The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available 20478 * by default, and the third one relies on the `$sanitize` service (which may be loaded through 20479 * the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be 20480 * accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#trustedResourceUrlList 20481 * `$sceDelegateProvider.trustedResourceUrlList`} and {@link ng.$sceDelegateProvider#bannedResourceUrlList 20482 * `$sceDelegateProvider.bannedResourceUrlList`} accepts that resource. 20483 * 20484 * This function will throw if the safe type isn't appropriate for this context, or if the 20485 * value given cannot be accepted in the context (which might be caused by sanitization not 20486 * being available, or the value not being recognized as safe). 20487 * 20488 * <div class="alert alert-danger"> 20489 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 20490 * (XSS) vulnerability in your application. 20491 * </div> 20492 * 20493 * @param {string} type The context in which this value is to be used (such as `$sce.HTML`). 20494 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 20495 * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.) 20496 * @return {*} A version of the value that's safe to use in the given context, or throws an 20497 * exception if this is impossible. 20498 */ 20499 function getTrusted(type, maybeTrusted) { 20500 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 20501 return maybeTrusted; 20502 } 20503 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 20504 // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return 20505 // as-is. 20506 if (constructor && maybeTrusted instanceof constructor) { 20507 return maybeTrusted.$$unwrapTrustedValue(); 20508 } 20509 20510 // If maybeTrusted is a trusted class instance but not of the correct trusted type 20511 // then unwrap it and allow it to pass through to the rest of the checks 20512 if (isFunction(maybeTrusted.$$unwrapTrustedValue)) { 20513 maybeTrusted = maybeTrusted.$$unwrapTrustedValue(); 20514 } 20515 20516 // If we get here, then we will either sanitize the value or throw an exception. 20517 if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) { 20518 // we attempt to sanitize non-resource URLs 20519 return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL); 20520 } else if (type === SCE_CONTEXTS.RESOURCE_URL) { 20521 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 20522 return maybeTrusted; 20523 } else { 20524 throw $sceMinErr('insecurl', 20525 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 20526 maybeTrusted.toString()); 20527 } 20528 } else if (type === SCE_CONTEXTS.HTML) { 20529 // htmlSanitizer throws its own error when no sanitizer is available. 20530 return htmlSanitizer(maybeTrusted); 20531 } 20532 // Default error when the $sce service has no way to make the input safe. 20533 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 20534 } 20535 20536 return { trustAs: trustAs, 20537 getTrusted: getTrusted, 20538 valueOf: valueOf }; 20539 }]; 20540} 20541 20542 20543/** 20544 * @ngdoc provider 20545 * @name $sceProvider 20546 * @this 20547 * 20548 * @description 20549 * 20550 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 20551 * - enable/disable Strict Contextual Escaping (SCE) in a module 20552 * - override the default implementation with a custom delegate 20553 * 20554 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 20555 */ 20556 20557/** 20558 * @ngdoc service 20559 * @name $sce 20560 * @kind function 20561 * 20562 * @description 20563 * 20564 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 20565 * 20566 * ## Strict Contextual Escaping 20567 * 20568 * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render 20569 * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and 20570 * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 20571 * 20572 * ### Overview 20573 * 20574 * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in 20575 * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically 20576 * run security checks on them (sanitizations, trusted URL resource, depending on context), or throw 20577 * when it cannot guarantee the security of the result. That behavior depends strongly on contexts: 20578 * HTML can be sanitized, but template URLs cannot, for instance. 20579 * 20580 * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML: 20581 * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it 20582 * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and 20583 * render the input as-is, you will need to mark it as trusted for that context before attempting 20584 * to bind it. 20585 * 20586 * As of version 1.2, AngularJS ships with SCE enabled by default. 20587 * 20588 * ### In practice 20589 * 20590 * Here's an example of a binding in a privileged context: 20591 * 20592 * ``` 20593 * <input ng-model="userHtml" aria-label="User input"> 20594 * <div ng-bind-html="userHtml"></div> 20595 * ``` 20596 * 20597 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 20598 * disabled, this application allows the user to render arbitrary HTML into the DIV, which would 20599 * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog 20600 * articles, etc. via bindings. (HTML is just one example of a context where rendering user 20601 * controlled input creates security vulnerabilities.) 20602 * 20603 * For the case of HTML, you might use a library, either on the client side, or on the server side, 20604 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 20605 * 20606 * How would you ensure that every place that used these types of bindings was bound to a value that 20607 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 20608 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 20609 * properties/fields and forgot to update the binding to the sanitized value? 20610 * 20611 * To be secure by default, AngularJS makes sure bindings go through that sanitization, or 20612 * any similar validation process, unless there's a good reason to trust the given value in this 20613 * context. That trust is formalized with a function call. This means that as a developer, you 20614 * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues, 20615 * you just need to ensure the values you mark as trusted indeed are safe - because they were 20616 * received from your server, sanitized by your library, etc. You can organize your codebase to 20617 * help with this - perhaps allowing only the files in a specific directory to do this. 20618 * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then 20619 * becomes a more manageable task. 20620 * 20621 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 20622 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 20623 * build the trusted versions of your values. 20624 * 20625 * ### How does it work? 20626 * 20627 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 20628 * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as 20629 * a way to enforce the required security context in your data sink. Directives use {@link 20630 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs 20631 * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also, 20632 * when binding without directives, AngularJS will understand the context of your bindings 20633 * automatically. 20634 * 20635 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 20636 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 20637 * simplified): 20638 * 20639 * ``` 20640 * var ngBindHtmlDirective = ['$sce', function($sce) { 20641 * return function(scope, element, attr) { 20642 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 20643 * element.html(value || ''); 20644 * }); 20645 * }; 20646 * }]; 20647 * ``` 20648 * 20649 * ### Impact on loading templates 20650 * 20651 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 20652 * `templateUrl`'s specified by {@link guide/directive directives}. 20653 * 20654 * By default, AngularJS only loads templates from the same domain and protocol as the application 20655 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 20656 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 20657 * protocols, you may either add them to the {@link ng.$sceDelegateProvider#trustedResourceUrlList 20658 * trustedResourceUrlList} or {@link ng.$sce#trustAsResourceUrl wrap them} into trusted values. 20659 * 20660 * *Please note*: 20661 * The browser's 20662 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 20663 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 20664 * policy apply in addition to this and may further restrict whether the template is successfully 20665 * loaded. This means that without the right CORS policy, loading templates from a different domain 20666 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 20667 * browsers. 20668 * 20669 * ### This feels like too much overhead 20670 * 20671 * It's important to remember that SCE only applies to interpolation expressions. 20672 * 20673 * If your expressions are constant literals, they're automatically trusted and you don't need to 20674 * call `$sce.trustAs` on them (e.g. 20675 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works (remember to include the 20676 * `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available 20677 * when binding untrusted values to `$sce.HTML` context. 20678 * AngularJS provides an implementation in `angular-sanitize.js`, and if you 20679 * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in 20680 * your application. 20681 * 20682 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 20683 * templates in `ng-include` from your application's domain without having to even know about SCE. 20684 * It blocks loading templates from other domains or loading templates over http from an https 20685 * served document. You can change these by setting your own custom {@link 20686 * ng.$sceDelegateProvider#trustedResourceUrlList trusted resource URL list} and {@link 20687 * ng.$sceDelegateProvider#bannedResourceUrlList banned resource URL list} for matching such URLs. 20688 * 20689 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 20690 * application that's secure and can be audited to verify that with much more ease than bolting 20691 * security onto an application later. 20692 * 20693 * <a name="contexts"></a> 20694 * ### What trusted context types are supported? 20695 * 20696 * | Context | Notes | 20697 * |---------------------|----------------| 20698 * | `$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} module is present this will sanitize the value instead of throwing an error. | 20699 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 20700 * | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. | 20701 * | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.| 20702 * | `$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` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. <br><br> The {@link $sceDelegateProvider#trustedResourceUrlList $sceDelegateProvider#trustedResourceUrlList()} and {@link $sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider#bannedResourceUrlList()} can be used to restrict trusted origins for `RESOURCE_URL` | 20703 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | 20704 * 20705 * 20706 * <div class="alert alert-warning"> 20707 * Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their 20708 * interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}. 20709 * 20710 * **As of 1.7.0, this is no longer the case.** 20711 * 20712 * Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL` 20713 * (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate` 20714 * service evaluates the expressions. 20715 * </div> 20716 * 20717 * There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs` 20718 * functions aren't useful yet. This might evolve. 20719 * 20720 * ### Format of items in {@link ng.$sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList}/{@link ng.$sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList} <a name="resourceUrlPatternItem"></a> 20721 * 20722 * Each element in these arrays must be one of the following: 20723 * 20724 * - **'self'** 20725 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 20726 * domain** as the application document using the **same protocol**. 20727 * - **String** (except the special value `'self'`) 20728 * - The string is matched against the full *normalized / absolute URL* of the resource 20729 * being tested (substring matches are not good enough.) 20730 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 20731 * match themselves. 20732 * - `*`: matches zero or more occurrences of any character other than one of the following 6 20733 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 20734 * for matching resource URL lists. 20735 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 20736 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 20737 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 20738 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 20739 * http://foo.example.com/templates/**). 20740 * - **RegExp** (*see caveat below*) 20741 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 20742 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 20743 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 20744 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 20745 * small number of cases. A `.` character in the regex used when matching the scheme or a 20746 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 20747 * is highly recommended to use the string patterns and only fall back to regular expressions 20748 * as a last resort. 20749 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 20750 * matched against the **entire** *normalized / absolute URL* of the resource being tested 20751 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 20752 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 20753 * - If you are generating your JavaScript from some other templating engine (not 20754 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 20755 * remember to escape your regular expression (and be aware that you might need more than 20756 * one level of escaping depending on your templating engine and the way you interpolated 20757 * the value.) Do make use of your platform's escaping mechanism as it might be good 20758 * enough before coding your own. E.g. Ruby has 20759 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 20760 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 20761 * Javascript lacks a similar built in function for escaping. Take a look at Google 20762 * Closure library's [goog.string.regExpEscape(s)]( 20763 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 20764 * 20765 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 20766 * 20767 * ### Show me an example using SCE. 20768 * 20769 * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service"> 20770 * <file name="index.html"> 20771 * <div ng-controller="AppController as myCtrl"> 20772 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 20773 * <b>User comments</b><br> 20774 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 20775 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 20776 * exploit. 20777 * <div class="well"> 20778 * <div ng-repeat="userComment in myCtrl.userComments"> 20779 * <b>{{userComment.name}}</b>: 20780 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 20781 * <br> 20782 * </div> 20783 * </div> 20784 * </div> 20785 * </file> 20786 * 20787 * <file name="script.js"> 20788 * angular.module('mySceApp', ['ngSanitize']) 20789 * .controller('AppController', ['$http', '$templateCache', '$sce', 20790 * function AppController($http, $templateCache, $sce) { 20791 * var self = this; 20792 * $http.get('test_data.json', {cache: $templateCache}).then(function(response) { 20793 * self.userComments = response.data; 20794 * }); 20795 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 20796 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 20797 * 'sanitization."">Hover over this text.</span>'); 20798 * }]); 20799 * </file> 20800 * 20801 * <file name="test_data.json"> 20802 * [ 20803 * { "name": "Alice", 20804 * "htmlComment": 20805 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 20806 * }, 20807 * { "name": "Bob", 20808 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 20809 * } 20810 * ] 20811 * </file> 20812 * 20813 * <file name="protractor.js" type="protractor"> 20814 * describe('SCE doc demo', function() { 20815 * it('should sanitize untrusted values', function() { 20816 * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML')) 20817 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 20818 * }); 20819 * 20820 * it('should NOT sanitize explicitly trusted values', function() { 20821 * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe( 20822 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 20823 * 'sanitization."">Hover over this text.</span>'); 20824 * }); 20825 * }); 20826 * </file> 20827 * </example> 20828 * 20829 * 20830 * 20831 * ## Can I disable SCE completely? 20832 * 20833 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 20834 * for little coding overhead. It will be much harder to take an SCE disabled application and 20835 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 20836 * for cases where you have a lot of existing code that was written before SCE was introduced and 20837 * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if 20838 * you are writing a library, you will cause security bugs applications using it. 20839 * 20840 * That said, here's how you can completely disable SCE: 20841 * 20842 * ``` 20843 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 20844 * // Completely disable SCE. For demonstration purposes only! 20845 * // Do not use in new projects or libraries. 20846 * $sceProvider.enabled(false); 20847 * }); 20848 * ``` 20849 * 20850 */ 20851 20852function $SceProvider() { 20853 var enabled = true; 20854 20855 /** 20856 * @ngdoc method 20857 * @name $sceProvider#enabled 20858 * @kind function 20859 * 20860 * @param {boolean=} value If provided, then enables/disables SCE application-wide. 20861 * @return {boolean} True if SCE is enabled, false otherwise. 20862 * 20863 * @description 20864 * Enables/disables SCE and returns the current value. 20865 */ 20866 this.enabled = function(value) { 20867 if (arguments.length) { 20868 enabled = !!value; 20869 } 20870 return enabled; 20871 }; 20872 20873 20874 /* Design notes on the default implementation for SCE. 20875 * 20876 * The API contract for the SCE delegate 20877 * ------------------------------------- 20878 * The SCE delegate object must provide the following 3 methods: 20879 * 20880 * - trustAs(contextEnum, value) 20881 * This method is used to tell the SCE service that the provided value is OK to use in the 20882 * contexts specified by contextEnum. It must return an object that will be accepted by 20883 * getTrusted() for a compatible contextEnum and return this value. 20884 * 20885 * - valueOf(value) 20886 * For values that were not produced by trustAs(), return them as is. For values that were 20887 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 20888 * trustAs is wrapping the given values into some type, this operation unwraps it when given 20889 * such a value. 20890 * 20891 * - getTrusted(contextEnum, value) 20892 * This function should return the value that is safe to use in the context specified by 20893 * contextEnum or throw and exception otherwise. 20894 * 20895 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 20896 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 20897 * instance, an implementation could maintain a registry of all trusted objects by context. In 20898 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 20899 * return the same object passed in if it was found in the registry under a compatible context or 20900 * throw an exception otherwise. An implementation might only wrap values some of the time based 20901 * on some criteria. getTrusted() might return a value and not throw an exception for special 20902 * constants or objects even if not wrapped. All such implementations fulfill this contract. 20903 * 20904 * 20905 * A note on the inheritance model for SCE contexts 20906 * ------------------------------------------------ 20907 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 20908 * is purely an implementation details. 20909 * 20910 * The contract is simply this: 20911 * 20912 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 20913 * will also succeed. 20914 * 20915 * Inheritance happens to capture this in a natural way. In some future, we may not use 20916 * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to 20917 * be aware of this detail. 20918 */ 20919 20920 this.$get = ['$parse', '$sceDelegate', function( 20921 $parse, $sceDelegate) { 20922 // Support: IE 9-11 only 20923 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 20924 // the "expression(javascript expression)" syntax which is insecure. 20925 if (enabled && msie < 8) { 20926 throw $sceMinErr('iequirks', 20927 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 20928 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 20929 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 20930 } 20931 20932 var sce = shallowCopy(SCE_CONTEXTS); 20933 20934 /** 20935 * @ngdoc method 20936 * @name $sce#isEnabled 20937 * @kind function 20938 * 20939 * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you 20940 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 20941 * 20942 * @description 20943 * Returns a boolean indicating if SCE is enabled. 20944 */ 20945 sce.isEnabled = function() { 20946 return enabled; 20947 }; 20948 sce.trustAs = $sceDelegate.trustAs; 20949 sce.getTrusted = $sceDelegate.getTrusted; 20950 sce.valueOf = $sceDelegate.valueOf; 20951 20952 if (!enabled) { 20953 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 20954 sce.valueOf = identity; 20955 } 20956 20957 /** 20958 * @ngdoc method 20959 * @name $sce#parseAs 20960 * 20961 * @description 20962 * Converts AngularJS {@link guide/expression expression} into a function. This is like {@link 20963 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 20964 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 20965 * *result*)} 20966 * 20967 * @param {string} type The SCE context in which this result will be used. 20968 * @param {string} expression String expression to compile. 20969 * @return {function(context, locals)} A function which represents the compiled expression: 20970 * 20971 * * `context` – `{object}` – an object against which any expressions embedded in the 20972 * strings are evaluated against (typically a scope object). 20973 * * `locals` – `{object=}` – local variables context object, useful for overriding values 20974 * in `context`. 20975 */ 20976 sce.parseAs = function sceParseAs(type, expr) { 20977 var parsed = $parse(expr); 20978 if (parsed.literal && parsed.constant) { 20979 return parsed; 20980 } else { 20981 return $parse(expr, function(value) { 20982 return sce.getTrusted(type, value); 20983 }); 20984 } 20985 }; 20986 20987 /** 20988 * @ngdoc method 20989 * @name $sce#trustAs 20990 * 20991 * @description 20992 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a 20993 * wrapped object that represents your value, and the trust you have in its safety for the given 20994 * context. AngularJS can then use that value as-is in bindings of the specified secure context. 20995 * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute 20996 * interpolations. See {@link ng.$sce $sce} for strict contextual escaping. 20997 * 20998 * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`, 20999 * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`. 21000 * 21001 * @param {*} value The value that that should be considered trusted. 21002 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21003 * in the context you specified. 21004 */ 21005 21006 /** 21007 * @ngdoc method 21008 * @name $sce#trustAsHtml 21009 * 21010 * @description 21011 * Shorthand method. `$sce.trustAsHtml(value)` → 21012 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 21013 * 21014 * @param {*} value The value to mark as trusted for `$sce.HTML` context. 21015 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21016 * in `$sce.HTML` context (like `ng-bind-html`). 21017 */ 21018 21019 /** 21020 * @ngdoc method 21021 * @name $sce#trustAsCss 21022 * 21023 * @description 21024 * Shorthand method. `$sce.trustAsCss(value)` → 21025 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`} 21026 * 21027 * @param {*} value The value to mark as trusted for `$sce.CSS` context. 21028 * @return {*} A wrapped version of value that can be used as a trusted variant 21029 * of your `value` in `$sce.CSS` context. This context is currently unused, so there are 21030 * almost no reasons to use this function so far. 21031 */ 21032 21033 /** 21034 * @ngdoc method 21035 * @name $sce#trustAsUrl 21036 * 21037 * @description 21038 * Shorthand method. `$sce.trustAsUrl(value)` → 21039 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 21040 * 21041 * @param {*} value The value to mark as trusted for `$sce.URL` context. 21042 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21043 * in `$sce.URL` context. That context is currently unused, so there are almost no reasons 21044 * to use this function so far. 21045 */ 21046 21047 /** 21048 * @ngdoc method 21049 * @name $sce#trustAsResourceUrl 21050 * 21051 * @description 21052 * Shorthand method. `$sce.trustAsResourceUrl(value)` → 21053 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 21054 * 21055 * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context. 21056 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21057 * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute 21058 * bindings, ...) 21059 */ 21060 21061 /** 21062 * @ngdoc method 21063 * @name $sce#trustAsJs 21064 * 21065 * @description 21066 * Shorthand method. `$sce.trustAsJs(value)` → 21067 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 21068 * 21069 * @param {*} value The value to mark as trusted for `$sce.JS` context. 21070 * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` 21071 * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to 21072 * use this function so far. 21073 */ 21074 21075 /** 21076 * @ngdoc method 21077 * @name $sce#getTrusted 21078 * 21079 * @description 21080 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 21081 * takes any input, and either returns a value that's safe to use in the specified context, 21082 * or throws an exception. This function is aware of trusted values created by the `trustAs` 21083 * function and its shorthands, and when contexts are appropriate, returns the unwrapped value 21084 * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a 21085 * safe value (e.g., no sanitization is available or possible.) 21086 * 21087 * @param {string} type The context in which this value is to be used. 21088 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs 21089 * `$sce.trustAs`} call, or anything else (which will not be considered trusted.) 21090 * @return {*} A version of the value that's safe to use in the given context, or throws an 21091 * exception if this is impossible. 21092 */ 21093 21094 /** 21095 * @ngdoc method 21096 * @name $sce#getTrustedHtml 21097 * 21098 * @description 21099 * Shorthand method. `$sce.getTrustedHtml(value)` → 21100 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 21101 * 21102 * @param {*} value The value to pass to `$sce.getTrusted`. 21103 * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)` 21104 */ 21105 21106 /** 21107 * @ngdoc method 21108 * @name $sce#getTrustedCss 21109 * 21110 * @description 21111 * Shorthand method. `$sce.getTrustedCss(value)` → 21112 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 21113 * 21114 * @param {*} value The value to pass to `$sce.getTrusted`. 21115 * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)` 21116 */ 21117 21118 /** 21119 * @ngdoc method 21120 * @name $sce#getTrustedUrl 21121 * 21122 * @description 21123 * Shorthand method. `$sce.getTrustedUrl(value)` → 21124 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 21125 * 21126 * @param {*} value The value to pass to `$sce.getTrusted`. 21127 * @return {*} The return value of `$sce.getTrusted($sce.URL, value)` 21128 */ 21129 21130 /** 21131 * @ngdoc method 21132 * @name $sce#getTrustedResourceUrl 21133 * 21134 * @description 21135 * Shorthand method. `$sce.getTrustedResourceUrl(value)` → 21136 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 21137 * 21138 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 21139 * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 21140 */ 21141 21142 /** 21143 * @ngdoc method 21144 * @name $sce#getTrustedJs 21145 * 21146 * @description 21147 * Shorthand method. `$sce.getTrustedJs(value)` → 21148 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 21149 * 21150 * @param {*} value The value to pass to `$sce.getTrusted`. 21151 * @return {*} The return value of `$sce.getTrusted($sce.JS, value)` 21152 */ 21153 21154 /** 21155 * @ngdoc method 21156 * @name $sce#parseAsHtml 21157 * 21158 * @description 21159 * Shorthand method. `$sce.parseAsHtml(expression string)` → 21160 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 21161 * 21162 * @param {string} expression String expression to compile. 21163 * @return {function(context, locals)} A function which represents the compiled expression: 21164 * 21165 * * `context` – `{object}` – an object against which any expressions embedded in the 21166 * strings are evaluated against (typically a scope object). 21167 * * `locals` – `{object=}` – local variables context object, useful for overriding values 21168 * in `context`. 21169 */ 21170 21171 /** 21172 * @ngdoc method 21173 * @name $sce#parseAsCss 21174 * 21175 * @description 21176 * Shorthand method. `$sce.parseAsCss(value)` → 21177 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 21178 * 21179 * @param {string} expression String expression to compile. 21180 * @return {function(context, locals)} A function which represents the compiled expression: 21181 * 21182 * * `context` – `{object}` – an object against which any expressions embedded in the 21183 * strings are evaluated against (typically a scope object). 21184 * * `locals` – `{object=}` – local variables context object, useful for overriding values 21185 * in `context`. 21186 */ 21187 21188 /** 21189 * @ngdoc method 21190 * @name $sce#parseAsUrl 21191 * 21192 * @description 21193 * Shorthand method. `$sce.parseAsUrl(value)` → 21194 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 21195 * 21196 * @param {string} expression String expression to compile. 21197 * @return {function(context, locals)} A function which represents the compiled expression: 21198 * 21199 * * `context` – `{object}` – an object against which any expressions embedded in the 21200 * strings are evaluated against (typically a scope object). 21201 * * `locals` – `{object=}` – local variables context object, useful for overriding values 21202 * in `context`. 21203 */ 21204 21205 /** 21206 * @ngdoc method 21207 * @name $sce#parseAsResourceUrl 21208 * 21209 * @description 21210 * Shorthand method. `$sce.parseAsResourceUrl(value)` → 21211 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 21212 * 21213 * @param {string} expression String expression to compile. 21214 * @return {function(context, locals)} A function which represents the compiled expression: 21215 * 21216 * * `context` – `{object}` – an object against which any expressions embedded in the 21217 * strings are evaluated against (typically a scope object). 21218 * * `locals` – `{object=}` – local variables context object, useful for overriding values 21219 * in `context`. 21220 */ 21221 21222 /** 21223 * @ngdoc method 21224 * @name $sce#parseAsJs 21225 * 21226 * @description 21227 * Shorthand method. `$sce.parseAsJs(value)` → 21228 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 21229 * 21230 * @param {string} expression String expression to compile. 21231 * @return {function(context, locals)} A function which represents the compiled expression: 21232 * 21233 * * `context` – `{object}` – an object against which any expressions embedded in the 21234 * strings are evaluated against (typically a scope object). 21235 * * `locals` – `{object=}` – local variables context object, useful for overriding values 21236 * in `context`. 21237 */ 21238 21239 // Shorthand delegations. 21240 var parse = sce.parseAs, 21241 getTrusted = sce.getTrusted, 21242 trustAs = sce.trustAs; 21243 21244 forEach(SCE_CONTEXTS, function(enumValue, name) { 21245 var lName = lowercase(name); 21246 sce[snakeToCamel('parse_as_' + lName)] = function(expr) { 21247 return parse(enumValue, expr); 21248 }; 21249 sce[snakeToCamel('get_trusted_' + lName)] = function(value) { 21250 return getTrusted(enumValue, value); 21251 }; 21252 sce[snakeToCamel('trust_as_' + lName)] = function(value) { 21253 return trustAs(enumValue, value); 21254 }; 21255 }); 21256 21257 return sce; 21258 }]; 21259} 21260 21261/* exported $SnifferProvider */ 21262 21263/** 21264 * !!! This is an undocumented "private" service !!! 21265 * 21266 * @name $sniffer 21267 * @requires $window 21268 * @requires $document 21269 * @this 21270 * 21271 * @property {boolean} history Does the browser support html5 history api ? 21272 * @property {boolean} transitions Does the browser support CSS transition events ? 21273 * @property {boolean} animations Does the browser support CSS animation events ? 21274 * 21275 * @description 21276 * This is very simple implementation of testing browser's features. 21277 */ 21278function $SnifferProvider() { 21279 this.$get = ['$window', '$document', function($window, $document) { 21280 var eventSupport = {}, 21281 // Chrome Packaged Apps are not allowed to access `history.pushState`. 21282 // If not sandboxed, they can be detected by the presence of `chrome.app.runtime` 21283 // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by 21284 // the presence of an extension runtime ID and the absence of other Chrome runtime APIs 21285 // (see https://developer.chrome.com/apps/manifest/sandbox). 21286 // (NW.js apps have access to Chrome APIs, but do support `history`.) 21287 isNw = $window.nw && $window.nw.process, 21288 isChromePackagedApp = 21289 !isNw && 21290 $window.chrome && 21291 ($window.chrome.app && $window.chrome.app.runtime || 21292 !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id), 21293 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, 21294 android = 21295 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 21296 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 21297 document = $document[0] || {}, 21298 bodyStyle = document.body && document.body.style, 21299 transitions = false, 21300 animations = false; 21301 21302 if (bodyStyle) { 21303 // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x 21304 // Mentioned browsers need a -webkit- prefix for transitions & animations. 21305 transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle); 21306 animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle); 21307 } 21308 21309 21310 return { 21311 // Android has history.pushState, but it does not update location correctly 21312 // so let's not use the history API at all. 21313 // http://code.google.com/p/android/issues/detail?id=17471 21314 // https://github.com/angular/angular.js/issues/904 21315 21316 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 21317 // so let's not use the history API also 21318 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 21319 history: !!(hasHistoryPushState && !(android < 4) && !boxee), 21320 hasEvent: function(event) { 21321 // Support: IE 9-11 only 21322 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 21323 // it. In particular the event is not fired when backspace or delete key are pressed or 21324 // when cut operation is performed. 21325 // IE10+ implements 'input' event but it erroneously fires under various situations, 21326 // e.g. when placeholder changes, or a form is focused. 21327 if (event === 'input' && msie) return false; 21328 21329 if (isUndefined(eventSupport[event])) { 21330 var divElm = document.createElement('div'); 21331 eventSupport[event] = 'on' + event in divElm; 21332 } 21333 21334 return eventSupport[event]; 21335 }, 21336 csp: csp(), 21337 transitions: transitions, 21338 animations: animations, 21339 android: android 21340 }; 21341 }]; 21342} 21343 21344/** 21345 * ! This is a private undocumented service ! 21346 * 21347 * @name $$taskTrackerFactory 21348 * @description 21349 * A function to create `TaskTracker` instances. 21350 * 21351 * A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested 21352 * parties when all pending tasks (or tasks of a specific type) have been completed. 21353 * 21354 * @param {$log} log - A logger instance (such as `$log`). Used to log error during callback 21355 * execution. 21356 * 21357 * @this 21358 */ 21359function $$TaskTrackerFactoryProvider() { 21360 this.$get = valueFn(function(log) { return new TaskTracker(log); }); 21361} 21362 21363function TaskTracker(log) { 21364 var self = this; 21365 var taskCounts = {}; 21366 var taskCallbacks = []; 21367 21368 var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$'; 21369 var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$'; 21370 21371 /** 21372 * Execute the specified function and decrement the appropriate `taskCounts` counter. 21373 * If the counter reaches 0, all corresponding `taskCallbacks` are executed. 21374 * 21375 * @param {Function} fn - The function to execute. 21376 * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed. 21377 */ 21378 self.completeTask = completeTask; 21379 21380 /** 21381 * Increase the task count for the specified task type (or the default task type if non is 21382 * specified). 21383 * 21384 * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased. 21385 */ 21386 self.incTaskCount = incTaskCount; 21387 21388 /** 21389 * Execute the specified callback when all pending tasks have been completed. 21390 * 21391 * If there are no pending tasks, the callback is executed immediately. You can optionally limit 21392 * the tasks that will be waited for to a specific type, by passing a `taskType`. 21393 * 21394 * @param {function} callback - The function to call when there are no pending tasks. 21395 * @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for. 21396 */ 21397 self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks; 21398 21399 function completeTask(fn, taskType) { 21400 taskType = taskType || DEFAULT_TASK_TYPE; 21401 21402 try { 21403 fn(); 21404 } finally { 21405 decTaskCount(taskType); 21406 21407 var countForType = taskCounts[taskType]; 21408 var countForAll = taskCounts[ALL_TASKS_TYPE]; 21409 21410 // If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks. 21411 if (!countForAll || !countForType) { 21412 var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType; 21413 var nextCb; 21414 21415 while ((nextCb = getNextCallback(taskType))) { 21416 try { 21417 nextCb(); 21418 } catch (e) { 21419 log.error(e); 21420 } 21421 } 21422 } 21423 } 21424 } 21425 21426 function decTaskCount(taskType) { 21427 taskType = taskType || DEFAULT_TASK_TYPE; 21428 if (taskCounts[taskType]) { 21429 taskCounts[taskType]--; 21430 taskCounts[ALL_TASKS_TYPE]--; 21431 } 21432 } 21433 21434 function getLastCallback() { 21435 var cbInfo = taskCallbacks.pop(); 21436 return cbInfo && cbInfo.cb; 21437 } 21438 21439 function getLastCallbackForType(taskType) { 21440 for (var i = taskCallbacks.length - 1; i >= 0; --i) { 21441 var cbInfo = taskCallbacks[i]; 21442 if (cbInfo.type === taskType) { 21443 taskCallbacks.splice(i, 1); 21444 return cbInfo.cb; 21445 } 21446 } 21447 } 21448 21449 function incTaskCount(taskType) { 21450 taskType = taskType || DEFAULT_TASK_TYPE; 21451 taskCounts[taskType] = (taskCounts[taskType] || 0) + 1; 21452 taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1; 21453 } 21454 21455 function notifyWhenNoPendingTasks(callback, taskType) { 21456 taskType = taskType || ALL_TASKS_TYPE; 21457 if (!taskCounts[taskType]) { 21458 callback(); 21459 } else { 21460 taskCallbacks.push({type: taskType, cb: callback}); 21461 } 21462 } 21463} 21464 21465var $templateRequestMinErr = minErr('$templateRequest'); 21466 21467/** 21468 * @ngdoc provider 21469 * @name $templateRequestProvider 21470 * @this 21471 * 21472 * @description 21473 * Used to configure the options passed to the {@link $http} service when making a template request. 21474 * 21475 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 21476 * requesting a template. 21477 */ 21478function $TemplateRequestProvider() { 21479 21480 var httpOptions; 21481 21482 /** 21483 * @ngdoc method 21484 * @name $templateRequestProvider#httpOptions 21485 * @description 21486 * The options to be passed to the {@link $http} service when making the request. 21487 * You can use this to override options such as the "Accept" header for template requests. 21488 * 21489 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 21490 * options if not overridden here. 21491 * 21492 * @param {string=} value new value for the {@link $http} options. 21493 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 21494 */ 21495 this.httpOptions = function(val) { 21496 if (val) { 21497 httpOptions = val; 21498 return this; 21499 } 21500 return httpOptions; 21501 }; 21502 21503 /** 21504 * @ngdoc service 21505 * @name $templateRequest 21506 * 21507 * @description 21508 * The `$templateRequest` service runs security checks then downloads the provided template using 21509 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 21510 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 21511 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 21512 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 21513 * when `tpl` is of type string and `$templateCache` has the matching entry. 21514 * 21515 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 21516 * can configure this via {@link $templateRequestProvider#httpOptions}. 21517 * 21518 * `$templateRequest` is used internally by {@link $compile}, {@link ngRoute.$route}, and directives such 21519 * as {@link ngInclude} to download and cache templates. 21520 * 21521 * 3rd party modules should use `$templateRequest` if their services or directives are loading 21522 * templates. 21523 * 21524 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 21525 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 21526 * 21527 * @return {Promise} a promise for the HTTP response data of the given URL. 21528 * 21529 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 21530 */ 21531 this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce', 21532 function($exceptionHandler, $templateCache, $http, $q, $sce) { 21533 21534 function handleRequestFn(tpl, ignoreRequestError) { 21535 handleRequestFn.totalPendingRequests++; 21536 21537 // We consider the template cache holds only trusted templates, so 21538 // there's no need to go through adding the template again to the trusted 21539 // resources for keys that already are included in there. This also makes 21540 // AngularJS accept any script directive, no matter its name. However, we 21541 // still need to unwrap trusted types. 21542 if (!isString(tpl) || isUndefined($templateCache.get(tpl))) { 21543 tpl = $sce.getTrustedResourceUrl(tpl); 21544 } 21545 21546 var transformResponse = $http.defaults && $http.defaults.transformResponse; 21547 21548 if (isArray(transformResponse)) { 21549 transformResponse = transformResponse.filter(function(transformer) { 21550 return transformer !== defaultHttpResponseTransform; 21551 }); 21552 } else if (transformResponse === defaultHttpResponseTransform) { 21553 transformResponse = null; 21554 } 21555 21556 return $http.get(tpl, extend({ 21557 cache: $templateCache, 21558 transformResponse: transformResponse 21559 }, httpOptions)) 21560 .finally(function() { 21561 handleRequestFn.totalPendingRequests--; 21562 }) 21563 .then(function(response) { 21564 return $templateCache.put(tpl, response.data); 21565 }, handleError); 21566 21567 function handleError(resp) { 21568 if (!ignoreRequestError) { 21569 resp = $templateRequestMinErr('tpload', 21570 'Failed to load template: {0} (HTTP status: {1} {2})', 21571 tpl, resp.status, resp.statusText); 21572 21573 $exceptionHandler(resp); 21574 } 21575 21576 return $q.reject(resp); 21577 } 21578 } 21579 21580 handleRequestFn.totalPendingRequests = 0; 21581 21582 return handleRequestFn; 21583 } 21584 ]; 21585} 21586 21587/** @this */ 21588function $$TestabilityProvider() { 21589 this.$get = ['$rootScope', '$browser', '$location', 21590 function($rootScope, $browser, $location) { 21591 21592 /** 21593 * @name $testability 21594 * 21595 * @description 21596 * The private $$testability service provides a collection of methods for use when debugging 21597 * or by automated test and debugging tools. 21598 */ 21599 var testability = {}; 21600 21601 /** 21602 * @name $$testability#findBindings 21603 * 21604 * @description 21605 * Returns an array of elements that are bound (via ng-bind or {{}}) 21606 * to expressions matching the input. 21607 * 21608 * @param {Element} element The element root to search from. 21609 * @param {string} expression The binding expression to match. 21610 * @param {boolean} opt_exactMatch If true, only returns exact matches 21611 * for the expression. Filters and whitespace are ignored. 21612 */ 21613 testability.findBindings = function(element, expression, opt_exactMatch) { 21614 var bindings = element.getElementsByClassName('ng-binding'); 21615 var matches = []; 21616 forEach(bindings, function(binding) { 21617 var dataBinding = angular.element(binding).data('$binding'); 21618 if (dataBinding) { 21619 forEach(dataBinding, function(bindingName) { 21620 if (opt_exactMatch) { 21621 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 21622 if (matcher.test(bindingName)) { 21623 matches.push(binding); 21624 } 21625 } else { 21626 if (bindingName.indexOf(expression) !== -1) { 21627 matches.push(binding); 21628 } 21629 } 21630 }); 21631 } 21632 }); 21633 return matches; 21634 }; 21635 21636 /** 21637 * @name $$testability#findModels 21638 * 21639 * @description 21640 * Returns an array of elements that are two-way found via ng-model to 21641 * expressions matching the input. 21642 * 21643 * @param {Element} element The element root to search from. 21644 * @param {string} expression The model expression to match. 21645 * @param {boolean} opt_exactMatch If true, only returns exact matches 21646 * for the expression. 21647 */ 21648 testability.findModels = function(element, expression, opt_exactMatch) { 21649 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 21650 for (var p = 0; p < prefixes.length; ++p) { 21651 var attributeEquals = opt_exactMatch ? '=' : '*='; 21652 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 21653 var elements = element.querySelectorAll(selector); 21654 if (elements.length) { 21655 return elements; 21656 } 21657 } 21658 }; 21659 21660 /** 21661 * @name $$testability#getLocation 21662 * 21663 * @description 21664 * Shortcut for getting the location in a browser agnostic way. Returns 21665 * the path, search, and hash. (e.g. /path?a=b#hash) 21666 */ 21667 testability.getLocation = function() { 21668 return $location.url(); 21669 }; 21670 21671 /** 21672 * @name $$testability#setLocation 21673 * 21674 * @description 21675 * Shortcut for navigating to a location without doing a full page reload. 21676 * 21677 * @param {string} url The location url (path, search and hash, 21678 * e.g. /path?a=b#hash) to go to. 21679 */ 21680 testability.setLocation = function(url) { 21681 if (url !== $location.url()) { 21682 $location.url(url); 21683 $rootScope.$digest(); 21684 } 21685 }; 21686 21687 /** 21688 * @name $$testability#whenStable 21689 * 21690 * @description 21691 * Calls the callback when all pending tasks are completed. 21692 * 21693 * Types of tasks waited for include: 21694 * - Pending timeouts (via {@link $timeout}). 21695 * - Pending HTTP requests (via {@link $http}). 21696 * - In-progress route transitions (via {@link $route}). 21697 * - Pending tasks scheduled via {@link $rootScope#$applyAsync}. 21698 * - Pending tasks scheduled via {@link $rootScope#$evalAsync}. 21699 * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises). 21700 * 21701 * @param {function} callback 21702 */ 21703 testability.whenStable = function(callback) { 21704 $browser.notifyWhenNoOutstandingRequests(callback); 21705 }; 21706 21707 return testability; 21708 }]; 21709} 21710 21711var $timeoutMinErr = minErr('$timeout'); 21712 21713/** @this */ 21714function $TimeoutProvider() { 21715 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 21716 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 21717 21718 var deferreds = {}; 21719 21720 21721 /** 21722 * @ngdoc service 21723 * @name $timeout 21724 * 21725 * @description 21726 * AngularJS's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 21727 * block and delegates any exceptions to 21728 * {@link ng.$exceptionHandler $exceptionHandler} service. 21729 * 21730 * The return value of calling `$timeout` is a promise, which will be resolved when 21731 * the delay has passed and the timeout function, if provided, is executed. 21732 * 21733 * To cancel a timeout request, call `$timeout.cancel(promise)`. 21734 * 21735 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 21736 * synchronously flush the queue of deferred functions. 21737 * 21738 * If you only want a promise that will be resolved after some specified delay 21739 * then you can call `$timeout` without the `fn` function. 21740 * 21741 * @param {function()=} fn A function, whose execution should be delayed. 21742 * @param {number=} [delay=0] Delay in milliseconds. 21743 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 21744 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 21745 * @param {...*=} Pass additional parameters to the executed function. 21746 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 21747 * will be resolved with the return value of the `fn` function. 21748 * 21749 */ 21750 function timeout(fn, delay, invokeApply) { 21751 if (!isFunction(fn)) { 21752 invokeApply = delay; 21753 delay = fn; 21754 fn = noop; 21755 } 21756 21757 var args = sliceArgs(arguments, 3), 21758 skipApply = (isDefined(invokeApply) && !invokeApply), 21759 deferred = (skipApply ? $$q : $q).defer(), 21760 promise = deferred.promise, 21761 timeoutId; 21762 21763 timeoutId = $browser.defer(function() { 21764 try { 21765 deferred.resolve(fn.apply(null, args)); 21766 } catch (e) { 21767 deferred.reject(e); 21768 $exceptionHandler(e); 21769 } finally { 21770 delete deferreds[promise.$$timeoutId]; 21771 } 21772 21773 if (!skipApply) $rootScope.$apply(); 21774 }, delay, '$timeout'); 21775 21776 promise.$$timeoutId = timeoutId; 21777 deferreds[timeoutId] = deferred; 21778 21779 return promise; 21780 } 21781 21782 21783 /** 21784 * @ngdoc method 21785 * @name $timeout#cancel 21786 * 21787 * @description 21788 * Cancels a task associated with the `promise`. As a result of this, the promise will be 21789 * resolved with a rejection. 21790 * 21791 * @param {Promise=} promise Promise returned by the `$timeout` function. 21792 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 21793 * canceled. 21794 */ 21795 timeout.cancel = function(promise) { 21796 if (!promise) return false; 21797 21798 if (!promise.hasOwnProperty('$$timeoutId')) { 21799 throw $timeoutMinErr('badprom', 21800 '`$timeout.cancel()` called with a promise that was not generated by `$timeout()`.'); 21801 } 21802 21803 if (!deferreds.hasOwnProperty(promise.$$timeoutId)) return false; 21804 21805 var id = promise.$$timeoutId; 21806 var deferred = deferreds[id]; 21807 21808 // Timeout cancels should not report an unhandled promise. 21809 markQExceptionHandled(deferred.promise); 21810 deferred.reject('canceled'); 21811 delete deferreds[id]; 21812 21813 return $browser.defer.cancel(id); 21814 }; 21815 21816 return timeout; 21817 }]; 21818} 21819 21820// NOTE: The usage of window and document instead of $window and $document here is 21821// deliberate. This service depends on the specific behavior of anchor nodes created by the 21822// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 21823// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 21824// doesn't know about mocked locations and resolves URLs to the real document - which is 21825// exactly the behavior needed here. There is little value is mocking these out for this 21826// service. 21827var urlParsingNode = window.document.createElement('a'); 21828var originUrl = urlResolve(window.location.href); 21829var baseUrlParsingNode; 21830 21831urlParsingNode.href = 'http://[::1]'; 21832 21833// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview) 21834// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets 21835// when parsed out of an anchor element. 21836var ipv6InBrackets = urlParsingNode.hostname === '[::1]'; 21837 21838/** 21839 * 21840 * Implementation Notes for non-IE browsers 21841 * ---------------------------------------- 21842 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 21843 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 21844 * URL will be resolved into an absolute URL in the context of the application document. 21845 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 21846 * properties are all populated to reflect the normalized URL. This approach has wide 21847 * compatibility - Safari 1+, Mozilla 1+ etc. See 21848 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 21849 * 21850 * Implementation Notes for IE 21851 * --------------------------- 21852 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 21853 * browsers. However, the parsed components will not be set if the URL assigned did not specify 21854 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 21855 * work around that by performing the parsing in a 2nd step by taking a previously normalized 21856 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 21857 * properties such as protocol, hostname, port, etc. 21858 * 21859 * References: 21860 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 21861 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 21862 * http://url.spec.whatwg.org/#urlutils 21863 * https://github.com/angular/angular.js/pull/2902 21864 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 21865 * 21866 * @kind function 21867 * @param {string|object} url The URL to be parsed. If `url` is not a string, it will be returned 21868 * unchanged. 21869 * @description Normalizes and parses a URL. 21870 * @returns {object} Returns the normalized URL as a dictionary. 21871 * 21872 * | member name | Description | 21873 * |---------------|------------------------------------------------------------------------| 21874 * | href | A normalized version of the provided URL if it was not an absolute URL | 21875 * | protocol | The protocol without the trailing colon | 21876 * | host | The host and port (if the port is non-default) of the normalizedUrl | 21877 * | search | The search params, minus the question mark | 21878 * | hash | The hash string, minus the hash symbol | 21879 * | hostname | The hostname | 21880 * | port | The port, without ":" | 21881 * | pathname | The pathname, beginning with "/" | 21882 * 21883 */ 21884function urlResolve(url) { 21885 if (!isString(url)) return url; 21886 21887 var href = url; 21888 21889 // Support: IE 9-11 only 21890 if (msie) { 21891 // Normalize before parse. Refer Implementation Notes on why this is 21892 // done in two steps on IE. 21893 urlParsingNode.setAttribute('href', href); 21894 href = urlParsingNode.href; 21895 } 21896 21897 urlParsingNode.setAttribute('href', href); 21898 21899 var hostname = urlParsingNode.hostname; 21900 21901 if (!ipv6InBrackets && hostname.indexOf(':') > -1) { 21902 hostname = '[' + hostname + ']'; 21903 } 21904 21905 return { 21906 href: urlParsingNode.href, 21907 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 21908 host: urlParsingNode.host, 21909 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 21910 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 21911 hostname: hostname, 21912 port: urlParsingNode.port, 21913 pathname: (urlParsingNode.pathname.charAt(0) === '/') 21914 ? urlParsingNode.pathname 21915 : '/' + urlParsingNode.pathname 21916 }; 21917} 21918 21919/** 21920 * Parse a request URL and determine whether this is a same-origin request as the application 21921 * document. 21922 * 21923 * @param {string|object} requestUrl The url of the request as a string that will be resolved 21924 * or a parsed URL object. 21925 * @returns {boolean} Whether the request is for the same origin as the application document. 21926 */ 21927function urlIsSameOrigin(requestUrl) { 21928 return urlsAreSameOrigin(requestUrl, originUrl); 21929} 21930 21931/** 21932 * Parse a request URL and determine whether it is same-origin as the current document base URL. 21933 * 21934 * Note: The base URL is usually the same as the document location (`location.href`) but can 21935 * be overriden by using the `<base>` tag. 21936 * 21937 * @param {string|object} requestUrl The url of the request as a string that will be resolved 21938 * or a parsed URL object. 21939 * @returns {boolean} Whether the URL is same-origin as the document base URL. 21940 */ 21941function urlIsSameOriginAsBaseUrl(requestUrl) { 21942 return urlsAreSameOrigin(requestUrl, getBaseUrl()); 21943} 21944 21945/** 21946 * Create a function that can check a URL's origin against a list of allowed/trusted origins. 21947 * The current location's origin is implicitly trusted. 21948 * 21949 * @param {string[]} trustedOriginUrls - A list of URLs (strings), whose origins are trusted. 21950 * 21951 * @returns {Function} - A function that receives a URL (string or parsed URL object) and returns 21952 * whether it is of an allowed origin. 21953 */ 21954function urlIsAllowedOriginFactory(trustedOriginUrls) { 21955 var parsedAllowedOriginUrls = [originUrl].concat(trustedOriginUrls.map(urlResolve)); 21956 21957 /** 21958 * Check whether the specified URL (string or parsed URL object) has an origin that is allowed 21959 * based on a list of trusted-origin URLs. The current location's origin is implicitly 21960 * trusted. 21961 * 21962 * @param {string|Object} requestUrl - The URL to be checked (provided as a string that will be 21963 * resolved or a parsed URL object). 21964 * 21965 * @returns {boolean} - Whether the specified URL is of an allowed origin. 21966 */ 21967 return function urlIsAllowedOrigin(requestUrl) { 21968 var parsedUrl = urlResolve(requestUrl); 21969 return parsedAllowedOriginUrls.some(urlsAreSameOrigin.bind(null, parsedUrl)); 21970 }; 21971} 21972 21973/** 21974 * Determine if two URLs share the same origin. 21975 * 21976 * @param {string|Object} url1 - First URL to compare as a string or a normalized URL in the form of 21977 * a dictionary object returned by `urlResolve()`. 21978 * @param {string|object} url2 - Second URL to compare as a string or a normalized URL in the form 21979 * of a dictionary object returned by `urlResolve()`. 21980 * 21981 * @returns {boolean} - True if both URLs have the same origin, and false otherwise. 21982 */ 21983function urlsAreSameOrigin(url1, url2) { 21984 url1 = urlResolve(url1); 21985 url2 = urlResolve(url2); 21986 21987 return (url1.protocol === url2.protocol && 21988 url1.host === url2.host); 21989} 21990 21991/** 21992 * Returns the current document base URL. 21993 * @returns {string} 21994 */ 21995function getBaseUrl() { 21996 if (window.document.baseURI) { 21997 return window.document.baseURI; 21998 } 21999 22000 // `document.baseURI` is available everywhere except IE 22001 if (!baseUrlParsingNode) { 22002 baseUrlParsingNode = window.document.createElement('a'); 22003 baseUrlParsingNode.href = '.'; 22004 22005 // Work-around for IE bug described in Implementation Notes. The fix in `urlResolve()` is not 22006 // suitable here because we need to track changes to the base URL. 22007 baseUrlParsingNode = baseUrlParsingNode.cloneNode(false); 22008 } 22009 return baseUrlParsingNode.href; 22010} 22011 22012/** 22013 * @ngdoc service 22014 * @name $window 22015 * @this 22016 * 22017 * @description 22018 * A reference to the browser's `window` object. While `window` 22019 * is globally available in JavaScript, it causes testability problems, because 22020 * it is a global variable. In AngularJS we always refer to it through the 22021 * `$window` service, so it may be overridden, removed or mocked for testing. 22022 * 22023 * Expressions, like the one defined for the `ngClick` directive in the example 22024 * below, are evaluated with respect to the current scope. Therefore, there is 22025 * no risk of inadvertently coding in a dependency on a global value in such an 22026 * expression. 22027 * 22028 * @example 22029 <example module="windowExample" name="window-service"> 22030 <file name="index.html"> 22031 <script> 22032 angular.module('windowExample', []) 22033 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 22034 $scope.greeting = 'Hello, World!'; 22035 $scope.doGreeting = function(greeting) { 22036 $window.alert(greeting); 22037 }; 22038 }]); 22039 </script> 22040 <div ng-controller="ExampleController"> 22041 <input type="text" ng-model="greeting" aria-label="greeting" /> 22042 <button ng-click="doGreeting(greeting)">ALERT</button> 22043 </div> 22044 </file> 22045 <file name="protractor.js" type="protractor"> 22046 it('should display the greeting in the input box', function() { 22047 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 22048 // If we click the button it will block the test runner 22049 // element(':button').click(); 22050 }); 22051 </file> 22052 </example> 22053 */ 22054function $WindowProvider() { 22055 this.$get = valueFn(window); 22056} 22057 22058/** 22059 * @name $$cookieReader 22060 * @requires $document 22061 * 22062 * @description 22063 * This is a private service for reading cookies used by $http and ngCookies 22064 * 22065 * @return {Object} a key/value map of the current cookies 22066 */ 22067function $$CookieReader($document) { 22068 var rawDocument = $document[0] || {}; 22069 var lastCookies = {}; 22070 var lastCookieString = ''; 22071 22072 function safeGetCookie(rawDocument) { 22073 try { 22074 return rawDocument.cookie || ''; 22075 } catch (e) { 22076 return ''; 22077 } 22078 } 22079 22080 function safeDecodeURIComponent(str) { 22081 try { 22082 return decodeURIComponent(str); 22083 } catch (e) { 22084 return str; 22085 } 22086 } 22087 22088 return function() { 22089 var cookieArray, cookie, i, index, name; 22090 var currentCookieString = safeGetCookie(rawDocument); 22091 22092 if (currentCookieString !== lastCookieString) { 22093 lastCookieString = currentCookieString; 22094 cookieArray = lastCookieString.split('; '); 22095 lastCookies = {}; 22096 22097 for (i = 0; i < cookieArray.length; i++) { 22098 cookie = cookieArray[i]; 22099 index = cookie.indexOf('='); 22100 if (index > 0) { //ignore nameless cookies 22101 name = safeDecodeURIComponent(cookie.substring(0, index)); 22102 // the first value that is seen for a cookie is the most 22103 // specific one. values for the same cookie name that 22104 // follow are for less specific paths. 22105 if (isUndefined(lastCookies[name])) { 22106 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 22107 } 22108 } 22109 } 22110 } 22111 return lastCookies; 22112 }; 22113} 22114 22115$$CookieReader.$inject = ['$document']; 22116 22117/** @this */ 22118function $$CookieReaderProvider() { 22119 this.$get = $$CookieReader; 22120} 22121 22122/* global currencyFilter: true, 22123 dateFilter: true, 22124 filterFilter: true, 22125 jsonFilter: true, 22126 limitToFilter: true, 22127 lowercaseFilter: true, 22128 numberFilter: true, 22129 orderByFilter: true, 22130 uppercaseFilter: true, 22131 */ 22132 22133/** 22134 * @ngdoc provider 22135 * @name $filterProvider 22136 * @description 22137 * 22138 * Filters are just functions which transform input to an output. However filters need to be 22139 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 22140 * annotated with dependencies and is responsible for creating a filter function. 22141 * 22142 * <div class="alert alert-warning"> 22143 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 22144 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 22145 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 22146 * (`myapp_subsection_filterx`). 22147 * </div> 22148 * 22149 * ```js 22150 * // Filter registration 22151 * function MyModule($provide, $filterProvider) { 22152 * // create a service to demonstrate injection (not always needed) 22153 * $provide.value('greet', function(name){ 22154 * return 'Hello ' + name + '!'; 22155 * }); 22156 * 22157 * // register a filter factory which uses the 22158 * // greet service to demonstrate DI. 22159 * $filterProvider.register('greet', function(greet){ 22160 * // return the filter function which uses the greet service 22161 * // to generate salutation 22162 * return function(text) { 22163 * // filters need to be forgiving so check input validity 22164 * return text && greet(text) || text; 22165 * }; 22166 * }); 22167 * } 22168 * ``` 22169 * 22170 * The filter function is registered with the `$injector` under the filter name suffix with 22171 * `Filter`. 22172 * 22173 * ```js 22174 * it('should be the same instance', inject( 22175 * function($filterProvider) { 22176 * $filterProvider.register('reverse', function(){ 22177 * return ...; 22178 * }); 22179 * }, 22180 * function($filter, reverseFilter) { 22181 * expect($filter('reverse')).toBe(reverseFilter); 22182 * }); 22183 * ``` 22184 * 22185 * 22186 * For more information about how AngularJS filters work, and how to create your own filters, see 22187 * {@link guide/filter Filters} in the AngularJS Developer Guide. 22188 */ 22189 22190/** 22191 * @ngdoc service 22192 * @name $filter 22193 * @kind function 22194 * @description 22195 * Filters are used for formatting data displayed to the user. 22196 * 22197 * They can be used in view templates, controllers or services. AngularJS comes 22198 * with a collection of [built-in filters](api/ng/filter), but it is easy to 22199 * define your own as well. 22200 * 22201 * The general syntax in templates is as follows: 22202 * 22203 * ```html 22204 * {{ expression [| filter_name[:parameter_value] ... ] }} 22205 * ``` 22206 * 22207 * @param {String} name Name of the filter function to retrieve 22208 * @return {Function} the filter function 22209 * @example 22210 <example name="$filter" module="filterExample"> 22211 <file name="index.html"> 22212 <div ng-controller="MainCtrl"> 22213 <h3>{{ originalText }}</h3> 22214 <h3>{{ filteredText }}</h3> 22215 </div> 22216 </file> 22217 22218 <file name="script.js"> 22219 angular.module('filterExample', []) 22220 .controller('MainCtrl', function($scope, $filter) { 22221 $scope.originalText = 'hello'; 22222 $scope.filteredText = $filter('uppercase')($scope.originalText); 22223 }); 22224 </file> 22225 </example> 22226 */ 22227$FilterProvider.$inject = ['$provide']; 22228/** @this */ 22229function $FilterProvider($provide) { 22230 var suffix = 'Filter'; 22231 22232 /** 22233 * @ngdoc method 22234 * @name $filterProvider#register 22235 * @param {string|Object} name Name of the filter function, or an object map of filters where 22236 * the keys are the filter names and the values are the filter factories. 22237 * 22238 * <div class="alert alert-warning"> 22239 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. 22240 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 22241 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 22242 * (`myapp_subsection_filterx`). 22243 * </div> 22244 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 22245 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 22246 * of the registered filter instances. 22247 */ 22248 function register(name, factory) { 22249 if (isObject(name)) { 22250 var filters = {}; 22251 forEach(name, function(filter, key) { 22252 filters[key] = register(key, filter); 22253 }); 22254 return filters; 22255 } else { 22256 return $provide.factory(name + suffix, factory); 22257 } 22258 } 22259 this.register = register; 22260 22261 this.$get = ['$injector', function($injector) { 22262 return function(name) { 22263 return $injector.get(name + suffix); 22264 }; 22265 }]; 22266 22267 //////////////////////////////////////// 22268 22269 /* global 22270 currencyFilter: false, 22271 dateFilter: false, 22272 filterFilter: false, 22273 jsonFilter: false, 22274 limitToFilter: false, 22275 lowercaseFilter: false, 22276 numberFilter: false, 22277 orderByFilter: false, 22278 uppercaseFilter: false 22279 */ 22280 22281 register('currency', currencyFilter); 22282 register('date', dateFilter); 22283 register('filter', filterFilter); 22284 register('json', jsonFilter); 22285 register('limitTo', limitToFilter); 22286 register('lowercase', lowercaseFilter); 22287 register('number', numberFilter); 22288 register('orderBy', orderByFilter); 22289 register('uppercase', uppercaseFilter); 22290} 22291 22292/** 22293 * @ngdoc filter 22294 * @name filter 22295 * @kind function 22296 * 22297 * @description 22298 * Selects a subset of items from `array` and returns it as a new array. 22299 * 22300 * @param {Array} array The source array. 22301 * <div class="alert alert-info"> 22302 * **Note**: If the array contains objects that reference themselves, filtering is not possible. 22303 * </div> 22304 * @param {string|Object|function()} expression The predicate to be used for selecting items from 22305 * `array`. 22306 * 22307 * Can be one of: 22308 * 22309 * - `string`: The string is used for matching against the contents of the `array`. All strings or 22310 * objects with string properties in `array` that match this string will be returned. This also 22311 * applies to nested object properties. 22312 * The predicate can be negated by prefixing the string with `!`. 22313 * 22314 * - `Object`: A pattern object can be used to filter specific properties on objects contained 22315 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 22316 * which have property `name` containing "M" and property `phone` containing "1". A special 22317 * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match 22318 * against any property of the object or its nested object properties. That's equivalent to the 22319 * simple substring match with a `string` as described above. The special property name can be 22320 * overwritten, using the `anyPropertyKey` parameter. 22321 * The predicate can be negated by prefixing the string with `!`. 22322 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 22323 * not containing "M". 22324 * 22325 * Note that a named property will match properties on the same level only, while the special 22326 * `$` property will match properties on the same level or deeper. E.g. an array item like 22327 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 22328 * **will** be matched by `{$: 'John'}`. 22329 * 22330 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 22331 * The function is called for each element of the array, with the element, its index, and 22332 * the entire array itself as arguments. 22333 * 22334 * The final result is an array of those elements that the predicate returned true for. 22335 * 22336 * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in 22337 * determining if values retrieved using `expression` (when it is not a function) should be 22338 * considered a match based on the expected value (from the filter expression) and actual 22339 * value (from the object in the array). 22340 * 22341 * Can be one of: 22342 * 22343 * - `function(actual, expected)`: 22344 * The function will be given the object value and the predicate value to compare and 22345 * should return true if both values should be considered equal. 22346 * 22347 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 22348 * This is essentially strict comparison of expected and actual. 22349 * 22350 * - `false`: A short hand for a function which will look for a substring match in a case 22351 * insensitive way. Primitive values are converted to strings. Objects are not compared against 22352 * primitives, unless they have a custom `toString` method (e.g. `Date` objects). 22353 * 22354 * 22355 * Defaults to `false`. 22356 * 22357 * @param {string} [anyPropertyKey] The special property name that matches against any property. 22358 * By default `$`. 22359 * 22360 * @example 22361 <example name="filter-filter"> 22362 <file name="index.html"> 22363 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 22364 {name:'Mary', phone:'800-BIG-MARY'}, 22365 {name:'Mike', phone:'555-4321'}, 22366 {name:'Adam', phone:'555-5678'}, 22367 {name:'Julie', phone:'555-8765'}, 22368 {name:'Juliette', phone:'555-5678'}]"></div> 22369 22370 <label>Search: <input ng-model="searchText"></label> 22371 <table id="searchTextResults"> 22372 <tr><th>Name</th><th>Phone</th></tr> 22373 <tr ng-repeat="friend in friends | filter:searchText"> 22374 <td>{{friend.name}}</td> 22375 <td>{{friend.phone}}</td> 22376 </tr> 22377 </table> 22378 <hr> 22379 <label>Any: <input ng-model="search.$"></label> <br> 22380 <label>Name only <input ng-model="search.name"></label><br> 22381 <label>Phone only <input ng-model="search.phone"></label><br> 22382 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 22383 <table id="searchObjResults"> 22384 <tr><th>Name</th><th>Phone</th></tr> 22385 <tr ng-repeat="friendObj in friends | filter:search:strict"> 22386 <td>{{friendObj.name}}</td> 22387 <td>{{friendObj.phone}}</td> 22388 </tr> 22389 </table> 22390 </file> 22391 <file name="protractor.js" type="protractor"> 22392 var expectFriendNames = function(expectedNames, key) { 22393 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 22394 arr.forEach(function(wd, i) { 22395 expect(wd.getText()).toMatch(expectedNames[i]); 22396 }); 22397 }); 22398 }; 22399 22400 it('should search across all fields when filtering with a string', function() { 22401 var searchText = element(by.model('searchText')); 22402 searchText.clear(); 22403 searchText.sendKeys('m'); 22404 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 22405 22406 searchText.clear(); 22407 searchText.sendKeys('76'); 22408 expectFriendNames(['John', 'Julie'], 'friend'); 22409 }); 22410 22411 it('should search in specific fields when filtering with a predicate object', function() { 22412 var searchAny = element(by.model('search.$')); 22413 searchAny.clear(); 22414 searchAny.sendKeys('i'); 22415 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 22416 }); 22417 it('should use a equal comparison when comparator is true', function() { 22418 var searchName = element(by.model('search.name')); 22419 var strict = element(by.model('strict')); 22420 searchName.clear(); 22421 searchName.sendKeys('Julie'); 22422 strict.click(); 22423 expectFriendNames(['Julie'], 'friendObj'); 22424 }); 22425 </file> 22426 </example> 22427 */ 22428 22429function filterFilter() { 22430 return function(array, expression, comparator, anyPropertyKey) { 22431 if (!isArrayLike(array)) { 22432 if (array == null) { 22433 return array; 22434 } else { 22435 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 22436 } 22437 } 22438 22439 anyPropertyKey = anyPropertyKey || '$'; 22440 var expressionType = getTypeForFilter(expression); 22441 var predicateFn; 22442 var matchAgainstAnyProp; 22443 22444 switch (expressionType) { 22445 case 'function': 22446 predicateFn = expression; 22447 break; 22448 case 'boolean': 22449 case 'null': 22450 case 'number': 22451 case 'string': 22452 matchAgainstAnyProp = true; 22453 // falls through 22454 case 'object': 22455 predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp); 22456 break; 22457 default: 22458 return array; 22459 } 22460 22461 return Array.prototype.filter.call(array, predicateFn); 22462 }; 22463} 22464 22465// Helper functions for `filterFilter` 22466function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) { 22467 var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression); 22468 var predicateFn; 22469 22470 if (comparator === true) { 22471 comparator = equals; 22472 } else if (!isFunction(comparator)) { 22473 comparator = function(actual, expected) { 22474 if (isUndefined(actual)) { 22475 // No substring matching against `undefined` 22476 return false; 22477 } 22478 if ((actual === null) || (expected === null)) { 22479 // No substring matching against `null`; only match against `null` 22480 return actual === expected; 22481 } 22482 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 22483 // Should not compare primitives against objects, unless they have custom `toString` method 22484 return false; 22485 } 22486 22487 actual = lowercase('' + actual); 22488 expected = lowercase('' + expected); 22489 return actual.indexOf(expected) !== -1; 22490 }; 22491 } 22492 22493 predicateFn = function(item) { 22494 if (shouldMatchPrimitives && !isObject(item)) { 22495 return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false); 22496 } 22497 return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp); 22498 }; 22499 22500 return predicateFn; 22501} 22502 22503function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) { 22504 var actualType = getTypeForFilter(actual); 22505 var expectedType = getTypeForFilter(expected); 22506 22507 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 22508 return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp); 22509 } else if (isArray(actual)) { 22510 // In case `actual` is an array, consider it a match 22511 // if ANY of it's items matches `expected` 22512 return actual.some(function(item) { 22513 return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp); 22514 }); 22515 } 22516 22517 switch (actualType) { 22518 case 'object': 22519 var key; 22520 if (matchAgainstAnyProp) { 22521 for (key in actual) { 22522 // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined 22523 // See: https://github.com/angular/angular.js/issues/15644 22524 if (key.charAt && (key.charAt(0) !== '$') && 22525 deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { 22526 return true; 22527 } 22528 } 22529 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false); 22530 } else if (expectedType === 'object') { 22531 for (key in expected) { 22532 var expectedVal = expected[key]; 22533 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 22534 continue; 22535 } 22536 22537 var matchAnyProperty = key === anyPropertyKey; 22538 var actualVal = matchAnyProperty ? actual : actual[key]; 22539 if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) { 22540 return false; 22541 } 22542 } 22543 return true; 22544 } else { 22545 return comparator(actual, expected); 22546 } 22547 case 'function': 22548 return false; 22549 default: 22550 return comparator(actual, expected); 22551 } 22552} 22553 22554// Used for easily differentiating between `null` and actual `object` 22555function getTypeForFilter(val) { 22556 return (val === null) ? 'null' : typeof val; 22557} 22558 22559var MAX_DIGITS = 22; 22560var DECIMAL_SEP = '.'; 22561var ZERO_CHAR = '0'; 22562 22563/** 22564 * @ngdoc filter 22565 * @name currency 22566 * @kind function 22567 * 22568 * @description 22569 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 22570 * symbol for current locale is used. 22571 * 22572 * @param {number} amount Input to filter. 22573 * @param {string=} symbol Currency symbol or identifier to be displayed. 22574 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 22575 * @returns {string} Formatted number. 22576 * 22577 * 22578 * @example 22579 <example module="currencyExample" name="currency-filter"> 22580 <file name="index.html"> 22581 <script> 22582 angular.module('currencyExample', []) 22583 .controller('ExampleController', ['$scope', function($scope) { 22584 $scope.amount = 1234.56; 22585 }]); 22586 </script> 22587 <div ng-controller="ExampleController"> 22588 <input type="number" ng-model="amount" aria-label="amount"> <br> 22589 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 22590 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span><br> 22591 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 22592 </div> 22593 </file> 22594 <file name="protractor.js" type="protractor"> 22595 it('should init with 1234.56', function() { 22596 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 22597 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 22598 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 22599 }); 22600 it('should update', function() { 22601 if (browser.params.browser === 'safari') { 22602 // Safari does not understand the minus key. See 22603 // https://github.com/angular/protractor/issues/481 22604 return; 22605 } 22606 element(by.model('amount')).clear(); 22607 element(by.model('amount')).sendKeys('-1234'); 22608 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 22609 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 22610 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 22611 }); 22612 </file> 22613 </example> 22614 */ 22615currencyFilter.$inject = ['$locale']; 22616function currencyFilter($locale) { 22617 var formats = $locale.NUMBER_FORMATS; 22618 return function(amount, currencySymbol, fractionSize) { 22619 if (isUndefined(currencySymbol)) { 22620 currencySymbol = formats.CURRENCY_SYM; 22621 } 22622 22623 if (isUndefined(fractionSize)) { 22624 fractionSize = formats.PATTERNS[1].maxFrac; 22625 } 22626 22627 // If the currency symbol is empty, trim whitespace around the symbol 22628 var currencySymbolRe = !currencySymbol ? /\s*\u00A4\s*/g : /\u00A4/g; 22629 22630 // if null or undefined pass it through 22631 return (amount == null) 22632 ? amount 22633 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 22634 replace(currencySymbolRe, currencySymbol); 22635 }; 22636} 22637 22638/** 22639 * @ngdoc filter 22640 * @name number 22641 * @kind function 22642 * 22643 * @description 22644 * Formats a number as text. 22645 * 22646 * If the input is null or undefined, it will just be returned. 22647 * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively. 22648 * If the input is not a number an empty string is returned. 22649 * 22650 * 22651 * @param {number|string} number Number to format. 22652 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 22653 * If this is not provided then the fraction size is computed from the current locale's number 22654 * formatting pattern. In the case of the default locale, it will be 3. 22655 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current 22656 * locale (e.g., in the en_US locale it will have "." as the decimal separator and 22657 * include "," group separators after each third digit). 22658 * 22659 * @example 22660 <example module="numberFilterExample" name="number-filter"> 22661 <file name="index.html"> 22662 <script> 22663 angular.module('numberFilterExample', []) 22664 .controller('ExampleController', ['$scope', function($scope) { 22665 $scope.val = 1234.56789; 22666 }]); 22667 </script> 22668 <div ng-controller="ExampleController"> 22669 <label>Enter number: <input ng-model='val'></label><br> 22670 Default formatting: <span id='number-default'>{{val | number}}</span><br> 22671 No fractions: <span>{{val | number:0}}</span><br> 22672 Negative number: <span>{{-val | number:4}}</span> 22673 </div> 22674 </file> 22675 <file name="protractor.js" type="protractor"> 22676 it('should format numbers', function() { 22677 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 22678 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 22679 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 22680 }); 22681 22682 it('should update', function() { 22683 element(by.model('val')).clear(); 22684 element(by.model('val')).sendKeys('3374.333'); 22685 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 22686 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 22687 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 22688 }); 22689 </file> 22690 </example> 22691 */ 22692numberFilter.$inject = ['$locale']; 22693function numberFilter($locale) { 22694 var formats = $locale.NUMBER_FORMATS; 22695 return function(number, fractionSize) { 22696 22697 // if null or undefined pass it through 22698 return (number == null) 22699 ? number 22700 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 22701 fractionSize); 22702 }; 22703} 22704 22705/** 22706 * Parse a number (as a string) into three components that can be used 22707 * for formatting the number. 22708 * 22709 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 22710 * 22711 * @param {string} numStr The number to parse 22712 * @return {object} An object describing this number, containing the following keys: 22713 * - d : an array of digits containing leading zeros as necessary 22714 * - i : the number of the digits in `d` that are to the left of the decimal point 22715 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 22716 * 22717 */ 22718function parse(numStr) { 22719 var exponent = 0, digits, numberOfIntegerDigits; 22720 var i, j, zeros; 22721 22722 // Decimal point? 22723 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 22724 numStr = numStr.replace(DECIMAL_SEP, ''); 22725 } 22726 22727 // Exponential form? 22728 if ((i = numStr.search(/e/i)) > 0) { 22729 // Work out the exponent. 22730 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 22731 numberOfIntegerDigits += +numStr.slice(i + 1); 22732 numStr = numStr.substring(0, i); 22733 } else if (numberOfIntegerDigits < 0) { 22734 // There was no decimal point or exponent so it is an integer. 22735 numberOfIntegerDigits = numStr.length; 22736 } 22737 22738 // Count the number of leading zeros. 22739 for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ } 22740 22741 if (i === (zeros = numStr.length)) { 22742 // The digits are all zero. 22743 digits = [0]; 22744 numberOfIntegerDigits = 1; 22745 } else { 22746 // Count the number of trailing zeros 22747 zeros--; 22748 while (numStr.charAt(zeros) === ZERO_CHAR) zeros--; 22749 22750 // Trailing zeros are insignificant so ignore them 22751 numberOfIntegerDigits -= i; 22752 digits = []; 22753 // Convert string to array of digits without leading/trailing zeros. 22754 for (j = 0; i <= zeros; i++, j++) { 22755 digits[j] = +numStr.charAt(i); 22756 } 22757 } 22758 22759 // If the number overflows the maximum allowed digits then use an exponent. 22760 if (numberOfIntegerDigits > MAX_DIGITS) { 22761 digits = digits.splice(0, MAX_DIGITS - 1); 22762 exponent = numberOfIntegerDigits - 1; 22763 numberOfIntegerDigits = 1; 22764 } 22765 22766 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 22767} 22768 22769/** 22770 * Round the parsed number to the specified number of decimal places 22771 * This function changed the parsedNumber in-place 22772 */ 22773function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 22774 var digits = parsedNumber.d; 22775 var fractionLen = digits.length - parsedNumber.i; 22776 22777 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 22778 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 22779 22780 // The index of the digit to where rounding is to occur 22781 var roundAt = fractionSize + parsedNumber.i; 22782 var digit = digits[roundAt]; 22783 22784 if (roundAt > 0) { 22785 // Drop fractional digits beyond `roundAt` 22786 digits.splice(Math.max(parsedNumber.i, roundAt)); 22787 22788 // Set non-fractional digits beyond `roundAt` to 0 22789 for (var j = roundAt; j < digits.length; j++) { 22790 digits[j] = 0; 22791 } 22792 } else { 22793 // We rounded to zero so reset the parsedNumber 22794 fractionLen = Math.max(0, fractionLen); 22795 parsedNumber.i = 1; 22796 digits.length = Math.max(1, roundAt = fractionSize + 1); 22797 digits[0] = 0; 22798 for (var i = 1; i < roundAt; i++) digits[i] = 0; 22799 } 22800 22801 if (digit >= 5) { 22802 if (roundAt - 1 < 0) { 22803 for (var k = 0; k > roundAt; k--) { 22804 digits.unshift(0); 22805 parsedNumber.i++; 22806 } 22807 digits.unshift(1); 22808 parsedNumber.i++; 22809 } else { 22810 digits[roundAt - 1]++; 22811 } 22812 } 22813 22814 // Pad out with zeros to get the required fraction length 22815 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); 22816 22817 22818 // Do any carrying, e.g. a digit was rounded up to 10 22819 var carry = digits.reduceRight(function(carry, d, i, digits) { 22820 d = d + carry; 22821 digits[i] = d % 10; 22822 return Math.floor(d / 10); 22823 }, 0); 22824 if (carry) { 22825 digits.unshift(carry); 22826 parsedNumber.i++; 22827 } 22828} 22829 22830/** 22831 * Format a number into a string 22832 * @param {number} number The number to format 22833 * @param {{ 22834 * minFrac, // the minimum number of digits required in the fraction part of the number 22835 * maxFrac, // the maximum number of digits required in the fraction part of the number 22836 * gSize, // number of digits in each group of separated digits 22837 * lgSize, // number of digits in the last group of digits before the decimal separator 22838 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 22839 * posPre, // the string to go in front of a positive number 22840 * negSuf, // the string to go after a negative number (e.g. `)`) 22841 * posSuf // the string to go after a positive number 22842 * }} pattern 22843 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 22844 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 22845 * @param {[type]} fractionSize The size of the fractional part of the number 22846 * @return {string} The number formatted as a string 22847 */ 22848function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 22849 22850 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 22851 22852 var isInfinity = !isFinite(number); 22853 var isZero = false; 22854 var numStr = Math.abs(number) + '', 22855 formattedText = '', 22856 parsedNumber; 22857 22858 if (isInfinity) { 22859 formattedText = '\u221e'; 22860 } else { 22861 parsedNumber = parse(numStr); 22862 22863 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 22864 22865 var digits = parsedNumber.d; 22866 var integerLen = parsedNumber.i; 22867 var exponent = parsedNumber.e; 22868 var decimals = []; 22869 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 22870 22871 // pad zeros for small numbers 22872 while (integerLen < 0) { 22873 digits.unshift(0); 22874 integerLen++; 22875 } 22876 22877 // extract decimals digits 22878 if (integerLen > 0) { 22879 decimals = digits.splice(integerLen, digits.length); 22880 } else { 22881 decimals = digits; 22882 digits = [0]; 22883 } 22884 22885 // format the integer digits with grouping separators 22886 var groups = []; 22887 if (digits.length >= pattern.lgSize) { 22888 groups.unshift(digits.splice(-pattern.lgSize, digits.length).join('')); 22889 } 22890 while (digits.length > pattern.gSize) { 22891 groups.unshift(digits.splice(-pattern.gSize, digits.length).join('')); 22892 } 22893 if (digits.length) { 22894 groups.unshift(digits.join('')); 22895 } 22896 formattedText = groups.join(groupSep); 22897 22898 // append the decimal digits 22899 if (decimals.length) { 22900 formattedText += decimalSep + decimals.join(''); 22901 } 22902 22903 if (exponent) { 22904 formattedText += 'e+' + exponent; 22905 } 22906 } 22907 if (number < 0 && !isZero) { 22908 return pattern.negPre + formattedText + pattern.negSuf; 22909 } else { 22910 return pattern.posPre + formattedText + pattern.posSuf; 22911 } 22912} 22913 22914function padNumber(num, digits, trim, negWrap) { 22915 var neg = ''; 22916 if (num < 0 || (negWrap && num <= 0)) { 22917 if (negWrap) { 22918 num = -num + 1; 22919 } else { 22920 num = -num; 22921 neg = '-'; 22922 } 22923 } 22924 num = '' + num; 22925 while (num.length < digits) num = ZERO_CHAR + num; 22926 if (trim) { 22927 num = num.substr(num.length - digits); 22928 } 22929 return neg + num; 22930} 22931 22932 22933function dateGetter(name, size, offset, trim, negWrap) { 22934 offset = offset || 0; 22935 return function(date) { 22936 var value = date['get' + name](); 22937 if (offset > 0 || value > -offset) { 22938 value += offset; 22939 } 22940 if (value === 0 && offset === -12) value = 12; 22941 return padNumber(value, size, trim, negWrap); 22942 }; 22943} 22944 22945function dateStrGetter(name, shortForm, standAlone) { 22946 return function(date, formats) { 22947 var value = date['get' + name](); 22948 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); 22949 var get = uppercase(propPrefix + name); 22950 22951 return formats[get][value]; 22952 }; 22953} 22954 22955function timeZoneGetter(date, formats, offset) { 22956 var zone = -1 * offset; 22957 var paddedZone = (zone >= 0) ? '+' : ''; 22958 22959 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 22960 padNumber(Math.abs(zone % 60), 2); 22961 22962 return paddedZone; 22963} 22964 22965function getFirstThursdayOfYear(year) { 22966 // 0 = index of January 22967 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 22968 // 4 = index of Thursday (+1 to account for 1st = 5) 22969 // 11 = index of *next* Thursday (+1 account for 1st = 12) 22970 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 22971} 22972 22973function getThursdayThisWeek(datetime) { 22974 return new Date(datetime.getFullYear(), datetime.getMonth(), 22975 // 4 = index of Thursday 22976 datetime.getDate() + (4 - datetime.getDay())); 22977} 22978 22979function weekGetter(size) { 22980 return function(date) { 22981 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 22982 thisThurs = getThursdayThisWeek(date); 22983 22984 var diff = +thisThurs - +firstThurs, 22985 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 22986 22987 return padNumber(result, size); 22988 }; 22989} 22990 22991function ampmGetter(date, formats) { 22992 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 22993} 22994 22995function eraGetter(date, formats) { 22996 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 22997} 22998 22999function longEraGetter(date, formats) { 23000 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 23001} 23002 23003var DATE_FORMATS = { 23004 yyyy: dateGetter('FullYear', 4, 0, false, true), 23005 yy: dateGetter('FullYear', 2, 0, true, true), 23006 y: dateGetter('FullYear', 1, 0, false, true), 23007 MMMM: dateStrGetter('Month'), 23008 MMM: dateStrGetter('Month', true), 23009 MM: dateGetter('Month', 2, 1), 23010 M: dateGetter('Month', 1, 1), 23011 LLLL: dateStrGetter('Month', false, true), 23012 dd: dateGetter('Date', 2), 23013 d: dateGetter('Date', 1), 23014 HH: dateGetter('Hours', 2), 23015 H: dateGetter('Hours', 1), 23016 hh: dateGetter('Hours', 2, -12), 23017 h: dateGetter('Hours', 1, -12), 23018 mm: dateGetter('Minutes', 2), 23019 m: dateGetter('Minutes', 1), 23020 ss: dateGetter('Seconds', 2), 23021 s: dateGetter('Seconds', 1), 23022 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 23023 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 23024 sss: dateGetter('Milliseconds', 3), 23025 EEEE: dateStrGetter('Day'), 23026 EEE: dateStrGetter('Day', true), 23027 a: ampmGetter, 23028 Z: timeZoneGetter, 23029 ww: weekGetter(2), 23030 w: weekGetter(1), 23031 G: eraGetter, 23032 GG: eraGetter, 23033 GGG: eraGetter, 23034 GGGG: longEraGetter 23035}; 23036 23037var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/, 23038 NUMBER_STRING = /^-?\d+$/; 23039 23040/** 23041 * @ngdoc filter 23042 * @name date 23043 * @kind function 23044 * 23045 * @description 23046 * Formats `date` to a string based on the requested `format`. 23047 * 23048 * `format` string can be composed of the following elements: 23049 * 23050 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 23051 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 23052 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 23053 * * `'MMMM'`: Month in year (January-December) 23054 * * `'MMM'`: Month in year (Jan-Dec) 23055 * * `'MM'`: Month in year, padded (01-12) 23056 * * `'M'`: Month in year (1-12) 23057 * * `'LLLL'`: Stand-alone month in year (January-December) 23058 * * `'dd'`: Day in month, padded (01-31) 23059 * * `'d'`: Day in month (1-31) 23060 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 23061 * * `'EEE'`: Day in Week, (Sun-Sat) 23062 * * `'HH'`: Hour in day, padded (00-23) 23063 * * `'H'`: Hour in day (0-23) 23064 * * `'hh'`: Hour in AM/PM, padded (01-12) 23065 * * `'h'`: Hour in AM/PM, (1-12) 23066 * * `'mm'`: Minute in hour, padded (00-59) 23067 * * `'m'`: Minute in hour (0-59) 23068 * * `'ss'`: Second in minute, padded (00-59) 23069 * * `'s'`: Second in minute (0-59) 23070 * * `'sss'`: Millisecond in second, padded (000-999) 23071 * * `'a'`: AM/PM marker 23072 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 23073 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 23074 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 23075 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 23076 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 23077 * 23078 * `format` string can also be one of the following predefined 23079 * {@link guide/i18n localizable formats}: 23080 * 23081 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 23082 * (e.g. Sep 3, 2010 12:05:08 PM) 23083 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 23084 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 23085 * (e.g. Friday, September 3, 2010) 23086 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 23087 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 23088 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 23089 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 23090 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 23091 * 23092 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 23093 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 23094 * (e.g. `"h 'o''clock'"`). 23095 * 23096 * Any other characters in the `format` string will be output as-is. 23097 * 23098 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 23099 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 23100 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 23101 * specified in the string input, the time is considered to be in the local timezone. 23102 * @param {string=} format Formatting rules (see Description). If not specified, 23103 * `mediumDate` is used. 23104 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 23105 * continental US time zone abbreviations, but for general use, use a time zone offset, for 23106 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 23107 * If not specified, the timezone of the browser will be used. 23108 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 23109 * 23110 * @example 23111 <example name="filter-date"> 23112 <file name="index.html"> 23113 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 23114 <span>{{1288323623006 | date:'medium'}}</span><br> 23115 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 23116 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 23117 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 23118 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 23119 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 23120 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 23121 </file> 23122 <file name="protractor.js" type="protractor"> 23123 it('should format date', function() { 23124 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 23125 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 23126 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 23127 toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/); 23128 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 23129 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 23130 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 23131 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 23132 }); 23133 </file> 23134 </example> 23135 */ 23136dateFilter.$inject = ['$locale']; 23137function dateFilter($locale) { 23138 23139 23140 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 23141 // 1 2 3 4 5 6 7 8 9 10 11 23142 function jsonStringToDate(string) { 23143 var match; 23144 if ((match = string.match(R_ISO8601_STR))) { 23145 var date = new Date(0), 23146 tzHour = 0, 23147 tzMin = 0, 23148 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 23149 timeSetter = match[8] ? date.setUTCHours : date.setHours; 23150 23151 if (match[9]) { 23152 tzHour = toInt(match[9] + match[10]); 23153 tzMin = toInt(match[9] + match[11]); 23154 } 23155 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 23156 var h = toInt(match[4] || 0) - tzHour; 23157 var m = toInt(match[5] || 0) - tzMin; 23158 var s = toInt(match[6] || 0); 23159 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 23160 timeSetter.call(date, h, m, s, ms); 23161 return date; 23162 } 23163 return string; 23164 } 23165 23166 23167 return function(date, format, timezone) { 23168 var text = '', 23169 parts = [], 23170 fn, match; 23171 23172 format = format || 'mediumDate'; 23173 format = $locale.DATETIME_FORMATS[format] || format; 23174 if (isString(date)) { 23175 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 23176 } 23177 23178 if (isNumber(date)) { 23179 date = new Date(date); 23180 } 23181 23182 if (!isDate(date) || !isFinite(date.getTime())) { 23183 return date; 23184 } 23185 23186 while (format) { 23187 match = DATE_FORMATS_SPLIT.exec(format); 23188 if (match) { 23189 parts = concat(parts, match, 1); 23190 format = parts.pop(); 23191 } else { 23192 parts.push(format); 23193 format = null; 23194 } 23195 } 23196 23197 var dateTimezoneOffset = date.getTimezoneOffset(); 23198 if (timezone) { 23199 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 23200 date = convertTimezoneToLocal(date, timezone, true); 23201 } 23202 forEach(parts, function(value) { 23203 fn = DATE_FORMATS[value]; 23204 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 23205 : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\''); 23206 }); 23207 23208 return text; 23209 }; 23210} 23211 23212 23213/** 23214 * @ngdoc filter 23215 * @name json 23216 * @kind function 23217 * 23218 * @description 23219 * Allows you to convert a JavaScript object into JSON string. 23220 * 23221 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 23222 * the binding is automatically converted to JSON. 23223 * 23224 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 23225 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. 23226 * @returns {string} JSON string. 23227 * 23228 * 23229 * @example 23230 <example name="filter-json"> 23231 <file name="index.html"> 23232 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 23233 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 23234 </file> 23235 <file name="protractor.js" type="protractor"> 23236 it('should jsonify filtered objects', function() { 23237 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/); 23238 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/); 23239 }); 23240 </file> 23241 </example> 23242 * 23243 */ 23244function jsonFilter() { 23245 return function(object, spacing) { 23246 if (isUndefined(spacing)) { 23247 spacing = 2; 23248 } 23249 return toJson(object, spacing); 23250 }; 23251} 23252 23253 23254/** 23255 * @ngdoc filter 23256 * @name lowercase 23257 * @kind function 23258 * @description 23259 * Converts string to lowercase. 23260 * 23261 * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example. 23262 * 23263 * @see angular.lowercase 23264 */ 23265var lowercaseFilter = valueFn(lowercase); 23266 23267 23268/** 23269 * @ngdoc filter 23270 * @name uppercase 23271 * @kind function 23272 * @description 23273 * Converts string to uppercase. 23274 * @example 23275 <example module="uppercaseFilterExample" name="filter-uppercase"> 23276 <file name="index.html"> 23277 <script> 23278 angular.module('uppercaseFilterExample', []) 23279 .controller('ExampleController', ['$scope', function($scope) { 23280 $scope.title = 'This is a title'; 23281 }]); 23282 </script> 23283 <div ng-controller="ExampleController"> 23284 <!-- This title should be formatted normally --> 23285 <h1>{{title}}</h1> 23286 <!-- This title should be capitalized --> 23287 <h1>{{title | uppercase}}</h1> 23288 </div> 23289 </file> 23290 </example> 23291 */ 23292var uppercaseFilter = valueFn(uppercase); 23293 23294/** 23295 * @ngdoc filter 23296 * @name limitTo 23297 * @kind function 23298 * 23299 * @description 23300 * Creates a new array or string containing only a specified number of elements. The elements are 23301 * taken from either the beginning or the end of the source array, string or number, as specified by 23302 * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported 23303 * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input, 23304 * it is converted to a string. 23305 * 23306 * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited. 23307 * @param {string|number} limit - The length of the returned array or string. If the `limit` number 23308 * is positive, `limit` number of items from the beginning of the source array/string are copied. 23309 * If the number is negative, `limit` number of items from the end of the source array/string 23310 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 23311 * the input will be returned unchanged. 23312 * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index, 23313 * `begin` indicates an offset from the end of `input`. Defaults to `0`. 23314 * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had 23315 * less than `limit` elements. 23316 * 23317 * @example 23318 <example module="limitToExample" name="limit-to-filter"> 23319 <file name="index.html"> 23320 <script> 23321 angular.module('limitToExample', []) 23322 .controller('ExampleController', ['$scope', function($scope) { 23323 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 23324 $scope.letters = "abcdefghi"; 23325 $scope.longNumber = 2345432342; 23326 $scope.numLimit = 3; 23327 $scope.letterLimit = 3; 23328 $scope.longNumberLimit = 3; 23329 }]); 23330 </script> 23331 <div ng-controller="ExampleController"> 23332 <label> 23333 Limit {{numbers}} to: 23334 <input type="number" step="1" ng-model="numLimit"> 23335 </label> 23336 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 23337 <label> 23338 Limit {{letters}} to: 23339 <input type="number" step="1" ng-model="letterLimit"> 23340 </label> 23341 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 23342 <label> 23343 Limit {{longNumber}} to: 23344 <input type="number" step="1" ng-model="longNumberLimit"> 23345 </label> 23346 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 23347 </div> 23348 </file> 23349 <file name="protractor.js" type="protractor"> 23350 var numLimitInput = element(by.model('numLimit')); 23351 var letterLimitInput = element(by.model('letterLimit')); 23352 var longNumberLimitInput = element(by.model('longNumberLimit')); 23353 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 23354 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 23355 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 23356 23357 it('should limit the number array to first three items', function() { 23358 expect(numLimitInput.getAttribute('value')).toBe('3'); 23359 expect(letterLimitInput.getAttribute('value')).toBe('3'); 23360 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 23361 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 23362 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 23363 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 23364 }); 23365 23366 // There is a bug in safari and protractor that doesn't like the minus key 23367 // it('should update the output when -3 is entered', function() { 23368 // numLimitInput.clear(); 23369 // numLimitInput.sendKeys('-3'); 23370 // letterLimitInput.clear(); 23371 // letterLimitInput.sendKeys('-3'); 23372 // longNumberLimitInput.clear(); 23373 // longNumberLimitInput.sendKeys('-3'); 23374 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 23375 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 23376 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 23377 // }); 23378 23379 it('should not exceed the maximum size of input array', function() { 23380 numLimitInput.clear(); 23381 numLimitInput.sendKeys('100'); 23382 letterLimitInput.clear(); 23383 letterLimitInput.sendKeys('100'); 23384 longNumberLimitInput.clear(); 23385 longNumberLimitInput.sendKeys('100'); 23386 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 23387 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 23388 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 23389 }); 23390 </file> 23391 </example> 23392*/ 23393function limitToFilter() { 23394 return function(input, limit, begin) { 23395 if (Math.abs(Number(limit)) === Infinity) { 23396 limit = Number(limit); 23397 } else { 23398 limit = toInt(limit); 23399 } 23400 if (isNumberNaN(limit)) return input; 23401 23402 if (isNumber(input)) input = input.toString(); 23403 if (!isArrayLike(input)) return input; 23404 23405 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 23406 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 23407 23408 if (limit >= 0) { 23409 return sliceFn(input, begin, begin + limit); 23410 } else { 23411 if (begin === 0) { 23412 return sliceFn(input, limit, input.length); 23413 } else { 23414 return sliceFn(input, Math.max(0, begin + limit), begin); 23415 } 23416 } 23417 }; 23418} 23419 23420function sliceFn(input, begin, end) { 23421 if (isString(input)) return input.slice(begin, end); 23422 23423 return slice.call(input, begin, end); 23424} 23425 23426/** 23427 * @ngdoc filter 23428 * @name orderBy 23429 * @kind function 23430 * 23431 * @description 23432 * Returns an array containing the items from the specified `collection`, ordered by a `comparator` 23433 * function based on the values computed using the `expression` predicate. 23434 * 23435 * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in 23436 * `[{id: 'bar'}, {id: 'foo'}]`. 23437 * 23438 * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray, 23439 * String, etc). 23440 * 23441 * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker 23442 * for the preceding one. The `expression` is evaluated against each item and the output is used 23443 * for comparing with other items. 23444 * 23445 * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in 23446 * ascending order. 23447 * 23448 * The comparison is done using the `comparator` function. If none is specified, a default, built-in 23449 * comparator is used (see below for details - in a nutshell, it compares numbers numerically and 23450 * strings alphabetically). 23451 * 23452 * ### Under the hood 23453 * 23454 * Ordering the specified `collection` happens in two phases: 23455 * 23456 * 1. All items are passed through the predicate (or predicates), and the returned values are saved 23457 * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed 23458 * through a predicate that extracts the value of the `label` property, would be transformed to: 23459 * ``` 23460 * { 23461 * value: 'foo', 23462 * type: 'string', 23463 * index: ... 23464 * } 23465 * ``` 23466 * **Note:** `null` values use `'null'` as their type. 23467 * 2. The comparator function is used to sort the items, based on the derived values, types and 23468 * indices. 23469 * 23470 * If you use a custom comparator, it will be called with pairs of objects of the form 23471 * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal 23472 * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the 23473 * second, or `1` otherwise. 23474 * 23475 * In order to ensure that the sorting will be deterministic across platforms, if none of the 23476 * specified predicates can distinguish between two items, `orderBy` will automatically introduce a 23477 * dummy predicate that returns the item's index as `value`. 23478 * (If you are using a custom comparator, make sure it can handle this predicate as well.) 23479 * 23480 * If a custom comparator still can't distinguish between two items, then they will be sorted based 23481 * on their index using the built-in comparator. 23482 * 23483 * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted 23484 * value for an item, `orderBy` will try to convert that object to a primitive value, before passing 23485 * it to the comparator. The following rules govern the conversion: 23486 * 23487 * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be 23488 * used instead.<br /> 23489 * (If the object has a `valueOf()` method that returns another object, then the returned object 23490 * will be used in subsequent steps.) 23491 * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that 23492 * returns a primitive, its return value will be used instead.<br /> 23493 * (If the object has a `toString()` method that returns another object, then the returned object 23494 * will be used in subsequent steps.) 23495 * 3. No conversion; the object itself is used. 23496 * 23497 * ### The default comparator 23498 * 23499 * The default, built-in comparator should be sufficient for most usecases. In short, it compares 23500 * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to 23501 * using their index in the original collection, sorts values of different types by type and puts 23502 * `undefined` and `null` values at the end of the sorted list. 23503 * 23504 * More specifically, it follows these steps to determine the relative order of items: 23505 * 23506 * 1. If the compared values are of different types: 23507 * - If one of the values is undefined, consider it "greater than" the other. 23508 * - Else if one of the values is null, consider it "greater than" the other. 23509 * - Else compare the types themselves alphabetically. 23510 * 2. If both values are of type `string`, compare them alphabetically in a case- and 23511 * locale-insensitive way. 23512 * 3. If both values are objects, compare their indices instead. 23513 * 4. Otherwise, return: 23514 * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`). 23515 * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator). 23516 * - `1`, otherwise. 23517 * 23518 * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being 23519 * saved as numbers and not strings. 23520 * **Note:** For the purpose of sorting, `null` and `undefined` are considered "greater than" 23521 * any other value (with undefined "greater than" null). This effectively means that `null` 23522 * and `undefined` values end up at the end of a list sorted in ascending order. 23523 * **Note:** `null` values use `'null'` as their type to be able to distinguish them from objects. 23524 * 23525 * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort. 23526 * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of 23527 * predicates) to be used by the comparator to determine the order of elements. 23528 * 23529 * Can be one of: 23530 * 23531 * - `Function`: A getter function. This function will be called with each item as argument and 23532 * the return value will be used for sorting. 23533 * - `string`: An AngularJS expression. This expression will be evaluated against each item and the 23534 * result will be used for sorting. For example, use `'label'` to sort by a property called 23535 * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label` 23536 * property.<br /> 23537 * (The result of a constant expression is interpreted as a property name to be used for 23538 * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a 23539 * property called `special name`.)<br /> 23540 * An expression can be optionally prefixed with `+` or `-` to control the sorting direction, 23541 * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided, 23542 * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons. 23543 * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the 23544 * relative order of two items, the next predicate is used as a tie-breaker. 23545 * 23546 * **Note:** If the predicate is missing or empty then it defaults to `'+'`. 23547 * 23548 * @param {boolean=} reverse - If `true`, reverse the sorting order. 23549 * @param {(Function)=} comparator - The comparator function used to determine the relative order of 23550 * value pairs. If omitted, the built-in comparator will be used. 23551 * 23552 * @returns {Array} - The sorted array. 23553 * 23554 * 23555 * @example 23556 * ### Ordering a table with `ngRepeat` 23557 * 23558 * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by 23559 * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means 23560 * it defaults to the built-in comparator. 23561 * 23562 <example name="orderBy-static" module="orderByExample1"> 23563 <file name="index.html"> 23564 <div ng-controller="ExampleController"> 23565 <table class="friends"> 23566 <tr> 23567 <th>Name</th> 23568 <th>Phone Number</th> 23569 <th>Age</th> 23570 </tr> 23571 <tr ng-repeat="friend in friends | orderBy:'-age'"> 23572 <td>{{friend.name}}</td> 23573 <td>{{friend.phone}}</td> 23574 <td>{{friend.age}}</td> 23575 </tr> 23576 </table> 23577 </div> 23578 </file> 23579 <file name="script.js"> 23580 angular.module('orderByExample1', []) 23581 .controller('ExampleController', ['$scope', function($scope) { 23582 $scope.friends = [ 23583 {name: 'John', phone: '555-1212', age: 10}, 23584 {name: 'Mary', phone: '555-9876', age: 19}, 23585 {name: 'Mike', phone: '555-4321', age: 21}, 23586 {name: 'Adam', phone: '555-5678', age: 35}, 23587 {name: 'Julie', phone: '555-8765', age: 29} 23588 ]; 23589 }]); 23590 </file> 23591 <file name="style.css"> 23592 .friends { 23593 border-collapse: collapse; 23594 } 23595 23596 .friends th { 23597 border-bottom: 1px solid; 23598 } 23599 .friends td, .friends th { 23600 border-left: 1px solid; 23601 padding: 5px 10px; 23602 } 23603 .friends td:first-child, .friends th:first-child { 23604 border-left: none; 23605 } 23606 </file> 23607 <file name="protractor.js" type="protractor"> 23608 // Element locators 23609 var names = element.all(by.repeater('friends').column('friend.name')); 23610 23611 it('should sort friends by age in reverse order', function() { 23612 expect(names.get(0).getText()).toBe('Adam'); 23613 expect(names.get(1).getText()).toBe('Julie'); 23614 expect(names.get(2).getText()).toBe('Mike'); 23615 expect(names.get(3).getText()).toBe('Mary'); 23616 expect(names.get(4).getText()).toBe('John'); 23617 }); 23618 </file> 23619 </example> 23620 * <hr /> 23621 * 23622 * @example 23623 * ### Changing parameters dynamically 23624 * 23625 * All parameters can be changed dynamically. The next example shows how you can make the columns of 23626 * a table sortable, by binding the `expression` and `reverse` parameters to scope properties. 23627 * 23628 <example name="orderBy-dynamic" module="orderByExample2"> 23629 <file name="index.html"> 23630 <div ng-controller="ExampleController"> 23631 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre> 23632 <hr/> 23633 <button ng-click="propertyName = null; reverse = false">Set to unsorted</button> 23634 <hr/> 23635 <table class="friends"> 23636 <tr> 23637 <th> 23638 <button ng-click="sortBy('name')">Name</button> 23639 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span> 23640 </th> 23641 <th> 23642 <button ng-click="sortBy('phone')">Phone Number</button> 23643 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span> 23644 </th> 23645 <th> 23646 <button ng-click="sortBy('age')">Age</button> 23647 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span> 23648 </th> 23649 </tr> 23650 <tr ng-repeat="friend in friends | orderBy:propertyName:reverse"> 23651 <td>{{friend.name}}</td> 23652 <td>{{friend.phone}}</td> 23653 <td>{{friend.age}}</td> 23654 </tr> 23655 </table> 23656 </div> 23657 </file> 23658 <file name="script.js"> 23659 angular.module('orderByExample2', []) 23660 .controller('ExampleController', ['$scope', function($scope) { 23661 var friends = [ 23662 {name: 'John', phone: '555-1212', age: 10}, 23663 {name: 'Mary', phone: '555-9876', age: 19}, 23664 {name: 'Mike', phone: '555-4321', age: 21}, 23665 {name: 'Adam', phone: '555-5678', age: 35}, 23666 {name: 'Julie', phone: '555-8765', age: 29} 23667 ]; 23668 23669 $scope.propertyName = 'age'; 23670 $scope.reverse = true; 23671 $scope.friends = friends; 23672 23673 $scope.sortBy = function(propertyName) { 23674 $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; 23675 $scope.propertyName = propertyName; 23676 }; 23677 }]); 23678 </file> 23679 <file name="style.css"> 23680 .friends { 23681 border-collapse: collapse; 23682 } 23683 23684 .friends th { 23685 border-bottom: 1px solid; 23686 } 23687 .friends td, .friends th { 23688 border-left: 1px solid; 23689 padding: 5px 10px; 23690 } 23691 .friends td:first-child, .friends th:first-child { 23692 border-left: none; 23693 } 23694 23695 .sortorder:after { 23696 content: '\25b2'; // BLACK UP-POINTING TRIANGLE 23697 } 23698 .sortorder.reverse:after { 23699 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE 23700 } 23701 </file> 23702 <file name="protractor.js" type="protractor"> 23703 // Element locators 23704 var unsortButton = element(by.partialButtonText('unsorted')); 23705 var nameHeader = element(by.partialButtonText('Name')); 23706 var phoneHeader = element(by.partialButtonText('Phone')); 23707 var ageHeader = element(by.partialButtonText('Age')); 23708 var firstName = element(by.repeater('friends').column('friend.name').row(0)); 23709 var lastName = element(by.repeater('friends').column('friend.name').row(4)); 23710 23711 it('should sort friends by some property, when clicking on the column header', function() { 23712 expect(firstName.getText()).toBe('Adam'); 23713 expect(lastName.getText()).toBe('John'); 23714 23715 phoneHeader.click(); 23716 expect(firstName.getText()).toBe('John'); 23717 expect(lastName.getText()).toBe('Mary'); 23718 23719 nameHeader.click(); 23720 expect(firstName.getText()).toBe('Adam'); 23721 expect(lastName.getText()).toBe('Mike'); 23722 23723 ageHeader.click(); 23724 expect(firstName.getText()).toBe('John'); 23725 expect(lastName.getText()).toBe('Adam'); 23726 }); 23727 23728 it('should sort friends in reverse order, when clicking on the same column', function() { 23729 expect(firstName.getText()).toBe('Adam'); 23730 expect(lastName.getText()).toBe('John'); 23731 23732 ageHeader.click(); 23733 expect(firstName.getText()).toBe('John'); 23734 expect(lastName.getText()).toBe('Adam'); 23735 23736 ageHeader.click(); 23737 expect(firstName.getText()).toBe('Adam'); 23738 expect(lastName.getText()).toBe('John'); 23739 }); 23740 23741 it('should restore the original order, when clicking "Set to unsorted"', function() { 23742 expect(firstName.getText()).toBe('Adam'); 23743 expect(lastName.getText()).toBe('John'); 23744 23745 unsortButton.click(); 23746 expect(firstName.getText()).toBe('John'); 23747 expect(lastName.getText()).toBe('Julie'); 23748 }); 23749 </file> 23750 </example> 23751 * <hr /> 23752 * 23753 * @example 23754 * ### Using `orderBy` inside a controller 23755 * 23756 * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and 23757 * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory 23758 * and retrieve the `orderBy` filter with `$filter('orderBy')`.) 23759 * 23760 <example name="orderBy-call-manually" module="orderByExample3"> 23761 <file name="index.html"> 23762 <div ng-controller="ExampleController"> 23763 <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre> 23764 <hr/> 23765 <button ng-click="sortBy(null)">Set to unsorted</button> 23766 <hr/> 23767 <table class="friends"> 23768 <tr> 23769 <th> 23770 <button ng-click="sortBy('name')">Name</button> 23771 <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span> 23772 </th> 23773 <th> 23774 <button ng-click="sortBy('phone')">Phone Number</button> 23775 <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span> 23776 </th> 23777 <th> 23778 <button ng-click="sortBy('age')">Age</button> 23779 <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span> 23780 </th> 23781 </tr> 23782 <tr ng-repeat="friend in friends"> 23783 <td>{{friend.name}}</td> 23784 <td>{{friend.phone}}</td> 23785 <td>{{friend.age}}</td> 23786 </tr> 23787 </table> 23788 </div> 23789 </file> 23790 <file name="script.js"> 23791 angular.module('orderByExample3', []) 23792 .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) { 23793 var friends = [ 23794 {name: 'John', phone: '555-1212', age: 10}, 23795 {name: 'Mary', phone: '555-9876', age: 19}, 23796 {name: 'Mike', phone: '555-4321', age: 21}, 23797 {name: 'Adam', phone: '555-5678', age: 35}, 23798 {name: 'Julie', phone: '555-8765', age: 29} 23799 ]; 23800 23801 $scope.propertyName = 'age'; 23802 $scope.reverse = true; 23803 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); 23804 23805 $scope.sortBy = function(propertyName) { 23806 $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName) 23807 ? !$scope.reverse : false; 23808 $scope.propertyName = propertyName; 23809 $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); 23810 }; 23811 }]); 23812 </file> 23813 <file name="style.css"> 23814 .friends { 23815 border-collapse: collapse; 23816 } 23817 23818 .friends th { 23819 border-bottom: 1px solid; 23820 } 23821 .friends td, .friends th { 23822 border-left: 1px solid; 23823 padding: 5px 10px; 23824 } 23825 .friends td:first-child, .friends th:first-child { 23826 border-left: none; 23827 } 23828 23829 .sortorder:after { 23830 content: '\25b2'; // BLACK UP-POINTING TRIANGLE 23831 } 23832 .sortorder.reverse:after { 23833 content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE 23834 } 23835 </file> 23836 <file name="protractor.js" type="protractor"> 23837 // Element locators 23838 var unsortButton = element(by.partialButtonText('unsorted')); 23839 var nameHeader = element(by.partialButtonText('Name')); 23840 var phoneHeader = element(by.partialButtonText('Phone')); 23841 var ageHeader = element(by.partialButtonText('Age')); 23842 var firstName = element(by.repeater('friends').column('friend.name').row(0)); 23843 var lastName = element(by.repeater('friends').column('friend.name').row(4)); 23844 23845 it('should sort friends by some property, when clicking on the column header', function() { 23846 expect(firstName.getText()).toBe('Adam'); 23847 expect(lastName.getText()).toBe('John'); 23848 23849 phoneHeader.click(); 23850 expect(firstName.getText()).toBe('John'); 23851 expect(lastName.getText()).toBe('Mary'); 23852 23853 nameHeader.click(); 23854 expect(firstName.getText()).toBe('Adam'); 23855 expect(lastName.getText()).toBe('Mike'); 23856 23857 ageHeader.click(); 23858 expect(firstName.getText()).toBe('John'); 23859 expect(lastName.getText()).toBe('Adam'); 23860 }); 23861 23862 it('should sort friends in reverse order, when clicking on the same column', function() { 23863 expect(firstName.getText()).toBe('Adam'); 23864 expect(lastName.getText()).toBe('John'); 23865 23866 ageHeader.click(); 23867 expect(firstName.getText()).toBe('John'); 23868 expect(lastName.getText()).toBe('Adam'); 23869 23870 ageHeader.click(); 23871 expect(firstName.getText()).toBe('Adam'); 23872 expect(lastName.getText()).toBe('John'); 23873 }); 23874 23875 it('should restore the original order, when clicking "Set to unsorted"', function() { 23876 expect(firstName.getText()).toBe('Adam'); 23877 expect(lastName.getText()).toBe('John'); 23878 23879 unsortButton.click(); 23880 expect(firstName.getText()).toBe('John'); 23881 expect(lastName.getText()).toBe('Julie'); 23882 }); 23883 </file> 23884 </example> 23885 * <hr /> 23886 * 23887 * @example 23888 * ### Using a custom comparator 23889 * 23890 * If you have very specific requirements about the way items are sorted, you can pass your own 23891 * comparator function. For example, you might need to compare some strings in a locale-sensitive 23892 * way. (When specifying a custom comparator, you also need to pass a value for the `reverse` 23893 * argument - passing `false` retains the default sorting order, i.e. ascending.) 23894 * 23895 <example name="orderBy-custom-comparator" module="orderByExample4"> 23896 <file name="index.html"> 23897 <div ng-controller="ExampleController"> 23898 <div class="friends-container custom-comparator"> 23899 <h3>Locale-sensitive Comparator</h3> 23900 <table class="friends"> 23901 <tr> 23902 <th>Name</th> 23903 <th>Favorite Letter</th> 23904 </tr> 23905 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator"> 23906 <td>{{friend.name}}</td> 23907 <td>{{friend.favoriteLetter}}</td> 23908 </tr> 23909 </table> 23910 </div> 23911 <div class="friends-container default-comparator"> 23912 <h3>Default Comparator</h3> 23913 <table class="friends"> 23914 <tr> 23915 <th>Name</th> 23916 <th>Favorite Letter</th> 23917 </tr> 23918 <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'"> 23919 <td>{{friend.name}}</td> 23920 <td>{{friend.favoriteLetter}}</td> 23921 </tr> 23922 </table> 23923 </div> 23924 </div> 23925 </file> 23926 <file name="script.js"> 23927 angular.module('orderByExample4', []) 23928 .controller('ExampleController', ['$scope', function($scope) { 23929 $scope.friends = [ 23930 {name: 'John', favoriteLetter: 'Ä'}, 23931 {name: 'Mary', favoriteLetter: 'Ü'}, 23932 {name: 'Mike', favoriteLetter: 'Ö'}, 23933 {name: 'Adam', favoriteLetter: 'H'}, 23934 {name: 'Julie', favoriteLetter: 'Z'} 23935 ]; 23936 23937 $scope.localeSensitiveComparator = function(v1, v2) { 23938 // If we don't get strings, just compare by index 23939 if (v1.type !== 'string' || v2.type !== 'string') { 23940 return (v1.index < v2.index) ? -1 : 1; 23941 } 23942 23943 // Compare strings alphabetically, taking locale into account 23944 return v1.value.localeCompare(v2.value); 23945 }; 23946 }]); 23947 </file> 23948 <file name="style.css"> 23949 .friends-container { 23950 display: inline-block; 23951 margin: 0 30px; 23952 } 23953 23954 .friends { 23955 border-collapse: collapse; 23956 } 23957 23958 .friends th { 23959 border-bottom: 1px solid; 23960 } 23961 .friends td, .friends th { 23962 border-left: 1px solid; 23963 padding: 5px 10px; 23964 } 23965 .friends td:first-child, .friends th:first-child { 23966 border-left: none; 23967 } 23968 </file> 23969 <file name="protractor.js" type="protractor"> 23970 // Element locators 23971 var container = element(by.css('.custom-comparator')); 23972 var names = container.all(by.repeater('friends').column('friend.name')); 23973 23974 it('should sort friends by favorite letter (in correct alphabetical order)', function() { 23975 expect(names.get(0).getText()).toBe('John'); 23976 expect(names.get(1).getText()).toBe('Adam'); 23977 expect(names.get(2).getText()).toBe('Mike'); 23978 expect(names.get(3).getText()).toBe('Mary'); 23979 expect(names.get(4).getText()).toBe('Julie'); 23980 }); 23981 </file> 23982 </example> 23983 * 23984 */ 23985orderByFilter.$inject = ['$parse']; 23986function orderByFilter($parse) { 23987 return function(array, sortPredicate, reverseOrder, compareFn) { 23988 23989 if (array == null) return array; 23990 if (!isArrayLike(array)) { 23991 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 23992 } 23993 23994 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 23995 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 23996 23997 var predicates = processPredicates(sortPredicate); 23998 23999 var descending = reverseOrder ? -1 : 1; 24000 24001 // Define the `compare()` function. Use a default comparator if none is specified. 24002 var compare = isFunction(compareFn) ? compareFn : defaultCompare; 24003 24004 // The next three lines are a version of a Swartzian Transform idiom from Perl 24005 // (sometimes called the Decorate-Sort-Undecorate idiom) 24006 // See https://en.wikipedia.org/wiki/Schwartzian_transform 24007 var compareValues = Array.prototype.map.call(array, getComparisonObject); 24008 compareValues.sort(doComparison); 24009 array = compareValues.map(function(item) { return item.value; }); 24010 24011 return array; 24012 24013 function getComparisonObject(value, index) { 24014 // NOTE: We are adding an extra `tieBreaker` value based on the element's index. 24015 // This will be used to keep the sort stable when none of the input predicates can 24016 // distinguish between two elements. 24017 return { 24018 value: value, 24019 tieBreaker: {value: index, type: 'number', index: index}, 24020 predicateValues: predicates.map(function(predicate) { 24021 return getPredicateValue(predicate.get(value), index); 24022 }) 24023 }; 24024 } 24025 24026 function doComparison(v1, v2) { 24027 for (var i = 0, ii = predicates.length; i < ii; i++) { 24028 var result = compare(v1.predicateValues[i], v2.predicateValues[i]); 24029 if (result) { 24030 return result * predicates[i].descending * descending; 24031 } 24032 } 24033 24034 return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending; 24035 } 24036 }; 24037 24038 function processPredicates(sortPredicates) { 24039 return sortPredicates.map(function(predicate) { 24040 var descending = 1, get = identity; 24041 24042 if (isFunction(predicate)) { 24043 get = predicate; 24044 } else if (isString(predicate)) { 24045 if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) { 24046 descending = predicate.charAt(0) === '-' ? -1 : 1; 24047 predicate = predicate.substring(1); 24048 } 24049 if (predicate !== '') { 24050 get = $parse(predicate); 24051 if (get.constant) { 24052 var key = get(); 24053 get = function(value) { return value[key]; }; 24054 } 24055 } 24056 } 24057 return {get: get, descending: descending}; 24058 }); 24059 } 24060 24061 function isPrimitive(value) { 24062 switch (typeof value) { 24063 case 'number': /* falls through */ 24064 case 'boolean': /* falls through */ 24065 case 'string': 24066 return true; 24067 default: 24068 return false; 24069 } 24070 } 24071 24072 function objectValue(value) { 24073 // If `valueOf` is a valid function use that 24074 if (isFunction(value.valueOf)) { 24075 value = value.valueOf(); 24076 if (isPrimitive(value)) return value; 24077 } 24078 // If `toString` is a valid function and not the one from `Object.prototype` use that 24079 if (hasCustomToString(value)) { 24080 value = value.toString(); 24081 if (isPrimitive(value)) return value; 24082 } 24083 24084 return value; 24085 } 24086 24087 function getPredicateValue(value, index) { 24088 var type = typeof value; 24089 if (value === null) { 24090 type = 'null'; 24091 } else if (type === 'object') { 24092 value = objectValue(value); 24093 } 24094 return {value: value, type: type, index: index}; 24095 } 24096 24097 function defaultCompare(v1, v2) { 24098 var result = 0; 24099 var type1 = v1.type; 24100 var type2 = v2.type; 24101 24102 if (type1 === type2) { 24103 var value1 = v1.value; 24104 var value2 = v2.value; 24105 24106 if (type1 === 'string') { 24107 // Compare strings case-insensitively 24108 value1 = value1.toLowerCase(); 24109 value2 = value2.toLowerCase(); 24110 } else if (type1 === 'object') { 24111 // For basic objects, use the position of the object 24112 // in the collection instead of the value 24113 if (isObject(value1)) value1 = v1.index; 24114 if (isObject(value2)) value2 = v2.index; 24115 } 24116 24117 if (value1 !== value2) { 24118 result = value1 < value2 ? -1 : 1; 24119 } 24120 } else { 24121 result = (type1 === 'undefined') ? 1 : 24122 (type2 === 'undefined') ? -1 : 24123 (type1 === 'null') ? 1 : 24124 (type2 === 'null') ? -1 : 24125 (type1 < type2) ? -1 : 1; 24126 } 24127 24128 return result; 24129 } 24130} 24131 24132function ngDirective(directive) { 24133 if (isFunction(directive)) { 24134 directive = { 24135 link: directive 24136 }; 24137 } 24138 directive.restrict = directive.restrict || 'AC'; 24139 return valueFn(directive); 24140} 24141 24142/** 24143 * @ngdoc directive 24144 * @name a 24145 * @restrict E 24146 * 24147 * @description 24148 * Modifies the default behavior of the html a tag so that the default action is prevented when 24149 * the href attribute is empty. 24150 * 24151 * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive. 24152 */ 24153var htmlAnchorDirective = valueFn({ 24154 restrict: 'E', 24155 compile: function(element, attr) { 24156 if (!attr.href && !attr.xlinkHref) { 24157 return function(scope, element) { 24158 // If the linked element is not an anchor tag anymore, do nothing 24159 if (element[0].nodeName.toLowerCase() !== 'a') return; 24160 24161 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 24162 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 24163 'xlink:href' : 'href'; 24164 element.on('click', function(event) { 24165 // if we have no href url, then don't navigate anywhere. 24166 if (!element.attr(href)) { 24167 event.preventDefault(); 24168 } 24169 }); 24170 }; 24171 } 24172 } 24173}); 24174 24175/** 24176 * @ngdoc directive 24177 * @name ngHref 24178 * @restrict A 24179 * @priority 99 24180 * 24181 * @description 24182 * Using AngularJS markup like `{{hash}}` in an href attribute will 24183 * make the link go to the wrong URL if the user clicks it before 24184 * AngularJS has a chance to replace the `{{hash}}` markup with its 24185 * value. Until AngularJS replaces the markup the link will be broken 24186 * and will most likely return a 404 error. The `ngHref` directive 24187 * solves this problem. 24188 * 24189 * The wrong way to write it: 24190 * ```html 24191 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 24192 * ``` 24193 * 24194 * The correct way to write it: 24195 * ```html 24196 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 24197 * ``` 24198 * 24199 * @element A 24200 * @param {template} ngHref any string which can contain `{{}}` markup. 24201 * 24202 * @example 24203 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 24204 * in links and their different behaviors: 24205 <example name="ng-href"> 24206 <file name="index.html"> 24207 <input ng-model="value" /><br /> 24208 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 24209 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 24210 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 24211 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 24212 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 24213 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 24214 </file> 24215 <file name="protractor.js" type="protractor"> 24216 it('should execute ng-click but not reload when href without value', function() { 24217 element(by.id('link-1')).click(); 24218 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 24219 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 24220 }); 24221 24222 it('should execute ng-click but not reload when href empty string', function() { 24223 element(by.id('link-2')).click(); 24224 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 24225 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 24226 }); 24227 24228 it('should execute ng-click and change url when ng-href specified', function() { 24229 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 24230 24231 element(by.id('link-3')).click(); 24232 24233 // At this point, we navigate away from an AngularJS page, so we need 24234 // to use browser.driver to get the base webdriver. 24235 24236 browser.wait(function() { 24237 return browser.driver.getCurrentUrl().then(function(url) { 24238 return url.match(/\/123$/); 24239 }); 24240 }, 5000, 'page should navigate to /123'); 24241 }); 24242 24243 it('should execute ng-click but not reload when href empty string and name specified', function() { 24244 element(by.id('link-4')).click(); 24245 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 24246 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 24247 }); 24248 24249 it('should execute ng-click but not reload when no href but name specified', function() { 24250 element(by.id('link-5')).click(); 24251 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 24252 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 24253 }); 24254 24255 it('should only change url when only ng-href', function() { 24256 element(by.model('value')).clear(); 24257 element(by.model('value')).sendKeys('6'); 24258 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 24259 24260 element(by.id('link-6')).click(); 24261 24262 // At this point, we navigate away from an AngularJS page, so we need 24263 // to use browser.driver to get the base webdriver. 24264 browser.wait(function() { 24265 return browser.driver.getCurrentUrl().then(function(url) { 24266 return url.match(/\/6$/); 24267 }); 24268 }, 5000, 'page should navigate to /6'); 24269 }); 24270 </file> 24271 </example> 24272 */ 24273 24274/** 24275 * @ngdoc directive 24276 * @name ngSrc 24277 * @restrict A 24278 * @priority 99 24279 * 24280 * @description 24281 * Using AngularJS markup like `{{hash}}` in a `src` attribute doesn't 24282 * work right: The browser will fetch from the URL with the literal 24283 * text `{{hash}}` until AngularJS replaces the expression inside 24284 * `{{hash}}`. The `ngSrc` directive solves this problem. 24285 * 24286 * The buggy way to write it: 24287 * ```html 24288 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 24289 * ``` 24290 * 24291 * The correct way to write it: 24292 * ```html 24293 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 24294 * ``` 24295 * 24296 * @element IMG 24297 * @param {template} ngSrc any string which can contain `{{}}` markup. 24298 */ 24299 24300/** 24301 * @ngdoc directive 24302 * @name ngSrcset 24303 * @restrict A 24304 * @priority 99 24305 * 24306 * @description 24307 * Using AngularJS markup like `{{hash}}` in a `srcset` attribute doesn't 24308 * work right: The browser will fetch from the URL with the literal 24309 * text `{{hash}}` until AngularJS replaces the expression inside 24310 * `{{hash}}`. The `ngSrcset` directive solves this problem. 24311 * 24312 * The buggy way to write it: 24313 * ```html 24314 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 24315 * ``` 24316 * 24317 * The correct way to write it: 24318 * ```html 24319 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 24320 * ``` 24321 * 24322 * @element IMG 24323 * @param {template} ngSrcset any string which can contain `{{}}` markup. 24324 */ 24325 24326/** 24327 * @ngdoc directive 24328 * @name ngDisabled 24329 * @restrict A 24330 * @priority 100 24331 * 24332 * @description 24333 * 24334 * This directive sets the `disabled` attribute on the element (typically a form control, 24335 * e.g. `input`, `button`, `select` etc.) if the 24336 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 24337 * 24338 * A special directive is necessary because we cannot use interpolation inside the `disabled` 24339 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24340 * 24341 * @example 24342 <example name="ng-disabled"> 24343 <file name="index.html"> 24344 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 24345 <button ng-model="button" ng-disabled="checked">Button</button> 24346 </file> 24347 <file name="protractor.js" type="protractor"> 24348 it('should toggle button', function() { 24349 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); 24350 element(by.model('checked')).click(); 24351 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 24352 }); 24353 </file> 24354 </example> 24355 * 24356 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 24357 * then the `disabled` attribute will be set on the element 24358 */ 24359 24360 24361/** 24362 * @ngdoc directive 24363 * @name ngChecked 24364 * @restrict A 24365 * @priority 100 24366 * 24367 * @description 24368 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 24369 * 24370 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 24371 * as this can lead to unexpected behavior. 24372 * 24373 * A special directive is necessary because we cannot use interpolation inside the `checked` 24374 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24375 * 24376 * @example 24377 <example name="ng-checked"> 24378 <file name="index.html"> 24379 <label>Check me to check both: <input type="checkbox" ng-model="leader"></label><br/> 24380 <input id="checkFollower" type="checkbox" ng-checked="leader" aria-label="Follower input"> 24381 </file> 24382 <file name="protractor.js" type="protractor"> 24383 it('should check both checkBoxes', function() { 24384 expect(element(by.id('checkFollower')).getAttribute('checked')).toBeFalsy(); 24385 element(by.model('leader')).click(); 24386 expect(element(by.id('checkFollower')).getAttribute('checked')).toBeTruthy(); 24387 }); 24388 </file> 24389 </example> 24390 * 24391 * @element INPUT 24392 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 24393 * then the `checked` attribute will be set on the element 24394 */ 24395 24396 24397/** 24398 * @ngdoc directive 24399 * @name ngReadonly 24400 * @restrict A 24401 * @priority 100 24402 * 24403 * @description 24404 * 24405 * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy. 24406 * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on 24407 * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information. 24408 * 24409 * A special directive is necessary because we cannot use interpolation inside the `readonly` 24410 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24411 * 24412 * @example 24413 <example name="ng-readonly"> 24414 <file name="index.html"> 24415 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 24416 <input type="text" ng-readonly="checked" value="I'm AngularJS" aria-label="Readonly field" /> 24417 </file> 24418 <file name="protractor.js" type="protractor"> 24419 it('should toggle readonly attr', function() { 24420 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 24421 element(by.model('checked')).click(); 24422 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 24423 }); 24424 </file> 24425 </example> 24426 * 24427 * @element INPUT 24428 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 24429 * then special attribute "readonly" will be set on the element 24430 */ 24431 24432 24433/** 24434 * @ngdoc directive 24435 * @name ngSelected 24436 * @restrict A 24437 * @priority 100 24438 * 24439 * @description 24440 * 24441 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 24442 * 24443 * A special directive is necessary because we cannot use interpolation inside the `selected` 24444 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24445 * 24446 * <div class="alert alert-warning"> 24447 * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only 24448 * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you 24449 * should not use `ngSelected` on the options, as `ngModel` will set the select value and 24450 * selected options. 24451 * </div> 24452 * 24453 * @example 24454 <example name="ng-selected"> 24455 <file name="index.html"> 24456 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 24457 <select aria-label="ngSelected demo"> 24458 <option>Hello!</option> 24459 <option id="greet" ng-selected="selected">Greetings!</option> 24460 </select> 24461 </file> 24462 <file name="protractor.js" type="protractor"> 24463 it('should select Greetings!', function() { 24464 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 24465 element(by.model('selected')).click(); 24466 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 24467 }); 24468 </file> 24469 </example> 24470 * 24471 * @element OPTION 24472 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 24473 * then special attribute "selected" will be set on the element 24474 */ 24475 24476/** 24477 * @ngdoc directive 24478 * @name ngOpen 24479 * @restrict A 24480 * @priority 100 24481 * 24482 * @description 24483 * 24484 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 24485 * 24486 * A special directive is necessary because we cannot use interpolation inside the `open` 24487 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 24488 * 24489 * ## A note about browser compatibility 24490 * 24491 * Internet Explorer and Edge do not support the `details` element, it is 24492 * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. 24493 * 24494 * @example 24495 <example name="ng-open"> 24496 <file name="index.html"> 24497 <label>Toggle details: <input type="checkbox" ng-model="open"></label><br/> 24498 <details id="details" ng-open="open"> 24499 <summary>List</summary> 24500 <ul> 24501 <li>Apple</li> 24502 <li>Orange</li> 24503 <li>Durian</li> 24504 </ul> 24505 </details> 24506 </file> 24507 <file name="protractor.js" type="protractor"> 24508 it('should toggle open', function() { 24509 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 24510 element(by.model('open')).click(); 24511 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 24512 }); 24513 </file> 24514 </example> 24515 * 24516 * @element DETAILS 24517 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 24518 * then special attribute "open" will be set on the element 24519 */ 24520 24521var ngAttributeAliasDirectives = {}; 24522 24523// boolean attrs are evaluated 24524forEach(BOOLEAN_ATTR, function(propName, attrName) { 24525 // binding to multiple is not supported 24526 if (propName === 'multiple') return; 24527 24528 function defaultLinkFn(scope, element, attr) { 24529 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 24530 attr.$set(attrName, !!value); 24531 }); 24532 } 24533 24534 var normalized = directiveNormalize('ng-' + attrName); 24535 var linkFn = defaultLinkFn; 24536 24537 if (propName === 'checked') { 24538 linkFn = function(scope, element, attr) { 24539 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 24540 if (attr.ngModel !== attr[normalized]) { 24541 defaultLinkFn(scope, element, attr); 24542 } 24543 }; 24544 } 24545 24546 ngAttributeAliasDirectives[normalized] = function() { 24547 return { 24548 restrict: 'A', 24549 priority: 100, 24550 link: linkFn 24551 }; 24552 }; 24553}); 24554 24555// aliased input attrs are evaluated 24556forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 24557 ngAttributeAliasDirectives[ngAttr] = function() { 24558 return { 24559 priority: 100, 24560 link: function(scope, element, attr) { 24561 //special case ngPattern when a literal regular expression value 24562 //is used as the expression (this way we don't have to watch anything). 24563 if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') { 24564 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 24565 if (match) { 24566 attr.$set('ngPattern', new RegExp(match[1], match[2])); 24567 return; 24568 } 24569 } 24570 24571 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 24572 attr.$set(ngAttr, value); 24573 }); 24574 } 24575 }; 24576 }; 24577}); 24578 24579// ng-src, ng-srcset, ng-href are interpolated 24580forEach(['src', 'srcset', 'href'], function(attrName) { 24581 var normalized = directiveNormalize('ng-' + attrName); 24582 ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) { 24583 return { 24584 priority: 99, // it needs to run after the attributes are interpolated 24585 link: function(scope, element, attr) { 24586 var propName = attrName, 24587 name = attrName; 24588 24589 if (attrName === 'href' && 24590 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 24591 name = 'xlinkHref'; 24592 attr.$attr[name] = 'xlink:href'; 24593 propName = null; 24594 } 24595 24596 // We need to sanitize the url at least once, in case it is a constant 24597 // non-interpolated attribute. 24598 attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized])); 24599 24600 attr.$observe(normalized, function(value) { 24601 if (!value) { 24602 if (attrName === 'href') { 24603 attr.$set(name, null); 24604 } 24605 return; 24606 } 24607 24608 attr.$set(name, value); 24609 24610 // Support: IE 9-11 only 24611 // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 24612 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 24613 // to set the property as well to achieve the desired effect. 24614 // We use attr[attrName] value since $set might have sanitized the url. 24615 if (msie && propName) element.prop(propName, attr[name]); 24616 }); 24617 } 24618 }; 24619 }]; 24620}); 24621 24622/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS 24623 */ 24624var nullFormCtrl = { 24625 $addControl: noop, 24626 $getControls: valueFn([]), 24627 $$renameControl: nullFormRenameControl, 24628 $removeControl: noop, 24629 $setValidity: noop, 24630 $setDirty: noop, 24631 $setPristine: noop, 24632 $setSubmitted: noop, 24633 $$setSubmitted: noop 24634}, 24635PENDING_CLASS = 'ng-pending', 24636SUBMITTED_CLASS = 'ng-submitted'; 24637 24638function nullFormRenameControl(control, name) { 24639 control.$name = name; 24640} 24641 24642/** 24643 * @ngdoc type 24644 * @name form.FormController 24645 * 24646 * @property {boolean} $pristine True if user has not interacted with the form yet. 24647 * @property {boolean} $dirty True if user has already interacted with the form. 24648 * @property {boolean} $valid True if all of the containing forms and controls are valid. 24649 * @property {boolean} $invalid True if at least one containing control or form is invalid. 24650 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 24651 * 24652 * @property {Object} $pending An object hash, containing references to controls or forms with 24653 * pending validators, where: 24654 * 24655 * - keys are validations tokens (error names). 24656 * - values are arrays of controls or forms that have a pending validator for the given error name. 24657 * 24658 * See {@link form.FormController#$error $error} for a list of built-in validation tokens. 24659 * 24660 * @property {Object} $error An object hash, containing references to controls or forms with failing 24661 * validators, where: 24662 * 24663 * - keys are validation tokens (error names), 24664 * - values are arrays of controls or forms that have a failing validator for the given error name. 24665 * 24666 * Built-in validation tokens: 24667 * - `email` 24668 * - `max` 24669 * - `maxlength` 24670 * - `min` 24671 * - `minlength` 24672 * - `number` 24673 * - `pattern` 24674 * - `required` 24675 * - `url` 24676 * - `date` 24677 * - `datetimelocal` 24678 * - `time` 24679 * - `week` 24680 * - `month` 24681 * 24682 * @description 24683 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 24684 * such as being valid/invalid or dirty/pristine. 24685 * 24686 * Each {@link ng.directive:form form} directive creates an instance 24687 * of `FormController`. 24688 * 24689 */ 24690//asks for $scope to fool the BC controller module 24691FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 24692function FormController($element, $attrs, $scope, $animate, $interpolate) { 24693 this.$$controls = []; 24694 24695 // init state 24696 this.$error = {}; 24697 this.$$success = {}; 24698 this.$pending = undefined; 24699 this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope); 24700 this.$dirty = false; 24701 this.$pristine = true; 24702 this.$valid = true; 24703 this.$invalid = false; 24704 this.$submitted = false; 24705 this.$$parentForm = nullFormCtrl; 24706 24707 this.$$element = $element; 24708 this.$$animate = $animate; 24709 24710 setupValidity(this); 24711} 24712 24713FormController.prototype = { 24714 /** 24715 * @ngdoc method 24716 * @name form.FormController#$rollbackViewValue 24717 * 24718 * @description 24719 * Rollback all form controls pending updates to the `$modelValue`. 24720 * 24721 * Updates may be pending by a debounced event or because the input is waiting for a some future 24722 * event defined in `ng-model-options`. This method is typically needed by the reset button of 24723 * a form that uses `ng-model-options` to pend updates. 24724 */ 24725 $rollbackViewValue: function() { 24726 forEach(this.$$controls, function(control) { 24727 control.$rollbackViewValue(); 24728 }); 24729 }, 24730 24731 /** 24732 * @ngdoc method 24733 * @name form.FormController#$commitViewValue 24734 * 24735 * @description 24736 * Commit all form controls pending updates to the `$modelValue`. 24737 * 24738 * Updates may be pending by a debounced event or because the input is waiting for a some future 24739 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 24740 * usually handles calling this in response to input events. 24741 */ 24742 $commitViewValue: function() { 24743 forEach(this.$$controls, function(control) { 24744 control.$commitViewValue(); 24745 }); 24746 }, 24747 24748 /** 24749 * @ngdoc method 24750 * @name form.FormController#$addControl 24751 * @param {object} control control object, either a {@link form.FormController} or an 24752 * {@link ngModel.NgModelController} 24753 * 24754 * @description 24755 * Register a control with the form. Input elements using ngModelController do this automatically 24756 * when they are linked. 24757 * 24758 * Note that the current state of the control will not be reflected on the new parent form. This 24759 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 24760 * state. 24761 * 24762 * However, if the method is used programmatically, for example by adding dynamically created controls, 24763 * or controls that have been previously removed without destroying their corresponding DOM element, 24764 * it's the developers responsibility to make sure the current state propagates to the parent form. 24765 * 24766 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 24767 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 24768 */ 24769 $addControl: function(control) { 24770 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 24771 // and not added to the scope. Now we throw an error. 24772 assertNotHasOwnProperty(control.$name, 'input'); 24773 this.$$controls.push(control); 24774 24775 if (control.$name) { 24776 this[control.$name] = control; 24777 } 24778 24779 control.$$parentForm = this; 24780 }, 24781 24782 /** 24783 * @ngdoc method 24784 * @name form.FormController#$getControls 24785 * @returns {Array} the controls that are currently part of this form 24786 * 24787 * @description 24788 * This method returns a **shallow copy** of the controls that are currently part of this form. 24789 * The controls can be instances of {@link form.FormController `FormController`} 24790 * ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}. 24791 * If you need access to the controls of child-forms, you have to call `$getControls()` 24792 * recursively on them. 24793 * This can be used for example to iterate over all controls to validate them. 24794 * 24795 * The controls can be accessed normally, but adding to, or removing controls from the array has 24796 * no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and 24797 * {@link form.FormController#$removeControl `$removeControl()`} for this use-case. 24798 * Likewise, adding a control to, or removing a control from the form is not reflected 24799 * in the shallow copy. That means you should get a fresh copy from `$getControls()` every time 24800 * you need access to the controls. 24801 */ 24802 $getControls: function() { 24803 return shallowCopy(this.$$controls); 24804 }, 24805 24806 // Private API: rename a form control 24807 $$renameControl: function(control, newName) { 24808 var oldName = control.$name; 24809 24810 if (this[oldName] === control) { 24811 delete this[oldName]; 24812 } 24813 this[newName] = control; 24814 control.$name = newName; 24815 }, 24816 24817 /** 24818 * @ngdoc method 24819 * @name form.FormController#$removeControl 24820 * @param {object} control control object, either a {@link form.FormController} or an 24821 * {@link ngModel.NgModelController} 24822 * 24823 * @description 24824 * Deregister a control from the form. 24825 * 24826 * Input elements using ngModelController do this automatically when they are destroyed. 24827 * 24828 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 24829 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 24830 * different from case to case. For example, removing the only `$dirty` control from a form may or 24831 * may not mean that the form is still `$dirty`. 24832 */ 24833 $removeControl: function(control) { 24834 if (control.$name && this[control.$name] === control) { 24835 delete this[control.$name]; 24836 } 24837 forEach(this.$pending, function(value, name) { 24838 // eslint-disable-next-line no-invalid-this 24839 this.$setValidity(name, null, control); 24840 }, this); 24841 forEach(this.$error, function(value, name) { 24842 // eslint-disable-next-line no-invalid-this 24843 this.$setValidity(name, null, control); 24844 }, this); 24845 forEach(this.$$success, function(value, name) { 24846 // eslint-disable-next-line no-invalid-this 24847 this.$setValidity(name, null, control); 24848 }, this); 24849 24850 arrayRemove(this.$$controls, control); 24851 control.$$parentForm = nullFormCtrl; 24852 }, 24853 24854 /** 24855 * @ngdoc method 24856 * @name form.FormController#$setDirty 24857 * 24858 * @description 24859 * Sets the form to a dirty state. 24860 * 24861 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 24862 * state (ng-dirty class). This method will also propagate to parent forms. 24863 */ 24864 $setDirty: function() { 24865 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); 24866 this.$$animate.addClass(this.$$element, DIRTY_CLASS); 24867 this.$dirty = true; 24868 this.$pristine = false; 24869 this.$$parentForm.$setDirty(); 24870 }, 24871 24872 /** 24873 * @ngdoc method 24874 * @name form.FormController#$setPristine 24875 * 24876 * @description 24877 * Sets the form to its pristine state. 24878 * 24879 * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes 24880 * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted` 24881 * state to false. 24882 * 24883 * This method will also propagate to all the controls contained in this form. 24884 * 24885 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 24886 * saving or resetting it. 24887 */ 24888 $setPristine: function() { 24889 this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 24890 this.$dirty = false; 24891 this.$pristine = true; 24892 this.$submitted = false; 24893 forEach(this.$$controls, function(control) { 24894 control.$setPristine(); 24895 }); 24896 }, 24897 24898 /** 24899 * @ngdoc method 24900 * @name form.FormController#$setUntouched 24901 * 24902 * @description 24903 * Sets the form to its untouched state. 24904 * 24905 * This method can be called to remove the 'ng-touched' class and set the form controls to their 24906 * untouched state (ng-untouched class). 24907 * 24908 * Setting a form controls back to their untouched state is often useful when setting the form 24909 * back to its pristine state. 24910 */ 24911 $setUntouched: function() { 24912 forEach(this.$$controls, function(control) { 24913 control.$setUntouched(); 24914 }); 24915 }, 24916 24917 /** 24918 * @ngdoc method 24919 * @name form.FormController#$setSubmitted 24920 * 24921 * @description 24922 * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and 24923 * parent forms of the form. 24924 */ 24925 $setSubmitted: function() { 24926 var rootForm = this; 24927 while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) { 24928 rootForm = rootForm.$$parentForm; 24929 } 24930 rootForm.$$setSubmitted(); 24931 }, 24932 24933 $$setSubmitted: function() { 24934 this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); 24935 this.$submitted = true; 24936 forEach(this.$$controls, function(control) { 24937 if (control.$$setSubmitted) { 24938 control.$$setSubmitted(); 24939 } 24940 }); 24941 } 24942}; 24943 24944/** 24945 * @ngdoc method 24946 * @name form.FormController#$setValidity 24947 * 24948 * @description 24949 * Change the validity state of the form, and notify the parent form (if any). 24950 * 24951 * Application developers will rarely need to call this method directly. It is used internally, by 24952 * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a 24953 * control's validity state to the parent `FormController`. 24954 * 24955 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be 24956 * assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for 24957 * unfulfilled `$asyncValidators`), so that it is available for data-binding. The 24958 * `validationErrorKey` should be in camelCase and will get converted into dash-case for 24959 * class name. Example: `myError` will result in `ng-valid-my-error` and 24960 * `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`. 24961 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending 24962 * (undefined), or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 24963 * Skipped is used by AngularJS when validators do not run because of parse errors and when 24964 * `$asyncValidators` do not run because any of the `$validators` failed. 24965 * @param {NgModelController | FormController} controller - The controller whose validity state is 24966 * triggering the change. 24967 */ 24968addSetValidityMethod({ 24969 clazz: FormController, 24970 set: function(object, property, controller) { 24971 var list = object[property]; 24972 if (!list) { 24973 object[property] = [controller]; 24974 } else { 24975 var index = list.indexOf(controller); 24976 if (index === -1) { 24977 list.push(controller); 24978 } 24979 } 24980 }, 24981 unset: function(object, property, controller) { 24982 var list = object[property]; 24983 if (!list) { 24984 return; 24985 } 24986 arrayRemove(list, controller); 24987 if (list.length === 0) { 24988 delete object[property]; 24989 } 24990 } 24991}); 24992 24993/** 24994 * @ngdoc directive 24995 * @name ngForm 24996 * @restrict EAC 24997 * 24998 * @description 24999 * Helper directive that makes it possible to create control groups inside a 25000 * {@link ng.directive:form `form`} directive. 25001 * These "child forms" can be used, for example, to determine the validity of a sub-group of 25002 * controls. 25003 * 25004 * <div class="alert alert-danger"> 25005 * **Note**: `ngForm` cannot be used as a replacement for `<form>`, because it lacks its 25006 * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element). 25007 * Specifically, you cannot submit `ngForm` like a `<form>` tag. That means, 25008 * you cannot send data to the server with `ngForm`, or integrate it with 25009 * {@link ng.directive:ngSubmit `ngSubmit`}. 25010 * </div> 25011 * 25012 * @param {string=} ngForm|name Name of the form. If specified, the form controller will 25013 * be published into the related scope, under this name. 25014 * 25015 */ 25016 25017 /** 25018 * @ngdoc directive 25019 * @name form 25020 * @restrict E 25021 * 25022 * @description 25023 * Directive that instantiates 25024 * {@link form.FormController FormController}. 25025 * 25026 * If the `name` attribute is specified, the form controller is published onto the current scope under 25027 * this name. 25028 * 25029 * ## Alias: {@link ng.directive:ngForm `ngForm`} 25030 * 25031 * In AngularJS, forms can be nested. This means that the outer form is valid when all of the child 25032 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 25033 * AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 25034 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 25035 * of controls needs to be determined. 25036 * 25037 * ## CSS classes 25038 * - `ng-valid` is set if the form is valid. 25039 * - `ng-invalid` is set if the form is invalid. 25040 * - `ng-pending` is set if the form is pending. 25041 * - `ng-pristine` is set if the form is pristine. 25042 * - `ng-dirty` is set if the form is dirty. 25043 * - `ng-submitted` is set if the form was submitted. 25044 * 25045 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25046 * 25047 * 25048 * ## Submitting a form and preventing the default action 25049 * 25050 * Since the role of forms in client-side AngularJS applications is different than in classical 25051 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 25052 * page reload that sends the data to the server. Instead some javascript logic should be triggered 25053 * to handle the form submission in an application-specific way. 25054 * 25055 * For this reason, AngularJS prevents the default action (form submission to the server) unless the 25056 * `<form>` element has an `action` attribute specified. 25057 * 25058 * You can use one of the following two ways to specify what javascript method should be called when 25059 * a form is submitted: 25060 * 25061 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 25062 * - {@link ng.directive:ngClick ngClick} directive on the first 25063 * button or input field of type submit (input[type=submit]) 25064 * 25065 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 25066 * or {@link ng.directive:ngClick ngClick} directives. 25067 * This is because of the following form submission rules in the HTML specification: 25068 * 25069 * - If a form has only one input field then hitting enter in this field triggers form submit 25070 * (`ngSubmit`) 25071 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 25072 * doesn't trigger submit 25073 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 25074 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 25075 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 25076 * 25077 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 25078 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 25079 * to have access to the updated model. 25080 * 25081 * @animations 25082 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 25083 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 25084 * other validations that are performed within the form. Animations in ngForm are similar to how 25085 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 25086 * as JS animations. 25087 * 25088 * The following example shows a simple way to utilize CSS transitions to style a form element 25089 * that has been rendered as invalid after it has been validated: 25090 * 25091 * <pre> 25092 * //be sure to include ngAnimate as a module to hook into more 25093 * //advanced animations 25094 * .my-form { 25095 * transition:0.5s linear all; 25096 * background: white; 25097 * } 25098 * .my-form.ng-invalid { 25099 * background: red; 25100 * color:white; 25101 * } 25102 * </pre> 25103 * 25104 * @example 25105 <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 25106 <file name="index.html"> 25107 <script> 25108 angular.module('formExample', []) 25109 .controller('FormController', ['$scope', function($scope) { 25110 $scope.userType = 'guest'; 25111 }]); 25112 </script> 25113 <style> 25114 .my-form { 25115 transition:all linear 0.5s; 25116 background: transparent; 25117 } 25118 .my-form.ng-invalid { 25119 background: red; 25120 } 25121 </style> 25122 <form name="myForm" ng-controller="FormController" class="my-form"> 25123 userType: <input name="input" ng-model="userType" required> 25124 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 25125 <code>userType = {{userType}}</code><br> 25126 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 25127 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 25128 <code>myForm.$valid = {{myForm.$valid}}</code><br> 25129 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 25130 </form> 25131 </file> 25132 <file name="protractor.js" type="protractor"> 25133 it('should initialize to model', function() { 25134 var userType = element(by.binding('userType')); 25135 var valid = element(by.binding('myForm.input.$valid')); 25136 25137 expect(userType.getText()).toContain('guest'); 25138 expect(valid.getText()).toContain('true'); 25139 }); 25140 25141 it('should be invalid if empty', function() { 25142 var userType = element(by.binding('userType')); 25143 var valid = element(by.binding('myForm.input.$valid')); 25144 var userInput = element(by.model('userType')); 25145 25146 userInput.clear(); 25147 userInput.sendKeys(''); 25148 25149 expect(userType.getText()).toEqual('userType ='); 25150 expect(valid.getText()).toContain('false'); 25151 }); 25152 </file> 25153 </example> 25154 * 25155 * @param {string=} name Name of the form. If specified, the form controller will be published into 25156 * related scope, under this name. 25157 */ 25158var formDirectiveFactory = function(isNgForm) { 25159 return ['$timeout', '$parse', function($timeout, $parse) { 25160 var formDirective = { 25161 name: 'form', 25162 restrict: isNgForm ? 'EAC' : 'E', 25163 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 25164 controller: FormController, 25165 compile: function ngFormCompile(formElement, attr) { 25166 // Setup initial state of the control 25167 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 25168 25169 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 25170 25171 return { 25172 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 25173 var controller = ctrls[0]; 25174 25175 // if `action` attr is not present on the form, prevent the default action (submission) 25176 if (!('action' in attr)) { 25177 // we can't use jq events because if a form is destroyed during submission the default 25178 // action is not prevented. see #1238 25179 // 25180 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 25181 // page reload if the form was destroyed by submission of the form via a click handler 25182 // on a button in the form. Looks like an IE9 specific bug. 25183 var handleFormSubmission = function(event) { 25184 scope.$apply(function() { 25185 controller.$commitViewValue(); 25186 controller.$setSubmitted(); 25187 }); 25188 25189 event.preventDefault(); 25190 }; 25191 25192 formElement[0].addEventListener('submit', handleFormSubmission); 25193 25194 // unregister the preventDefault listener so that we don't not leak memory but in a 25195 // way that will achieve the prevention of the default action. 25196 formElement.on('$destroy', function() { 25197 $timeout(function() { 25198 formElement[0].removeEventListener('submit', handleFormSubmission); 25199 }, 0, false); 25200 }); 25201 } 25202 25203 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 25204 parentFormCtrl.$addControl(controller); 25205 25206 var setter = nameAttr ? getSetter(controller.$name) : noop; 25207 25208 if (nameAttr) { 25209 setter(scope, controller); 25210 attr.$observe(nameAttr, function(newValue) { 25211 if (controller.$name === newValue) return; 25212 setter(scope, undefined); 25213 controller.$$parentForm.$$renameControl(controller, newValue); 25214 setter = getSetter(controller.$name); 25215 setter(scope, controller); 25216 }); 25217 } 25218 formElement.on('$destroy', function() { 25219 controller.$$parentForm.$removeControl(controller); 25220 setter(scope, undefined); 25221 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 25222 }); 25223 } 25224 }; 25225 } 25226 }; 25227 25228 return formDirective; 25229 25230 function getSetter(expression) { 25231 if (expression === '') { 25232 //create an assignable expression, so forms with an empty name can be renamed later 25233 return $parse('this[""]').assign; 25234 } 25235 return $parse(expression).assign || noop; 25236 } 25237 }]; 25238}; 25239 25240var formDirective = formDirectiveFactory(); 25241var ngFormDirective = formDirectiveFactory(true); 25242 25243 25244 25245// helper methods 25246function setupValidity(instance) { 25247 instance.$$classCache = {}; 25248 instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS)); 25249} 25250function addSetValidityMethod(context) { 25251 var clazz = context.clazz, 25252 set = context.set, 25253 unset = context.unset; 25254 25255 clazz.prototype.$setValidity = function(validationErrorKey, state, controller) { 25256 if (isUndefined(state)) { 25257 createAndSet(this, '$pending', validationErrorKey, controller); 25258 } else { 25259 unsetAndCleanup(this, '$pending', validationErrorKey, controller); 25260 } 25261 if (!isBoolean(state)) { 25262 unset(this.$error, validationErrorKey, controller); 25263 unset(this.$$success, validationErrorKey, controller); 25264 } else { 25265 if (state) { 25266 unset(this.$error, validationErrorKey, controller); 25267 set(this.$$success, validationErrorKey, controller); 25268 } else { 25269 set(this.$error, validationErrorKey, controller); 25270 unset(this.$$success, validationErrorKey, controller); 25271 } 25272 } 25273 if (this.$pending) { 25274 cachedToggleClass(this, PENDING_CLASS, true); 25275 this.$valid = this.$invalid = undefined; 25276 toggleValidationCss(this, '', null); 25277 } else { 25278 cachedToggleClass(this, PENDING_CLASS, false); 25279 this.$valid = isObjectEmpty(this.$error); 25280 this.$invalid = !this.$valid; 25281 toggleValidationCss(this, '', this.$valid); 25282 } 25283 25284 // re-read the state as the set/unset methods could have 25285 // combined state in this.$error[validationError] (used for forms), 25286 // where setting/unsetting only increments/decrements the value, 25287 // and does not replace it. 25288 var combinedState; 25289 if (this.$pending && this.$pending[validationErrorKey]) { 25290 combinedState = undefined; 25291 } else if (this.$error[validationErrorKey]) { 25292 combinedState = false; 25293 } else if (this.$$success[validationErrorKey]) { 25294 combinedState = true; 25295 } else { 25296 combinedState = null; 25297 } 25298 25299 toggleValidationCss(this, validationErrorKey, combinedState); 25300 this.$$parentForm.$setValidity(validationErrorKey, combinedState, this); 25301 }; 25302 25303 function createAndSet(ctrl, name, value, controller) { 25304 if (!ctrl[name]) { 25305 ctrl[name] = {}; 25306 } 25307 set(ctrl[name], value, controller); 25308 } 25309 25310 function unsetAndCleanup(ctrl, name, value, controller) { 25311 if (ctrl[name]) { 25312 unset(ctrl[name], value, controller); 25313 } 25314 if (isObjectEmpty(ctrl[name])) { 25315 ctrl[name] = undefined; 25316 } 25317 } 25318 25319 function cachedToggleClass(ctrl, className, switchValue) { 25320 if (switchValue && !ctrl.$$classCache[className]) { 25321 ctrl.$$animate.addClass(ctrl.$$element, className); 25322 ctrl.$$classCache[className] = true; 25323 } else if (!switchValue && ctrl.$$classCache[className]) { 25324 ctrl.$$animate.removeClass(ctrl.$$element, className); 25325 ctrl.$$classCache[className] = false; 25326 } 25327 } 25328 25329 function toggleValidationCss(ctrl, validationErrorKey, isValid) { 25330 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 25331 25332 cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true); 25333 cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false); 25334 } 25335} 25336 25337function isObjectEmpty(obj) { 25338 if (obj) { 25339 for (var prop in obj) { 25340 if (obj.hasOwnProperty(prop)) { 25341 return false; 25342 } 25343 } 25344 } 25345 return true; 25346} 25347 25348/* global 25349 VALID_CLASS: false, 25350 INVALID_CLASS: false, 25351 PRISTINE_CLASS: false, 25352 DIRTY_CLASS: false, 25353 ngModelMinErr: false 25354*/ 25355 25356// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 25357var 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)$/; 25358// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 25359// Note: We are being more lenient, because browsers are too. 25360// 1. Scheme 25361// 2. Slashes 25362// 3. Username 25363// 4. Password 25364// 5. Hostname 25365// 6. Port 25366// 7. Path 25367// 8. Query 25368// 9. Fragment 25369// 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999 25370var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 25371// eslint-disable-next-line max-len 25372var 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])?)*$/; 25373var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 25374var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; 25375var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 25376var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; 25377var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; 25378var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 25379 25380var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; 25381var PARTIAL_VALIDATION_TYPES = createMap(); 25382forEach('date,datetime-local,month,time,week'.split(','), function(type) { 25383 PARTIAL_VALIDATION_TYPES[type] = true; 25384}); 25385 25386var inputType = { 25387 25388 /** 25389 * @ngdoc input 25390 * @name input[text] 25391 * 25392 * @description 25393 * Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements. 25394 * 25395 * 25396 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25397 * @param {string=} name Property name of the form under which the control is published. 25398 * @param {string=} required Adds `required` validation error key if the value is not entered. 25399 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25400 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25401 * `required` when you want to data-bind to the `required` attribute. 25402 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 25403 * minlength. 25404 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 25405 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 25406 * any length. 25407 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 25408 * that contains the regular expression body that will be converted to a regular expression 25409 * as in the ngPattern directive. 25410 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 25411 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 25412 * If the expression evaluates to a RegExp object, then this is used directly. 25413 * If the expression evaluates to a string, then it will be converted to a RegExp 25414 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 25415 * `new RegExp('^abc$')`.<br /> 25416 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 25417 * start at the index of the last search's match, thus not taking the whole input value into 25418 * account. 25419 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25420 * interaction with the input element. 25421 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. 25422 * This parameter is ignored for input[type=password] controls, which will never trim the 25423 * input. 25424 * 25425 * @example 25426 <example name="text-input-directive" module="textInputExample"> 25427 <file name="index.html"> 25428 <script> 25429 angular.module('textInputExample', []) 25430 .controller('ExampleController', ['$scope', function($scope) { 25431 $scope.example = { 25432 text: 'guest', 25433 word: /^\s*\w*\s*$/ 25434 }; 25435 }]); 25436 </script> 25437 <form name="myForm" ng-controller="ExampleController"> 25438 <label>Single word: 25439 <input type="text" name="input" ng-model="example.text" 25440 ng-pattern="example.word" required ng-trim="false"> 25441 </label> 25442 <div role="alert"> 25443 <span class="error" ng-show="myForm.input.$error.required"> 25444 Required!</span> 25445 <span class="error" ng-show="myForm.input.$error.pattern"> 25446 Single word only!</span> 25447 </div> 25448 <code>text = {{example.text}}</code><br/> 25449 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/> 25450 <code>myForm.input.$error = {{myForm.input.$error}}</code><br/> 25451 <code>myForm.$valid = {{myForm.$valid}}</code><br/> 25452 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/> 25453 </form> 25454 </file> 25455 <file name="protractor.js" type="protractor"> 25456 var text = element(by.binding('example.text')); 25457 var valid = element(by.binding('myForm.input.$valid')); 25458 var input = element(by.model('example.text')); 25459 25460 it('should initialize to model', function() { 25461 expect(text.getText()).toContain('guest'); 25462 expect(valid.getText()).toContain('true'); 25463 }); 25464 25465 it('should be invalid if empty', function() { 25466 input.clear(); 25467 input.sendKeys(''); 25468 25469 expect(text.getText()).toEqual('text ='); 25470 expect(valid.getText()).toContain('false'); 25471 }); 25472 25473 it('should be invalid if multi word', function() { 25474 input.clear(); 25475 input.sendKeys('hello world'); 25476 25477 expect(valid.getText()).toContain('false'); 25478 }); 25479 </file> 25480 </example> 25481 */ 25482 'text': textInputType, 25483 25484 /** 25485 * @ngdoc input 25486 * @name input[date] 25487 * 25488 * @description 25489 * Input with date validation and transformation. In browsers that do not yet support 25490 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 25491 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 25492 * modern browsers do not yet support this input type, it is important to provide cues to users on the 25493 * expected input format via a placeholder or label. 25494 * 25495 * The model must always be a Date object, otherwise AngularJS will throw an error. 25496 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25497 * 25498 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25499 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25500 * 25501 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25502 * @param {string=} name Property name of the form under which the control is published. 25503 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 25504 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 25505 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 25506 * constraint validation. 25507 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 25508 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 25509 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 25510 * constraint validation. 25511 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 25512 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25513 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 25514 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25515 * @param {string=} required Sets `required` validation error key if the value is not entered. 25516 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25517 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25518 * `required` when you want to data-bind to the `required` attribute. 25519 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25520 * interaction with the input element. 25521 * 25522 * @example 25523 <example name="date-input-directive" module="dateInputExample"> 25524 <file name="index.html"> 25525 <script> 25526 angular.module('dateInputExample', []) 25527 .controller('DateController', ['$scope', function($scope) { 25528 $scope.example = { 25529 value: new Date(2013, 9, 22) 25530 }; 25531 }]); 25532 </script> 25533 <form name="myForm" ng-controller="DateController as dateCtrl"> 25534 <label for="exampleInput">Pick a date in 2013:</label> 25535 <input type="date" id="exampleInput" name="input" ng-model="example.value" 25536 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 25537 <div role="alert"> 25538 <span class="error" ng-show="myForm.input.$error.required"> 25539 Required!</span> 25540 <span class="error" ng-show="myForm.input.$error.date"> 25541 Not a valid date!</span> 25542 </div> 25543 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 25544 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25545 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25546 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25547 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25548 </form> 25549 </file> 25550 <file name="protractor.js" type="protractor"> 25551 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 25552 var valid = element(by.binding('myForm.input.$valid')); 25553 25554 // currently protractor/webdriver does not support 25555 // sending keys to all known HTML5 input controls 25556 // for various browsers (see https://github.com/angular/protractor/issues/562). 25557 function setInput(val) { 25558 // set the value of the element and force validation. 25559 var scr = "var ipt = document.getElementById('exampleInput'); " + 25560 "ipt.value = '" + val + "';" + 25561 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25562 browser.executeScript(scr); 25563 } 25564 25565 it('should initialize to model', function() { 25566 expect(value.getText()).toContain('2013-10-22'); 25567 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25568 }); 25569 25570 it('should be invalid if empty', function() { 25571 setInput(''); 25572 expect(value.getText()).toEqual('value ='); 25573 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25574 }); 25575 25576 it('should be invalid if over max', function() { 25577 setInput('2015-01-01'); 25578 expect(value.getText()).toContain(''); 25579 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25580 }); 25581 </file> 25582 </example> 25583 */ 25584 'date': createDateInputType('date', DATE_REGEXP, 25585 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 25586 'yyyy-MM-dd'), 25587 25588 /** 25589 * @ngdoc input 25590 * @name input[datetime-local] 25591 * 25592 * @description 25593 * Input with datetime validation and transformation. In browsers that do not yet support 25594 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25595 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 25596 * 25597 * The model must always be a Date object, otherwise AngularJS will throw an error. 25598 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25599 * 25600 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25601 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25602 * 25603 * The format of the displayed time can be adjusted with the 25604 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` 25605 * and `timeStripZeroSeconds`. 25606 * 25607 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25608 * @param {string=} name Property name of the form under which the control is published. 25609 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25610 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 25611 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 25612 * Note that `min` will also add native HTML5 constraint validation. 25613 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25614 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 25615 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 25616 * Note that `max` will also add native HTML5 constraint validation. 25617 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 25618 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25619 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 25620 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25621 * @param {string=} required Sets `required` validation error key if the value is not entered. 25622 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25623 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25624 * `required` when you want to data-bind to the `required` attribute. 25625 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25626 * interaction with the input element. 25627 * 25628 * @example 25629 <example name="datetimelocal-input-directive" module="dateExample"> 25630 <file name="index.html"> 25631 <script> 25632 angular.module('dateExample', []) 25633 .controller('DateController', ['$scope', function($scope) { 25634 $scope.example = { 25635 value: new Date(2010, 11, 28, 14, 57) 25636 }; 25637 }]); 25638 </script> 25639 <form name="myForm" ng-controller="DateController as dateCtrl"> 25640 <label for="exampleInput">Pick a date between in 2013:</label> 25641 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 25642 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 25643 <div role="alert"> 25644 <span class="error" ng-show="myForm.input.$error.required"> 25645 Required!</span> 25646 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 25647 Not a valid date!</span> 25648 </div> 25649 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 25650 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25651 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25652 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25653 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25654 </form> 25655 </file> 25656 <file name="protractor.js" type="protractor"> 25657 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 25658 var valid = element(by.binding('myForm.input.$valid')); 25659 25660 // currently protractor/webdriver does not support 25661 // sending keys to all known HTML5 input controls 25662 // for various browsers (https://github.com/angular/protractor/issues/562). 25663 function setInput(val) { 25664 // set the value of the element and force validation. 25665 var scr = "var ipt = document.getElementById('exampleInput'); " + 25666 "ipt.value = '" + val + "';" + 25667 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25668 browser.executeScript(scr); 25669 } 25670 25671 it('should initialize to model', function() { 25672 expect(value.getText()).toContain('2010-12-28T14:57:00'); 25673 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25674 }); 25675 25676 it('should be invalid if empty', function() { 25677 setInput(''); 25678 expect(value.getText()).toEqual('value ='); 25679 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25680 }); 25681 25682 it('should be invalid if over max', function() { 25683 setInput('2015-01-01T23:59:00'); 25684 expect(value.getText()).toContain(''); 25685 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25686 }); 25687 </file> 25688 </example> 25689 */ 25690 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 25691 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 25692 'yyyy-MM-ddTHH:mm:ss.sss'), 25693 25694 /** 25695 * @ngdoc input 25696 * @name input[time] 25697 * 25698 * @description 25699 * Input with time validation and transformation. In browsers that do not yet support 25700 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25701 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 25702 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 25703 * 25704 * The model must always be a Date object, otherwise AngularJS will throw an error. 25705 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25706 * 25707 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25708 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default, 25709 * this is the timezone of the browser. 25710 * 25711 * The format of the displayed time can be adjusted with the 25712 * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` 25713 * and `timeStripZeroSeconds`. 25714 * 25715 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25716 * @param {string=} name Property name of the form under which the control is published. 25717 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25718 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 25719 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 25720 * native HTML5 constraint validation. 25721 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25722 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 25723 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 25724 * native HTML5 constraint validation. 25725 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 25726 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25727 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 25728 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25729 * @param {string=} required Sets `required` validation error key if the value is not entered. 25730 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25731 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25732 * `required` when you want to data-bind to the `required` attribute. 25733 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25734 * interaction with the input element. 25735 * 25736 * @example 25737 <example name="time-input-directive" module="timeExample"> 25738 <file name="index.html"> 25739 <script> 25740 angular.module('timeExample', []) 25741 .controller('DateController', ['$scope', function($scope) { 25742 $scope.example = { 25743 value: new Date(1970, 0, 1, 14, 57, 0) 25744 }; 25745 }]); 25746 </script> 25747 <form name="myForm" ng-controller="DateController as dateCtrl"> 25748 <label for="exampleInput">Pick a time between 8am and 5pm:</label> 25749 <input type="time" id="exampleInput" name="input" ng-model="example.value" 25750 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 25751 <div role="alert"> 25752 <span class="error" ng-show="myForm.input.$error.required"> 25753 Required!</span> 25754 <span class="error" ng-show="myForm.input.$error.time"> 25755 Not a valid date!</span> 25756 </div> 25757 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 25758 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25759 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25760 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25761 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25762 </form> 25763 </file> 25764 <file name="protractor.js" type="protractor"> 25765 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 25766 var valid = element(by.binding('myForm.input.$valid')); 25767 25768 // currently protractor/webdriver does not support 25769 // sending keys to all known HTML5 input controls 25770 // for various browsers (https://github.com/angular/protractor/issues/562). 25771 function setInput(val) { 25772 // set the value of the element and force validation. 25773 var scr = "var ipt = document.getElementById('exampleInput'); " + 25774 "ipt.value = '" + val + "';" + 25775 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25776 browser.executeScript(scr); 25777 } 25778 25779 it('should initialize to model', function() { 25780 expect(value.getText()).toContain('14:57:00'); 25781 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25782 }); 25783 25784 it('should be invalid if empty', function() { 25785 setInput(''); 25786 expect(value.getText()).toEqual('value ='); 25787 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25788 }); 25789 25790 it('should be invalid if over max', function() { 25791 setInput('23:59:00'); 25792 expect(value.getText()).toContain(''); 25793 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25794 }); 25795 </file> 25796 </example> 25797 */ 25798 'time': createDateInputType('time', TIME_REGEXP, 25799 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 25800 'HH:mm:ss.sss'), 25801 25802 /** 25803 * @ngdoc input 25804 * @name input[week] 25805 * 25806 * @description 25807 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 25808 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25809 * week format (yyyy-W##), for example: `2013-W02`. 25810 * 25811 * The model must always be a Date object, otherwise AngularJS will throw an error. 25812 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25813 * 25814 * The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week, 25815 * due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the 25816 * year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates 25817 * 25818 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25819 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25820 * 25821 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25822 * @param {string=} name Property name of the form under which the control is published. 25823 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25824 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 25825 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 25826 * native HTML5 constraint validation. 25827 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25828 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 25829 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 25830 * native HTML5 constraint validation. 25831 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 25832 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25833 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 25834 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25835 * @param {string=} required Sets `required` validation error key if the value is not entered. 25836 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25837 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25838 * `required` when you want to data-bind to the `required` attribute. 25839 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25840 * interaction with the input element. 25841 * 25842 * @example 25843 <example name="week-input-directive" module="weekExample"> 25844 <file name="index.html"> 25845 <script> 25846 angular.module('weekExample', []) 25847 .controller('DateController', ['$scope', function($scope) { 25848 $scope.example = { 25849 value: new Date(2013, 0, 3) 25850 }; 25851 }]); 25852 </script> 25853 <form name="myForm" ng-controller="DateController as dateCtrl"> 25854 <label>Pick a date between in 2013: 25855 <input id="exampleInput" type="week" name="input" ng-model="example.value" 25856 placeholder="YYYY-W##" min="2012-W32" 25857 max="2013-W52" required /> 25858 </label> 25859 <div role="alert"> 25860 <span class="error" ng-show="myForm.input.$error.required"> 25861 Required!</span> 25862 <span class="error" ng-show="myForm.input.$error.week"> 25863 Not a valid date!</span> 25864 </div> 25865 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 25866 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25867 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25868 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25869 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25870 </form> 25871 </file> 25872 <file name="protractor.js" type="protractor"> 25873 var value = element(by.binding('example.value | date: "yyyy-Www"')); 25874 var valid = element(by.binding('myForm.input.$valid')); 25875 25876 // currently protractor/webdriver does not support 25877 // sending keys to all known HTML5 input controls 25878 // for various browsers (https://github.com/angular/protractor/issues/562). 25879 function setInput(val) { 25880 // set the value of the element and force validation. 25881 var scr = "var ipt = document.getElementById('exampleInput'); " + 25882 "ipt.value = '" + val + "';" + 25883 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25884 browser.executeScript(scr); 25885 } 25886 25887 it('should initialize to model', function() { 25888 expect(value.getText()).toContain('2013-W01'); 25889 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25890 }); 25891 25892 it('should be invalid if empty', function() { 25893 setInput(''); 25894 expect(value.getText()).toEqual('value ='); 25895 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25896 }); 25897 25898 it('should be invalid if over max', function() { 25899 setInput('2015-W01'); 25900 expect(value.getText()).toContain(''); 25901 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25902 }); 25903 </file> 25904 </example> 25905 */ 25906 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 25907 25908 /** 25909 * @ngdoc input 25910 * @name input[month] 25911 * 25912 * @description 25913 * Input with month validation and transformation. In browsers that do not yet support 25914 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25915 * month format (yyyy-MM), for example: `2009-01`. 25916 * 25917 * The model must always be a Date object, otherwise AngularJS will throw an error. 25918 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25919 * If the model is not set to the first of the month, the next view to model update will set it 25920 * to the first of the month. 25921 * 25922 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25923 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25924 * 25925 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 25926 * @param {string=} name Property name of the form under which the control is published. 25927 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25928 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 25929 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 25930 * native HTML5 constraint validation. 25931 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25932 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 25933 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 25934 * native HTML5 constraint validation. 25935 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 25936 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25937 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 25938 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25939 25940 * @param {string=} required Sets `required` validation error key if the value is not entered. 25941 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25942 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25943 * `required` when you want to data-bind to the `required` attribute. 25944 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 25945 * interaction with the input element. 25946 * 25947 * @example 25948 <example name="month-input-directive" module="monthExample"> 25949 <file name="index.html"> 25950 <script> 25951 angular.module('monthExample', []) 25952 .controller('DateController', ['$scope', function($scope) { 25953 $scope.example = { 25954 value: new Date(2013, 9, 1) 25955 }; 25956 }]); 25957 </script> 25958 <form name="myForm" ng-controller="DateController as dateCtrl"> 25959 <label for="exampleInput">Pick a month in 2013:</label> 25960 <input id="exampleInput" type="month" name="input" ng-model="example.value" 25961 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 25962 <div role="alert"> 25963 <span class="error" ng-show="myForm.input.$error.required"> 25964 Required!</span> 25965 <span class="error" ng-show="myForm.input.$error.month"> 25966 Not a valid month!</span> 25967 </div> 25968 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 25969 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25970 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25971 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25972 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25973 </form> 25974 </file> 25975 <file name="protractor.js" type="protractor"> 25976 var value = element(by.binding('example.value | date: "yyyy-MM"')); 25977 var valid = element(by.binding('myForm.input.$valid')); 25978 25979 // currently protractor/webdriver does not support 25980 // sending keys to all known HTML5 input controls 25981 // for various browsers (https://github.com/angular/protractor/issues/562). 25982 function setInput(val) { 25983 // set the value of the element and force validation. 25984 var scr = "var ipt = document.getElementById('exampleInput'); " + 25985 "ipt.value = '" + val + "';" + 25986 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25987 browser.executeScript(scr); 25988 } 25989 25990 it('should initialize to model', function() { 25991 expect(value.getText()).toContain('2013-10'); 25992 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25993 }); 25994 25995 it('should be invalid if empty', function() { 25996 setInput(''); 25997 expect(value.getText()).toEqual('value ='); 25998 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25999 }); 26000 26001 it('should be invalid if over max', function() { 26002 setInput('2015-01'); 26003 expect(value.getText()).toContain(''); 26004 expect(valid.getText()).toContain('myForm.input.$valid = false'); 26005 }); 26006 </file> 26007 </example> 26008 */ 26009 'month': createDateInputType('month', MONTH_REGEXP, 26010 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 26011 'yyyy-MM'), 26012 26013 /** 26014 * @ngdoc input 26015 * @name input[number] 26016 * 26017 * @description 26018 * Text input with number validation and transformation. Sets the `number` validation 26019 * error if not a valid number. 26020 * 26021 * <div class="alert alert-warning"> 26022 * The model must always be of type `number` otherwise AngularJS will throw an error. 26023 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 26024 * error docs for more information and an example of how to convert your model if necessary. 26025 * </div> 26026 * 26027 * 26028 * 26029 * @knownIssue 26030 * 26031 * ### HTML5 constraint validation and `allowInvalid` 26032 * 26033 * In browsers that follow the 26034 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 26035 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 26036 * If a non-number is entered in the input, the browser will report the value as an empty string, 26037 * which means the view / model values in `ngModel` and subsequently the scope value 26038 * will also be an empty string. 26039 * 26040 * @knownIssue 26041 * 26042 * ### Large numbers and `step` validation 26043 * 26044 * The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to 26045 * Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built 26046 * libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by 26047 * {@link guide/forms#modifying-built-in-validators overwriting the validators} 26048 * for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators} 26049 * to an `input[text]` element. The source for `input[number]` type can be used as a starting 26050 * point for both implementations. 26051 * 26052 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26053 * @param {string=} name Property name of the form under which the control is published. 26054 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 26055 * Can be interpolated. 26056 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 26057 * Can be interpolated. 26058 * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`, 26059 * but does not trigger HTML5 native validation. Takes an expression. 26060 * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`, 26061 * but does not trigger HTML5 native validation. Takes an expression. 26062 * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint. 26063 * Can be interpolated. 26064 * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint, 26065 * but does not trigger HTML5 native validation. Takes an expression. 26066 * @param {string=} required Sets `required` validation error key if the value is not entered. 26067 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26068 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26069 * `required` when you want to data-bind to the `required` attribute. 26070 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26071 * minlength. 26072 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26073 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26074 * any length. 26075 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26076 * that contains the regular expression body that will be converted to a regular expression 26077 * as in the ngPattern directive. 26078 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26079 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 26080 * If the expression evaluates to a RegExp object, then this is used directly. 26081 * If the expression evaluates to a string, then it will be converted to a RegExp 26082 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26083 * `new RegExp('^abc$')`.<br /> 26084 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26085 * start at the index of the last search's match, thus not taking the whole input value into 26086 * account. 26087 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26088 * interaction with the input element. 26089 * 26090 * @example 26091 <example name="number-input-directive" module="numberExample"> 26092 <file name="index.html"> 26093 <script> 26094 angular.module('numberExample', []) 26095 .controller('ExampleController', ['$scope', function($scope) { 26096 $scope.example = { 26097 value: 12 26098 }; 26099 }]); 26100 </script> 26101 <form name="myForm" ng-controller="ExampleController"> 26102 <label>Number: 26103 <input type="number" name="input" ng-model="example.value" 26104 min="0" max="99" required> 26105 </label> 26106 <div role="alert"> 26107 <span class="error" ng-show="myForm.input.$error.required"> 26108 Required!</span> 26109 <span class="error" ng-show="myForm.input.$error.number"> 26110 Not valid number!</span> 26111 </div> 26112 <tt>value = {{example.value}}</tt><br/> 26113 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26114 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26115 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26116 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26117 </form> 26118 </file> 26119 <file name="protractor.js" type="protractor"> 26120 var value = element(by.binding('example.value')); 26121 var valid = element(by.binding('myForm.input.$valid')); 26122 var input = element(by.model('example.value')); 26123 26124 it('should initialize to model', function() { 26125 expect(value.getText()).toContain('12'); 26126 expect(valid.getText()).toContain('true'); 26127 }); 26128 26129 it('should be invalid if empty', function() { 26130 input.clear(); 26131 input.sendKeys(''); 26132 expect(value.getText()).toEqual('value ='); 26133 expect(valid.getText()).toContain('false'); 26134 }); 26135 26136 it('should be invalid if over max', function() { 26137 input.clear(); 26138 input.sendKeys('123'); 26139 expect(value.getText()).toEqual('value ='); 26140 expect(valid.getText()).toContain('false'); 26141 }); 26142 </file> 26143 </example> 26144 */ 26145 'number': numberInputType, 26146 26147 26148 /** 26149 * @ngdoc input 26150 * @name input[url] 26151 * 26152 * @description 26153 * Text input with URL validation. Sets the `url` validation error key if the content is not a 26154 * valid URL. 26155 * 26156 * <div class="alert alert-warning"> 26157 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 26158 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 26159 * the built-in validators (see the {@link guide/forms Forms guide}) 26160 * </div> 26161 * 26162 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26163 * @param {string=} name Property name of the form under which the control is published. 26164 * @param {string=} required Sets `required` validation error key if the value is not entered. 26165 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26166 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26167 * `required` when you want to data-bind to the `required` attribute. 26168 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26169 * minlength. 26170 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26171 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26172 * any length. 26173 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26174 * that contains the regular expression body that will be converted to a regular expression 26175 * as in the ngPattern directive. 26176 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26177 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 26178 * If the expression evaluates to a RegExp object, then this is used directly. 26179 * If the expression evaluates to a string, then it will be converted to a RegExp 26180 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26181 * `new RegExp('^abc$')`.<br /> 26182 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26183 * start at the index of the last search's match, thus not taking the whole input value into 26184 * account. 26185 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26186 * interaction with the input element. 26187 * 26188 * @example 26189 <example name="url-input-directive" module="urlExample"> 26190 <file name="index.html"> 26191 <script> 26192 angular.module('urlExample', []) 26193 .controller('ExampleController', ['$scope', function($scope) { 26194 $scope.url = { 26195 text: 'http://google.com' 26196 }; 26197 }]); 26198 </script> 26199 <form name="myForm" ng-controller="ExampleController"> 26200 <label>URL: 26201 <input type="url" name="input" ng-model="url.text" required> 26202 <label> 26203 <div role="alert"> 26204 <span class="error" ng-show="myForm.input.$error.required"> 26205 Required!</span> 26206 <span class="error" ng-show="myForm.input.$error.url"> 26207 Not valid url!</span> 26208 </div> 26209 <tt>text = {{url.text}}</tt><br/> 26210 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26211 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26212 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26213 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26214 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 26215 </form> 26216 </file> 26217 <file name="protractor.js" type="protractor"> 26218 var text = element(by.binding('url.text')); 26219 var valid = element(by.binding('myForm.input.$valid')); 26220 var input = element(by.model('url.text')); 26221 26222 it('should initialize to model', function() { 26223 expect(text.getText()).toContain('http://google.com'); 26224 expect(valid.getText()).toContain('true'); 26225 }); 26226 26227 it('should be invalid if empty', function() { 26228 input.clear(); 26229 input.sendKeys(''); 26230 26231 expect(text.getText()).toEqual('text ='); 26232 expect(valid.getText()).toContain('false'); 26233 }); 26234 26235 it('should be invalid if not url', function() { 26236 input.clear(); 26237 input.sendKeys('box'); 26238 26239 expect(valid.getText()).toContain('false'); 26240 }); 26241 </file> 26242 </example> 26243 */ 26244 'url': urlInputType, 26245 26246 26247 /** 26248 * @ngdoc input 26249 * @name input[email] 26250 * 26251 * @description 26252 * Text input with email validation. Sets the `email` validation error key if not a valid email 26253 * address. 26254 * 26255 * <div class="alert alert-warning"> 26256 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 26257 * used in Chromium, which may not fulfill your app's requirements. 26258 * If you need stricter (e.g. requiring a top-level domain), or more relaxed validation 26259 * (e.g. allowing IPv6 address literals) you can use `ng-pattern` or 26260 * modify the built-in validators (see the {@link guide/forms Forms guide}). 26261 * </div> 26262 * 26263 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26264 * @param {string=} name Property name of the form under which the control is published. 26265 * @param {string=} required Sets `required` validation error key if the value is not entered. 26266 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26267 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26268 * `required` when you want to data-bind to the `required` attribute. 26269 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26270 * minlength. 26271 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26272 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26273 * any length. 26274 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26275 * that contains the regular expression body that will be converted to a regular expression 26276 * as in the ngPattern directive. 26277 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26278 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 26279 * If the expression evaluates to a RegExp object, then this is used directly. 26280 * If the expression evaluates to a string, then it will be converted to a RegExp 26281 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26282 * `new RegExp('^abc$')`.<br /> 26283 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26284 * start at the index of the last search's match, thus not taking the whole input value into 26285 * account. 26286 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26287 * interaction with the input element. 26288 * 26289 * @example 26290 <example name="email-input-directive" module="emailExample"> 26291 <file name="index.html"> 26292 <script> 26293 angular.module('emailExample', []) 26294 .controller('ExampleController', ['$scope', function($scope) { 26295 $scope.email = { 26296 text: '[email protected]' 26297 }; 26298 }]); 26299 </script> 26300 <form name="myForm" ng-controller="ExampleController"> 26301 <label>Email: 26302 <input type="email" name="input" ng-model="email.text" required> 26303 </label> 26304 <div role="alert"> 26305 <span class="error" ng-show="myForm.input.$error.required"> 26306 Required!</span> 26307 <span class="error" ng-show="myForm.input.$error.email"> 26308 Not valid email!</span> 26309 </div> 26310 <tt>text = {{email.text}}</tt><br/> 26311 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26312 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26313 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26314 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26315 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 26316 </form> 26317 </file> 26318 <file name="protractor.js" type="protractor"> 26319 var text = element(by.binding('email.text')); 26320 var valid = element(by.binding('myForm.input.$valid')); 26321 var input = element(by.model('email.text')); 26322 26323 it('should initialize to model', function() { 26324 expect(text.getText()).toContain('[email protected]'); 26325 expect(valid.getText()).toContain('true'); 26326 }); 26327 26328 it('should be invalid if empty', function() { 26329 input.clear(); 26330 input.sendKeys(''); 26331 expect(text.getText()).toEqual('text ='); 26332 expect(valid.getText()).toContain('false'); 26333 }); 26334 26335 it('should be invalid if not email', function() { 26336 input.clear(); 26337 input.sendKeys('xxx'); 26338 26339 expect(valid.getText()).toContain('false'); 26340 }); 26341 </file> 26342 </example> 26343 */ 26344 'email': emailInputType, 26345 26346 26347 /** 26348 * @ngdoc input 26349 * @name input[radio] 26350 * 26351 * @description 26352 * HTML radio button. 26353 * 26354 * **Note:**<br> 26355 * All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the 26356 * value of their `name` attribute to determine the property under which their 26357 * {@link ngModel.NgModelController NgModelController} will be published on the parent 26358 * {@link form.FormController FormController}. Thus, if you use the same `name` for multiple 26359 * inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be 26360 * published on the parent `FormController` under that name. The rest of the controllers will 26361 * continue to work as expected, but you won't be able to access them as properties on the parent 26362 * `FormController`. 26363 * 26364 * <div class="alert alert-info"> 26365 * <p> 26366 * In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so 26367 * that the browser can manage their state (checked/unchecked) based on the state of other 26368 * inputs in the same group. 26369 * </p> 26370 * <p> 26371 * In AngularJS forms, this is not necessary. The input's state will be updated based on the 26372 * value of the underlying model data. 26373 * </p> 26374 * </div> 26375 * 26376 * <div class="alert alert-success"> 26377 * If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a 26378 * unique name. 26379 * </div> 26380 * 26381 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26382 * @param {string} value The value to which the `ngModel` expression should be set when selected. 26383 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 26384 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 26385 * @param {string=} name Property name of the form under which the control is published. 26386 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26387 * interaction with the input element. 26388 * @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio 26389 * is selected. Should be used instead of the `value` attribute if you need 26390 * a non-string `ngModel` (`boolean`, `array`, ...). 26391 * 26392 * @example 26393 <example name="radio-input-directive" module="radioExample"> 26394 <file name="index.html"> 26395 <script> 26396 angular.module('radioExample', []) 26397 .controller('ExampleController', ['$scope', function($scope) { 26398 $scope.color = { 26399 name: 'blue' 26400 }; 26401 $scope.specialValue = { 26402 "id": "12345", 26403 "value": "green" 26404 }; 26405 }]); 26406 </script> 26407 <form name="myForm" ng-controller="ExampleController"> 26408 <label> 26409 <input type="radio" ng-model="color.name" value="red"> 26410 Red 26411 </label><br/> 26412 <label> 26413 <input type="radio" ng-model="color.name" ng-value="specialValue"> 26414 Green 26415 </label><br/> 26416 <label> 26417 <input type="radio" ng-model="color.name" value="blue"> 26418 Blue 26419 </label><br/> 26420 <tt>color = {{color.name | json}}</tt><br/> 26421 </form> 26422 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 26423 </file> 26424 <file name="protractor.js" type="protractor"> 26425 it('should change state', function() { 26426 var inputs = element.all(by.model('color.name')); 26427 var color = element(by.binding('color.name')); 26428 26429 expect(color.getText()).toContain('blue'); 26430 26431 inputs.get(0).click(); 26432 expect(color.getText()).toContain('red'); 26433 26434 inputs.get(1).click(); 26435 expect(color.getText()).toContain('green'); 26436 }); 26437 </file> 26438 </example> 26439 */ 26440 'radio': radioInputType, 26441 26442 /** 26443 * @ngdoc input 26444 * @name input[range] 26445 * 26446 * @description 26447 * Native range input with validation and transformation. 26448 * 26449 * The model for the range input must always be a `Number`. 26450 * 26451 * IE9 and other browsers that do not support the `range` type fall back 26452 * to a text input without any default values for `min`, `max` and `step`. Model binding, 26453 * validation and number parsing are nevertheless supported. 26454 * 26455 * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` 26456 * in a way that never allows the input to hold an invalid value. That means: 26457 * - any non-numerical value is set to `(max + min) / 2`. 26458 * - any numerical value that is less than the current min val, or greater than the current max val 26459 * is set to the min / max val respectively. 26460 * - additionally, the current `step` is respected, so the nearest value that satisfies a step 26461 * is used. 26462 * 26463 * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) 26464 * for more info. 26465 * 26466 * This has the following consequences for AngularJS: 26467 * 26468 * Since the element value should always reflect the current model value, a range input 26469 * will set the bound ngModel expression to the value that the browser has set for the 26470 * input element. For example, in the following input `<input type="range" ng-model="model.value">`, 26471 * if the application sets `model.value = null`, the browser will set the input to `'50'`. 26472 * AngularJS will then set the model to `50`, to prevent input and model value being out of sync. 26473 * 26474 * That means the model for range will immediately be set to `50` after `ngModel` has been 26475 * initialized. It also means a range input can never have the required error. 26476 * 26477 * This does not only affect changes to the model value, but also to the values of the `min`, 26478 * `max`, and `step` attributes. When these change in a way that will cause the browser to modify 26479 * the input value, AngularJS will also update the model value. 26480 * 26481 * Automatic value adjustment also means that a range input element can never have the `required`, 26482 * `min`, or `max` errors. 26483 * 26484 * However, `step` is currently only fully implemented by Firefox. Other browsers have problems 26485 * when the step value changes dynamically - they do not adjust the element value correctly, but 26486 * instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step` 26487 * error on the input, and set the model to `undefined`. 26488 * 26489 * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do 26490 * not set the `min` and `max` attributes, which means that the browser won't automatically adjust 26491 * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. 26492 * 26493 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26494 * @param {string=} name Property name of the form under which the control is published. 26495 * @param {string=} min Sets the `min` validation to ensure that the value entered is greater 26496 * than `min`. Can be interpolated. 26497 * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`. 26498 * Can be interpolated. 26499 * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` 26500 * Can be interpolated. 26501 * @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due 26502 * to user interaction with the input element. 26503 * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the 26504 * element. **Note** : `ngChecked` should not be used alongside `ngModel`. 26505 * Checkout {@link ng.directive:ngChecked ngChecked} for usage. 26506 * 26507 * @example 26508 <example name="range-input-directive" module="rangeExample"> 26509 <file name="index.html"> 26510 <script> 26511 angular.module('rangeExample', []) 26512 .controller('ExampleController', ['$scope', function($scope) { 26513 $scope.value = 75; 26514 $scope.min = 10; 26515 $scope.max = 90; 26516 }]); 26517 </script> 26518 <form name="myForm" ng-controller="ExampleController"> 26519 26520 Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}"> 26521 <hr> 26522 Model as number: <input type="number" ng-model="value"><br> 26523 Min: <input type="number" ng-model="min"><br> 26524 Max: <input type="number" ng-model="max"><br> 26525 value = <code>{{value}}</code><br/> 26526 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/> 26527 myForm.range.$error = <code>{{myForm.range.$error}}</code> 26528 </form> 26529 </file> 26530 </example> 26531 26532 * ## Range Input with ngMin & ngMax attributes 26533 26534 * @example 26535 <example name="range-input-directive-ng" module="rangeExample"> 26536 <file name="index.html"> 26537 <script> 26538 angular.module('rangeExample', []) 26539 .controller('ExampleController', ['$scope', function($scope) { 26540 $scope.value = 75; 26541 $scope.min = 10; 26542 $scope.max = 90; 26543 }]); 26544 </script> 26545 <form name="myForm" ng-controller="ExampleController"> 26546 Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max"> 26547 <hr> 26548 Model as number: <input type="number" ng-model="value"><br> 26549 Min: <input type="number" ng-model="min"><br> 26550 Max: <input type="number" ng-model="max"><br> 26551 value = <code>{{value}}</code><br/> 26552 myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/> 26553 myForm.range.$error = <code>{{myForm.range.$error}}</code> 26554 </form> 26555 </file> 26556 </example> 26557 26558 */ 26559 'range': rangeInputType, 26560 26561 /** 26562 * @ngdoc input 26563 * @name input[checkbox] 26564 * 26565 * @description 26566 * HTML checkbox. 26567 * 26568 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 26569 * @param {string=} name Property name of the form under which the control is published. 26570 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 26571 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 26572 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 26573 * interaction with the input element. 26574 * 26575 * @example 26576 <example name="checkbox-input-directive" module="checkboxExample"> 26577 <file name="index.html"> 26578 <script> 26579 angular.module('checkboxExample', []) 26580 .controller('ExampleController', ['$scope', function($scope) { 26581 $scope.checkboxModel = { 26582 value1 : true, 26583 value2 : 'YES' 26584 }; 26585 }]); 26586 </script> 26587 <form name="myForm" ng-controller="ExampleController"> 26588 <label>Value1: 26589 <input type="checkbox" ng-model="checkboxModel.value1"> 26590 </label><br/> 26591 <label>Value2: 26592 <input type="checkbox" ng-model="checkboxModel.value2" 26593 ng-true-value="'YES'" ng-false-value="'NO'"> 26594 </label><br/> 26595 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 26596 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 26597 </form> 26598 </file> 26599 <file name="protractor.js" type="protractor"> 26600 it('should change state', function() { 26601 var value1 = element(by.binding('checkboxModel.value1')); 26602 var value2 = element(by.binding('checkboxModel.value2')); 26603 26604 expect(value1.getText()).toContain('true'); 26605 expect(value2.getText()).toContain('YES'); 26606 26607 element(by.model('checkboxModel.value1')).click(); 26608 element(by.model('checkboxModel.value2')).click(); 26609 26610 expect(value1.getText()).toContain('false'); 26611 expect(value2.getText()).toContain('NO'); 26612 }); 26613 </file> 26614 </example> 26615 */ 26616 'checkbox': checkboxInputType, 26617 26618 'hidden': noop, 26619 'button': noop, 26620 'submit': noop, 26621 'reset': noop, 26622 'file': noop 26623}; 26624 26625function stringBasedInputType(ctrl) { 26626 ctrl.$formatters.push(function(value) { 26627 return ctrl.$isEmpty(value) ? value : value.toString(); 26628 }); 26629} 26630 26631function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 26632 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 26633 stringBasedInputType(ctrl); 26634} 26635 26636function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 26637 var type = lowercase(element[0].type); 26638 26639 // In composition mode, users are still inputting intermediate text buffer, 26640 // hold the listener until composition is done. 26641 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 26642 if (!$sniffer.android) { 26643 var composing = false; 26644 26645 element.on('compositionstart', function() { 26646 composing = true; 26647 }); 26648 26649 // Support: IE9+ 26650 element.on('compositionupdate', function(ev) { 26651 // End composition when ev.data is empty string on 'compositionupdate' event. 26652 // When the input de-focusses (e.g. by clicking away), IE triggers 'compositionupdate' 26653 // instead of 'compositionend'. 26654 if (isUndefined(ev.data) || ev.data === '') { 26655 composing = false; 26656 } 26657 }); 26658 26659 element.on('compositionend', function() { 26660 composing = false; 26661 listener(); 26662 }); 26663 } 26664 26665 var timeout; 26666 26667 var listener = function(ev) { 26668 if (timeout) { 26669 $browser.defer.cancel(timeout); 26670 timeout = null; 26671 } 26672 if (composing) return; 26673 var value = element.val(), 26674 event = ev && ev.type; 26675 26676 // By default we will trim the value 26677 // If the attribute ng-trim exists we will avoid trimming 26678 // If input type is 'password', the value is never trimmed 26679 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 26680 value = trim(value); 26681 } 26682 26683 // If a control is suffering from bad input (due to native validators), browsers discard its 26684 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 26685 // control's value is the same empty value twice in a row. 26686 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 26687 ctrl.$setViewValue(value, event); 26688 } 26689 }; 26690 26691 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 26692 // input event on backspace, delete or cut 26693 if ($sniffer.hasEvent('input')) { 26694 element.on('input', listener); 26695 } else { 26696 var deferListener = function(ev, input, origValue) { 26697 if (!timeout) { 26698 timeout = $browser.defer(function() { 26699 timeout = null; 26700 if (!input || input.value !== origValue) { 26701 listener(ev); 26702 } 26703 }); 26704 } 26705 }; 26706 26707 element.on('keydown', /** @this */ function(event) { 26708 var key = event.keyCode; 26709 26710 // ignore 26711 // command modifiers arrows 26712 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 26713 26714 deferListener(event, this, this.value); 26715 }); 26716 26717 // if user modifies input value using context menu in IE, we need "paste", "cut" and "drop" events to catch it 26718 if ($sniffer.hasEvent('paste')) { 26719 element.on('paste cut drop', deferListener); 26720 } 26721 } 26722 26723 // if user paste into input using mouse on older browser 26724 // or form autocomplete on newer browser, we need "change" event to catch it 26725 element.on('change', listener); 26726 26727 // Some native input types (date-family) have the ability to change validity without 26728 // firing any input/change events. 26729 // For these event types, when native validators are present and the browser supports the type, 26730 // check for validity changes on various DOM events. 26731 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { 26732 element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { 26733 if (!timeout) { 26734 var validity = this[VALIDITY_STATE_PROPERTY]; 26735 var origBadInput = validity.badInput; 26736 var origTypeMismatch = validity.typeMismatch; 26737 timeout = $browser.defer(function() { 26738 timeout = null; 26739 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { 26740 listener(ev); 26741 } 26742 }); 26743 } 26744 }); 26745 } 26746 26747 ctrl.$render = function() { 26748 // Workaround for Firefox validation #12102. 26749 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 26750 if (element.val() !== value) { 26751 element.val(value); 26752 } 26753 }; 26754} 26755 26756function weekParser(isoWeek, existingDate) { 26757 if (isDate(isoWeek)) { 26758 return isoWeek; 26759 } 26760 26761 if (isString(isoWeek)) { 26762 WEEK_REGEXP.lastIndex = 0; 26763 var parts = WEEK_REGEXP.exec(isoWeek); 26764 if (parts) { 26765 var year = +parts[1], 26766 week = +parts[2], 26767 hours = 0, 26768 minutes = 0, 26769 seconds = 0, 26770 milliseconds = 0, 26771 firstThurs = getFirstThursdayOfYear(year), 26772 addDays = (week - 1) * 7; 26773 26774 if (existingDate) { 26775 hours = existingDate.getHours(); 26776 minutes = existingDate.getMinutes(); 26777 seconds = existingDate.getSeconds(); 26778 milliseconds = existingDate.getMilliseconds(); 26779 } 26780 26781 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 26782 } 26783 } 26784 26785 return NaN; 26786} 26787 26788function createDateParser(regexp, mapping) { 26789 return function(iso, previousDate) { 26790 var parts, map; 26791 26792 if (isDate(iso)) { 26793 return iso; 26794 } 26795 26796 if (isString(iso)) { 26797 // When a date is JSON'ified to wraps itself inside of an extra 26798 // set of double quotes. This makes the date parsing code unable 26799 // to match the date string and parse it as a date. 26800 if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { 26801 iso = iso.substring(1, iso.length - 1); 26802 } 26803 if (ISO_DATE_REGEXP.test(iso)) { 26804 return new Date(iso); 26805 } 26806 regexp.lastIndex = 0; 26807 parts = regexp.exec(iso); 26808 26809 if (parts) { 26810 parts.shift(); 26811 if (previousDate) { 26812 map = { 26813 yyyy: previousDate.getFullYear(), 26814 MM: previousDate.getMonth() + 1, 26815 dd: previousDate.getDate(), 26816 HH: previousDate.getHours(), 26817 mm: previousDate.getMinutes(), 26818 ss: previousDate.getSeconds(), 26819 sss: previousDate.getMilliseconds() / 1000 26820 }; 26821 } else { 26822 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 26823 } 26824 26825 forEach(parts, function(part, index) { 26826 if (index < mapping.length) { 26827 map[mapping[index]] = +part; 26828 } 26829 }); 26830 26831 var date = new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 26832 if (map.yyyy < 100) { 26833 // In the constructor, 2-digit years map to 1900-1999. 26834 // Use `setFullYear()` to set the correct year. 26835 date.setFullYear(map.yyyy); 26836 } 26837 26838 return date; 26839 } 26840 } 26841 26842 return NaN; 26843 }; 26844} 26845 26846function createDateInputType(type, regexp, parseDate, format) { 26847 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 26848 badInputChecker(scope, element, attr, ctrl, type); 26849 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 26850 26851 var isTimeType = type === 'time' || type === 'datetimelocal'; 26852 var previousDate; 26853 var previousTimezone; 26854 26855 ctrl.$parsers.push(function(value) { 26856 if (ctrl.$isEmpty(value)) return null; 26857 26858 if (regexp.test(value)) { 26859 // Note: We cannot read ctrl.$modelValue, as there might be a different 26860 // parser/formatter in the processing chain so that the model 26861 // contains some different data format! 26862 return parseDateAndConvertTimeZoneToLocal(value, previousDate); 26863 } 26864 ctrl.$$parserName = type; 26865 return undefined; 26866 }); 26867 26868 ctrl.$formatters.push(function(value) { 26869 if (value && !isDate(value)) { 26870 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 26871 } 26872 if (isValidDate(value)) { 26873 previousDate = value; 26874 var timezone = ctrl.$options.getOption('timezone'); 26875 26876 if (timezone) { 26877 previousTimezone = timezone; 26878 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 26879 } 26880 26881 return formatter(value, timezone); 26882 } else { 26883 previousDate = null; 26884 previousTimezone = null; 26885 return ''; 26886 } 26887 }); 26888 26889 if (isDefined(attr.min) || attr.ngMin) { 26890 var minVal = attr.min || $parse(attr.ngMin)(scope); 26891 var parsedMinVal = parseObservedDateValue(minVal); 26892 26893 ctrl.$validators.min = function(value) { 26894 return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal; 26895 }; 26896 attr.$observe('min', function(val) { 26897 if (val !== minVal) { 26898 parsedMinVal = parseObservedDateValue(val); 26899 minVal = val; 26900 ctrl.$validate(); 26901 } 26902 }); 26903 } 26904 26905 if (isDefined(attr.max) || attr.ngMax) { 26906 var maxVal = attr.max || $parse(attr.ngMax)(scope); 26907 var parsedMaxVal = parseObservedDateValue(maxVal); 26908 26909 ctrl.$validators.max = function(value) { 26910 return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal; 26911 }; 26912 attr.$observe('max', function(val) { 26913 if (val !== maxVal) { 26914 parsedMaxVal = parseObservedDateValue(val); 26915 maxVal = val; 26916 ctrl.$validate(); 26917 } 26918 }); 26919 } 26920 26921 function isValidDate(value) { 26922 // Invalid Date: getTime() returns NaN 26923 return value && !(value.getTime && value.getTime() !== value.getTime()); 26924 } 26925 26926 function parseObservedDateValue(val) { 26927 return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val; 26928 } 26929 26930 function parseDateAndConvertTimeZoneToLocal(value, previousDate) { 26931 var timezone = ctrl.$options.getOption('timezone'); 26932 26933 if (previousTimezone && previousTimezone !== timezone) { 26934 // If the timezone has changed, adjust the previousDate to the default timezone 26935 // so that the new date is converted with the correct timezone offset 26936 previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone)); 26937 } 26938 26939 var parsedDate = parseDate(value, previousDate); 26940 26941 if (!isNaN(parsedDate) && timezone) { 26942 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 26943 } 26944 return parsedDate; 26945 } 26946 26947 function formatter(value, timezone) { 26948 var targetFormat = format; 26949 26950 if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) { 26951 targetFormat = format 26952 .replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat')) 26953 .replace(/:$/, ''); 26954 } 26955 26956 var formatted = $filter('date')(value, targetFormat, timezone); 26957 26958 if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) { 26959 formatted = formatted.replace(/(?::00)?(?:\.000)?$/, ''); 26960 } 26961 26962 return formatted; 26963 } 26964 }; 26965} 26966 26967function badInputChecker(scope, element, attr, ctrl, parserName) { 26968 var node = element[0]; 26969 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 26970 if (nativeValidation) { 26971 ctrl.$parsers.push(function(value) { 26972 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 26973 if (validity.badInput || validity.typeMismatch) { 26974 ctrl.$$parserName = parserName; 26975 return undefined; 26976 } 26977 26978 return value; 26979 }); 26980 } 26981} 26982 26983function numberFormatterParser(ctrl) { 26984 ctrl.$parsers.push(function(value) { 26985 if (ctrl.$isEmpty(value)) return null; 26986 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 26987 26988 ctrl.$$parserName = 'number'; 26989 return undefined; 26990 }); 26991 26992 ctrl.$formatters.push(function(value) { 26993 if (!ctrl.$isEmpty(value)) { 26994 if (!isNumber(value)) { 26995 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 26996 } 26997 value = value.toString(); 26998 } 26999 return value; 27000 }); 27001} 27002 27003function parseNumberAttrVal(val) { 27004 if (isDefined(val) && !isNumber(val)) { 27005 val = parseFloat(val); 27006 } 27007 return !isNumberNaN(val) ? val : undefined; 27008} 27009 27010function isNumberInteger(num) { 27011 // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066 27012 // (minus the assumption that `num` is a number) 27013 27014 // eslint-disable-next-line no-bitwise 27015 return (num | 0) === num; 27016} 27017 27018function countDecimals(num) { 27019 var numString = num.toString(); 27020 var decimalSymbolIndex = numString.indexOf('.'); 27021 27022 if (decimalSymbolIndex === -1) { 27023 if (-1 < num && num < 1) { 27024 // It may be in the exponential notation format (`1e-X`) 27025 var match = /e-(\d+)$/.exec(numString); 27026 27027 if (match) { 27028 return Number(match[1]); 27029 } 27030 } 27031 27032 return 0; 27033 } 27034 27035 return numString.length - decimalSymbolIndex - 1; 27036} 27037 27038function isValidForStep(viewValue, stepBase, step) { 27039 // At this point `stepBase` and `step` are expected to be non-NaN values 27040 // and `viewValue` is expected to be a valid stringified number. 27041 var value = Number(viewValue); 27042 27043 var isNonIntegerValue = !isNumberInteger(value); 27044 var isNonIntegerStepBase = !isNumberInteger(stepBase); 27045 var isNonIntegerStep = !isNumberInteger(step); 27046 27047 // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or 27048 // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. 27049 if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) { 27050 var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0; 27051 var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0; 27052 var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0; 27053 27054 var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals); 27055 var multiplier = Math.pow(10, decimalCount); 27056 27057 value = value * multiplier; 27058 stepBase = stepBase * multiplier; 27059 step = step * multiplier; 27060 27061 if (isNonIntegerValue) value = Math.round(value); 27062 if (isNonIntegerStepBase) stepBase = Math.round(stepBase); 27063 if (isNonIntegerStep) step = Math.round(step); 27064 } 27065 27066 return (value - stepBase) % step === 0; 27067} 27068 27069function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 27070 badInputChecker(scope, element, attr, ctrl, 'number'); 27071 numberFormatterParser(ctrl); 27072 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27073 27074 var parsedMinVal; 27075 27076 if (isDefined(attr.min) || attr.ngMin) { 27077 var minVal = attr.min || $parse(attr.ngMin)(scope); 27078 parsedMinVal = parseNumberAttrVal(minVal); 27079 27080 ctrl.$validators.min = function(modelValue, viewValue) { 27081 return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal; 27082 }; 27083 27084 attr.$observe('min', function(val) { 27085 if (val !== minVal) { 27086 parsedMinVal = parseNumberAttrVal(val); 27087 minVal = val; 27088 // TODO(matsko): implement validateLater to reduce number of validations 27089 ctrl.$validate(); 27090 } 27091 }); 27092 } 27093 27094 if (isDefined(attr.max) || attr.ngMax) { 27095 var maxVal = attr.max || $parse(attr.ngMax)(scope); 27096 var parsedMaxVal = parseNumberAttrVal(maxVal); 27097 27098 ctrl.$validators.max = function(modelValue, viewValue) { 27099 return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal; 27100 }; 27101 27102 attr.$observe('max', function(val) { 27103 if (val !== maxVal) { 27104 parsedMaxVal = parseNumberAttrVal(val); 27105 maxVal = val; 27106 // TODO(matsko): implement validateLater to reduce number of validations 27107 ctrl.$validate(); 27108 } 27109 }); 27110 } 27111 27112 if (isDefined(attr.step) || attr.ngStep) { 27113 var stepVal = attr.step || $parse(attr.ngStep)(scope); 27114 var parsedStepVal = parseNumberAttrVal(stepVal); 27115 27116 ctrl.$validators.step = function(modelValue, viewValue) { 27117 return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) || 27118 isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal); 27119 }; 27120 27121 attr.$observe('step', function(val) { 27122 // TODO(matsko): implement validateLater to reduce number of validations 27123 if (val !== stepVal) { 27124 parsedStepVal = parseNumberAttrVal(val); 27125 stepVal = val; 27126 ctrl.$validate(); 27127 } 27128 27129 }); 27130 27131 } 27132} 27133 27134function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27135 badInputChecker(scope, element, attr, ctrl, 'range'); 27136 numberFormatterParser(ctrl); 27137 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27138 27139 var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', 27140 minVal = supportsRange ? 0 : undefined, 27141 maxVal = supportsRange ? 100 : undefined, 27142 stepVal = supportsRange ? 1 : undefined, 27143 validity = element[0].validity, 27144 hasMinAttr = isDefined(attr.min), 27145 hasMaxAttr = isDefined(attr.max), 27146 hasStepAttr = isDefined(attr.step); 27147 27148 var originalRender = ctrl.$render; 27149 27150 ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? 27151 //Browsers that implement range will set these values automatically, but reading the adjusted values after 27152 //$render would cause the min / max validators to be applied with the wrong value 27153 function rangeRender() { 27154 originalRender(); 27155 ctrl.$setViewValue(element.val()); 27156 } : 27157 originalRender; 27158 27159 if (hasMinAttr) { 27160 minVal = parseNumberAttrVal(attr.min); 27161 27162 ctrl.$validators.min = supportsRange ? 27163 // Since all browsers set the input to a valid value, we don't need to check validity 27164 function noopMinValidator() { return true; } : 27165 // non-support browsers validate the min val 27166 function minValidator(modelValue, viewValue) { 27167 return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; 27168 }; 27169 27170 setInitialValueAndObserver('min', minChange); 27171 } 27172 27173 if (hasMaxAttr) { 27174 maxVal = parseNumberAttrVal(attr.max); 27175 27176 ctrl.$validators.max = supportsRange ? 27177 // Since all browsers set the input to a valid value, we don't need to check validity 27178 function noopMaxValidator() { return true; } : 27179 // non-support browsers validate the max val 27180 function maxValidator(modelValue, viewValue) { 27181 return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; 27182 }; 27183 27184 setInitialValueAndObserver('max', maxChange); 27185 } 27186 27187 if (hasStepAttr) { 27188 stepVal = parseNumberAttrVal(attr.step); 27189 27190 ctrl.$validators.step = supportsRange ? 27191 function nativeStepValidator() { 27192 // Currently, only FF implements the spec on step change correctly (i.e. adjusting the 27193 // input element value to a valid value). It's possible that other browsers set the stepMismatch 27194 // validity error instead, so we can at least report an error in that case. 27195 return !validity.stepMismatch; 27196 } : 27197 // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would 27198 function stepValidator(modelValue, viewValue) { 27199 return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || 27200 isValidForStep(viewValue, minVal || 0, stepVal); 27201 }; 27202 27203 setInitialValueAndObserver('step', stepChange); 27204 } 27205 27206 function setInitialValueAndObserver(htmlAttrName, changeFn) { 27207 // interpolated attributes set the attribute value only after a digest, but we need the 27208 // attribute value when the input is first rendered, so that the browser can adjust the 27209 // input value based on the min/max value 27210 element.attr(htmlAttrName, attr[htmlAttrName]); 27211 var oldVal = attr[htmlAttrName]; 27212 attr.$observe(htmlAttrName, function wrappedObserver(val) { 27213 if (val !== oldVal) { 27214 oldVal = val; 27215 changeFn(val); 27216 } 27217 }); 27218 } 27219 27220 function minChange(val) { 27221 minVal = parseNumberAttrVal(val); 27222 // ignore changes before model is initialized 27223 if (isNumberNaN(ctrl.$modelValue)) { 27224 return; 27225 } 27226 27227 if (supportsRange) { 27228 var elVal = element.val(); 27229 // IE11 doesn't set the el val correctly if the minVal is greater than the element value 27230 if (minVal > elVal) { 27231 elVal = minVal; 27232 element.val(elVal); 27233 } 27234 ctrl.$setViewValue(elVal); 27235 } else { 27236 // TODO(matsko): implement validateLater to reduce number of validations 27237 ctrl.$validate(); 27238 } 27239 } 27240 27241 function maxChange(val) { 27242 maxVal = parseNumberAttrVal(val); 27243 // ignore changes before model is initialized 27244 if (isNumberNaN(ctrl.$modelValue)) { 27245 return; 27246 } 27247 27248 if (supportsRange) { 27249 var elVal = element.val(); 27250 // IE11 doesn't set the el val correctly if the maxVal is less than the element value 27251 if (maxVal < elVal) { 27252 element.val(maxVal); 27253 // IE11 and Chrome don't set the value to the minVal when max < min 27254 elVal = maxVal < minVal ? minVal : maxVal; 27255 } 27256 ctrl.$setViewValue(elVal); 27257 } else { 27258 // TODO(matsko): implement validateLater to reduce number of validations 27259 ctrl.$validate(); 27260 } 27261 } 27262 27263 function stepChange(val) { 27264 stepVal = parseNumberAttrVal(val); 27265 // ignore changes before model is initialized 27266 if (isNumberNaN(ctrl.$modelValue)) { 27267 return; 27268 } 27269 27270 // Some browsers don't adjust the input value correctly, but set the stepMismatch error 27271 if (!supportsRange) { 27272 // TODO(matsko): implement validateLater to reduce number of validations 27273 ctrl.$validate(); 27274 } else if (ctrl.$viewValue !== element.val()) { 27275 ctrl.$setViewValue(element.val()); 27276 } 27277 } 27278} 27279 27280function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27281 // Note: no badInputChecker here by purpose as `url` is only a validation 27282 // in browsers, i.e. we can always read out input.value even if it is not valid! 27283 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27284 stringBasedInputType(ctrl); 27285 27286 ctrl.$validators.url = function(modelValue, viewValue) { 27287 var value = modelValue || viewValue; 27288 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 27289 }; 27290} 27291 27292function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27293 // Note: no badInputChecker here by purpose as `url` is only a validation 27294 // in browsers, i.e. we can always read out input.value even if it is not valid! 27295 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27296 stringBasedInputType(ctrl); 27297 27298 ctrl.$validators.email = function(modelValue, viewValue) { 27299 var value = modelValue || viewValue; 27300 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 27301 }; 27302} 27303 27304function radioInputType(scope, element, attr, ctrl) { 27305 var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false'; 27306 // make the name unique, if not defined 27307 if (isUndefined(attr.name)) { 27308 element.attr('name', nextUid()); 27309 } 27310 27311 var listener = function(ev) { 27312 var value; 27313 if (element[0].checked) { 27314 value = attr.value; 27315 if (doTrim) { 27316 value = trim(value); 27317 } 27318 ctrl.$setViewValue(value, ev && ev.type); 27319 } 27320 }; 27321 27322 element.on('change', listener); 27323 27324 ctrl.$render = function() { 27325 var value = attr.value; 27326 if (doTrim) { 27327 value = trim(value); 27328 } 27329 element[0].checked = (value === ctrl.$viewValue); 27330 }; 27331 27332 attr.$observe('value', ctrl.$render); 27333} 27334 27335function parseConstantExpr($parse, context, name, expression, fallback) { 27336 var parseFn; 27337 if (isDefined(expression)) { 27338 parseFn = $parse(expression); 27339 if (!parseFn.constant) { 27340 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 27341 '`{1}`.', name, expression); 27342 } 27343 return parseFn(context); 27344 } 27345 return fallback; 27346} 27347 27348function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 27349 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 27350 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 27351 27352 var listener = function(ev) { 27353 ctrl.$setViewValue(element[0].checked, ev && ev.type); 27354 }; 27355 27356 element.on('change', listener); 27357 27358 ctrl.$render = function() { 27359 element[0].checked = ctrl.$viewValue; 27360 }; 27361 27362 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 27363 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 27364 // it to a boolean. 27365 ctrl.$isEmpty = function(value) { 27366 return value === false; 27367 }; 27368 27369 ctrl.$formatters.push(function(value) { 27370 return equals(value, trueValue); 27371 }); 27372 27373 ctrl.$parsers.push(function(value) { 27374 return value ? trueValue : falseValue; 27375 }); 27376} 27377 27378 27379/** 27380 * @ngdoc directive 27381 * @name textarea 27382 * @restrict E 27383 * 27384 * @description 27385 * HTML textarea element control with AngularJS data-binding. The data-binding and validation 27386 * properties of this element are exactly the same as those of the 27387 * {@link ng.directive:input input element}. 27388 * 27389 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 27390 * @param {string=} name Property name of the form under which the control is published. 27391 * @param {string=} required Sets `required` validation error key if the value is not entered. 27392 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27393 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27394 * `required` when you want to data-bind to the `required` attribute. 27395 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27396 * minlength. 27397 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27398 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 27399 * length. 27400 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27401 * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 27402 * If the expression evaluates to a RegExp object, then this is used directly. 27403 * If the expression evaluates to a string, then it will be converted to a RegExp 27404 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27405 * `new RegExp('^abc$')`.<br /> 27406 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27407 * start at the index of the last search's match, thus not taking the whole input value into 27408 * account. 27409 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 27410 * interaction with the input element. 27411 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. 27412 * 27413 * @knownIssue 27414 * 27415 * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily 27416 * insert the placeholder value as the textarea's content. If the placeholder value contains 27417 * interpolation (`{{ ... }}`), an error will be logged in the console when AngularJS tries to update 27418 * the value of the by-then-removed text node. This doesn't affect the functionality of the 27419 * textarea, but can be undesirable. 27420 * 27421 * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of 27422 * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can 27423 * find more details on `ngAttr` in the 27424 * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the 27425 * Developer Guide. 27426 */ 27427 27428 27429/** 27430 * @ngdoc directive 27431 * @name input 27432 * @restrict E 27433 * 27434 * @description 27435 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 27436 * input state control, and validation. 27437 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 27438 * 27439 * <div class="alert alert-warning"> 27440 * **Note:** Not every feature offered is available for all input types. 27441 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 27442 * </div> 27443 * 27444 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 27445 * @param {string=} name Property name of the form under which the control is published. 27446 * @param {string=} required Sets `required` validation error key if the value is not entered. 27447 * @param {boolean=} ngRequired Sets `required` attribute if set to true 27448 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27449 * minlength. 27450 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27451 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 27452 * length. 27453 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27454 * value does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. 27455 * If the expression evaluates to a RegExp object, then this is used directly. 27456 * If the expression evaluates to a string, then it will be converted to a RegExp 27457 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27458 * `new RegExp('^abc$')`.<br /> 27459 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27460 * start at the index of the last search's match, thus not taking the whole input value into 27461 * account. 27462 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user 27463 * interaction with the input element. 27464 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. 27465 * This parameter is ignored for input[type=password] controls, which will never trim the 27466 * input. 27467 * 27468 * @example 27469 <example name="input-directive" module="inputExample"> 27470 <file name="index.html"> 27471 <script> 27472 angular.module('inputExample', []) 27473 .controller('ExampleController', ['$scope', function($scope) { 27474 $scope.user = {name: 'guest', last: 'visitor'}; 27475 }]); 27476 </script> 27477 <div ng-controller="ExampleController"> 27478 <form name="myForm"> 27479 <label> 27480 User name: 27481 <input type="text" name="userName" ng-model="user.name" required> 27482 </label> 27483 <div role="alert"> 27484 <span class="error" ng-show="myForm.userName.$error.required"> 27485 Required!</span> 27486 </div> 27487 <label> 27488 Last name: 27489 <input type="text" name="lastName" ng-model="user.last" 27490 ng-minlength="3" ng-maxlength="10"> 27491 </label> 27492 <div role="alert"> 27493 <span class="error" ng-show="myForm.lastName.$error.minlength"> 27494 Too short!</span> 27495 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 27496 Too long!</span> 27497 </div> 27498 </form> 27499 <hr> 27500 <tt>user = {{user}}</tt><br/> 27501 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 27502 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 27503 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 27504 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 27505 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27506 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27507 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 27508 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 27509 </div> 27510 </file> 27511 <file name="protractor.js" type="protractor"> 27512 var user = element(by.exactBinding('user')); 27513 var userNameValid = element(by.binding('myForm.userName.$valid')); 27514 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 27515 var lastNameError = element(by.binding('myForm.lastName.$error')); 27516 var formValid = element(by.binding('myForm.$valid')); 27517 var userNameInput = element(by.model('user.name')); 27518 var userLastInput = element(by.model('user.last')); 27519 27520 it('should initialize to model', function() { 27521 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 27522 expect(userNameValid.getText()).toContain('true'); 27523 expect(formValid.getText()).toContain('true'); 27524 }); 27525 27526 it('should be invalid if empty when required', function() { 27527 userNameInput.clear(); 27528 userNameInput.sendKeys(''); 27529 27530 expect(user.getText()).toContain('{"last":"visitor"}'); 27531 expect(userNameValid.getText()).toContain('false'); 27532 expect(formValid.getText()).toContain('false'); 27533 }); 27534 27535 it('should be valid if empty when min length is set', function() { 27536 userLastInput.clear(); 27537 userLastInput.sendKeys(''); 27538 27539 expect(user.getText()).toContain('{"name":"guest","last":""}'); 27540 expect(lastNameValid.getText()).toContain('true'); 27541 expect(formValid.getText()).toContain('true'); 27542 }); 27543 27544 it('should be invalid if less than required min length', function() { 27545 userLastInput.clear(); 27546 userLastInput.sendKeys('xx'); 27547 27548 expect(user.getText()).toContain('{"name":"guest"}'); 27549 expect(lastNameValid.getText()).toContain('false'); 27550 expect(lastNameError.getText()).toContain('minlength'); 27551 expect(formValid.getText()).toContain('false'); 27552 }); 27553 27554 it('should be invalid if longer than max length', function() { 27555 userLastInput.clear(); 27556 userLastInput.sendKeys('some ridiculously long name'); 27557 27558 expect(user.getText()).toContain('{"name":"guest"}'); 27559 expect(lastNameValid.getText()).toContain('false'); 27560 expect(lastNameError.getText()).toContain('maxlength'); 27561 expect(formValid.getText()).toContain('false'); 27562 }); 27563 </file> 27564 </example> 27565 */ 27566var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 27567 function($browser, $sniffer, $filter, $parse) { 27568 return { 27569 restrict: 'E', 27570 require: ['?ngModel'], 27571 link: { 27572 pre: function(scope, element, attr, ctrls) { 27573 if (ctrls[0]) { 27574 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 27575 $browser, $filter, $parse); 27576 } 27577 } 27578 } 27579 }; 27580}]; 27581 27582 27583var hiddenInputBrowserCacheDirective = function() { 27584 var valueProperty = { 27585 configurable: true, 27586 enumerable: false, 27587 get: function() { 27588 return this.getAttribute('value') || ''; 27589 }, 27590 set: function(val) { 27591 this.setAttribute('value', val); 27592 } 27593 }; 27594 27595 return { 27596 restrict: 'E', 27597 priority: 200, 27598 compile: function(_, attr) { 27599 if (lowercase(attr.type) !== 'hidden') { 27600 return; 27601 } 27602 27603 return { 27604 pre: function(scope, element, attr, ctrls) { 27605 var node = element[0]; 27606 27607 // Support: Edge 27608 // Moving the DOM around prevents autofillling 27609 if (node.parentNode) { 27610 node.parentNode.insertBefore(node, node.nextSibling); 27611 } 27612 27613 // Support: FF, IE 27614 // Avoiding direct assignment to .value prevents autofillling 27615 if (Object.defineProperty) { 27616 Object.defineProperty(node, 'value', valueProperty); 27617 } 27618 } 27619 }; 27620 } 27621 }; 27622}; 27623 27624 27625 27626var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 27627/** 27628 * @ngdoc directive 27629 * @name ngValue 27630 * @restrict A 27631 * @priority 100 27632 * 27633 * @description 27634 * Binds the given expression to the value of the element. 27635 * 27636 * It is mainly used on {@link input[radio] `input[radio]`} and option elements, 27637 * so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its 27638 * {@link select `select`} parent element) is set to the bound value. It is especially useful 27639 * for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below. 27640 * 27641 * It can also be used to achieve one-way binding of a given expression to an input element 27642 * such as an `input[text]` or a `textarea`, when that element does not use ngModel. 27643 * 27644 * @element ANY 27645 * @param {string=} ngValue AngularJS expression, whose value will be bound to the `value` attribute 27646 * and `value` property of the element. 27647 * 27648 * @example 27649 <example name="ngValue-directive" module="valueExample"> 27650 <file name="index.html"> 27651 <script> 27652 angular.module('valueExample', []) 27653 .controller('ExampleController', ['$scope', function($scope) { 27654 $scope.names = ['pizza', 'unicorns', 'robots']; 27655 $scope.my = { favorite: 'unicorns' }; 27656 }]); 27657 </script> 27658 <form ng-controller="ExampleController"> 27659 <h2>Which is your favorite?</h2> 27660 <label ng-repeat="name in names" for="{{name}}"> 27661 {{name}} 27662 <input type="radio" 27663 ng-model="my.favorite" 27664 ng-value="name" 27665 id="{{name}}" 27666 name="favorite"> 27667 </label> 27668 <div>You chose {{my.favorite}}</div> 27669 </form> 27670 </file> 27671 <file name="protractor.js" type="protractor"> 27672 var favorite = element(by.binding('my.favorite')); 27673 27674 it('should initialize to model', function() { 27675 expect(favorite.getText()).toContain('unicorns'); 27676 }); 27677 it('should bind the values to the inputs', function() { 27678 element.all(by.model('my.favorite')).get(0).click(); 27679 expect(favorite.getText()).toContain('pizza'); 27680 }); 27681 </file> 27682 </example> 27683 */ 27684var ngValueDirective = function() { 27685 /** 27686 * inputs use the value attribute as their default value if the value property is not set. 27687 * Once the value property has been set (by adding input), it will not react to changes to 27688 * the value attribute anymore. Setting both attribute and property fixes this behavior, and 27689 * makes it possible to use ngValue as a sort of one-way bind. 27690 */ 27691 function updateElementValue(element, attr, value) { 27692 // Support: IE9 only 27693 // In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`). 27694 var propValue = isDefined(value) ? value : (msie === 9) ? '' : null; 27695 element.prop('value', propValue); 27696 attr.$set('value', value); 27697 } 27698 27699 return { 27700 restrict: 'A', 27701 priority: 100, 27702 compile: function(tpl, tplAttr) { 27703 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 27704 return function ngValueConstantLink(scope, elm, attr) { 27705 var value = scope.$eval(attr.ngValue); 27706 updateElementValue(elm, attr, value); 27707 }; 27708 } else { 27709 return function ngValueLink(scope, elm, attr) { 27710 scope.$watch(attr.ngValue, function valueWatchAction(value) { 27711 updateElementValue(elm, attr, value); 27712 }); 27713 }; 27714 } 27715 } 27716 }; 27717}; 27718 27719/** 27720 * @ngdoc directive 27721 * @name ngBind 27722 * @restrict AC 27723 * 27724 * @description 27725 * The `ngBind` attribute tells AngularJS to replace the text content of the specified HTML element 27726 * with the value of a given expression, and to update the text content when the value of that 27727 * expression changes. 27728 * 27729 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 27730 * `{{ expression }}` which is similar but less verbose. 27731 * 27732 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 27733 * displayed by the browser in its raw state before AngularJS compiles it. Since `ngBind` is an 27734 * element attribute, it makes the bindings invisible to the user while the page is loading. 27735 * 27736 * An alternative solution to this problem would be using the 27737 * {@link ng.directive:ngCloak ngCloak} directive. 27738 * 27739 * 27740 * @element ANY 27741 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 27742 * 27743 * @example 27744 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 27745 <example module="bindExample" name="ng-bind"> 27746 <file name="index.html"> 27747 <script> 27748 angular.module('bindExample', []) 27749 .controller('ExampleController', ['$scope', function($scope) { 27750 $scope.name = 'Whirled'; 27751 }]); 27752 </script> 27753 <div ng-controller="ExampleController"> 27754 <label>Enter name: <input type="text" ng-model="name"></label><br> 27755 Hello <span ng-bind="name"></span>! 27756 </div> 27757 </file> 27758 <file name="protractor.js" type="protractor"> 27759 it('should check ng-bind', function() { 27760 var nameInput = element(by.model('name')); 27761 27762 expect(element(by.binding('name')).getText()).toBe('Whirled'); 27763 nameInput.clear(); 27764 nameInput.sendKeys('world'); 27765 expect(element(by.binding('name')).getText()).toBe('world'); 27766 }); 27767 </file> 27768 </example> 27769 */ 27770var ngBindDirective = ['$compile', function($compile) { 27771 return { 27772 restrict: 'AC', 27773 compile: function ngBindCompile(templateElement) { 27774 $compile.$$addBindingClass(templateElement); 27775 return function ngBindLink(scope, element, attr) { 27776 $compile.$$addBindingInfo(element, attr.ngBind); 27777 element = element[0]; 27778 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 27779 element.textContent = stringify(value); 27780 }); 27781 }; 27782 } 27783 }; 27784}]; 27785 27786 27787/** 27788 * @ngdoc directive 27789 * @name ngBindTemplate 27790 * 27791 * @description 27792 * The `ngBindTemplate` directive specifies that the element 27793 * text content should be replaced with the interpolation of the template 27794 * in the `ngBindTemplate` attribute. 27795 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 27796 * expressions. This directive is needed since some HTML elements 27797 * (such as TITLE and OPTION) cannot contain SPAN elements. 27798 * 27799 * @element ANY 27800 * @param {string} ngBindTemplate template of form 27801 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 27802 * 27803 * @example 27804 * Try it here: enter text in text box and watch the greeting change. 27805 <example module="bindExample" name="ng-bind-template"> 27806 <file name="index.html"> 27807 <script> 27808 angular.module('bindExample', []) 27809 .controller('ExampleController', ['$scope', function($scope) { 27810 $scope.salutation = 'Hello'; 27811 $scope.name = 'World'; 27812 }]); 27813 </script> 27814 <div ng-controller="ExampleController"> 27815 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 27816 <label>Name: <input type="text" ng-model="name"></label><br> 27817 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 27818 </div> 27819 </file> 27820 <file name="protractor.js" type="protractor"> 27821 it('should check ng-bind', function() { 27822 var salutationElem = element(by.binding('salutation')); 27823 var salutationInput = element(by.model('salutation')); 27824 var nameInput = element(by.model('name')); 27825 27826 expect(salutationElem.getText()).toBe('Hello World!'); 27827 27828 salutationInput.clear(); 27829 salutationInput.sendKeys('Greetings'); 27830 nameInput.clear(); 27831 nameInput.sendKeys('user'); 27832 27833 expect(salutationElem.getText()).toBe('Greetings user!'); 27834 }); 27835 </file> 27836 </example> 27837 */ 27838var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 27839 return { 27840 compile: function ngBindTemplateCompile(templateElement) { 27841 $compile.$$addBindingClass(templateElement); 27842 return function ngBindTemplateLink(scope, element, attr) { 27843 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 27844 $compile.$$addBindingInfo(element, interpolateFn.expressions); 27845 element = element[0]; 27846 attr.$observe('ngBindTemplate', function(value) { 27847 element.textContent = isUndefined(value) ? '' : value; 27848 }); 27849 }; 27850 } 27851 }; 27852}]; 27853 27854 27855/** 27856 * @ngdoc directive 27857 * @name ngBindHtml 27858 * 27859 * @description 27860 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 27861 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 27862 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 27863 * ngSanitize} in your module's dependencies (not in core AngularJS). In order to use {@link ngSanitize} 27864 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 27865 * 27866 * You may also bypass sanitization for values you know are safe. To do so, bind to 27867 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 27868 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 27869 * 27870 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 27871 * will have an exception (instead of an exploit.) 27872 * 27873 * @element ANY 27874 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 27875 * 27876 * @example 27877 27878 <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html"> 27879 <file name="index.html"> 27880 <div ng-controller="ExampleController"> 27881 <p ng-bind-html="myHTML"></p> 27882 </div> 27883 </file> 27884 27885 <file name="script.js"> 27886 angular.module('bindHtmlExample', ['ngSanitize']) 27887 .controller('ExampleController', ['$scope', function($scope) { 27888 $scope.myHTML = 27889 'I am an <code>HTML</code>string with ' + 27890 '<a href="#">links!</a> and other <em>stuff</em>'; 27891 }]); 27892 </file> 27893 27894 <file name="protractor.js" type="protractor"> 27895 it('should check ng-bind-html', function() { 27896 expect(element(by.binding('myHTML')).getText()).toBe( 27897 'I am an HTMLstring with links! and other stuff'); 27898 }); 27899 </file> 27900 </example> 27901 */ 27902var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 27903 return { 27904 restrict: 'A', 27905 compile: function ngBindHtmlCompile(tElement, tAttrs) { 27906 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 27907 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) { 27908 // Unwrap the value to compare the actual inner safe value, not the wrapper object. 27909 return $sce.valueOf(val); 27910 }); 27911 $compile.$$addBindingClass(tElement); 27912 27913 return function ngBindHtmlLink(scope, element, attr) { 27914 $compile.$$addBindingInfo(element, attr.ngBindHtml); 27915 27916 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 27917 // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter. 27918 var value = ngBindHtmlGetter(scope); 27919 element.html($sce.getTrustedHtml(value) || ''); 27920 }); 27921 }; 27922 } 27923 }; 27924}]; 27925 27926/** 27927 * @ngdoc directive 27928 * @name ngChange 27929 * @restrict A 27930 * 27931 * @description 27932 * Evaluate the given expression when the user changes the input. 27933 * The expression is evaluated immediately, unlike the JavaScript onchange event 27934 * which only triggers at the end of a change (usually, when the user leaves the 27935 * form element or presses the return key). 27936 * 27937 * The `ngChange` expression is only evaluated when a change in the input value causes 27938 * a new value to be committed to the model. 27939 * 27940 * It will not be evaluated: 27941 * * if the value returned from the `$parsers` transformation pipeline has not changed 27942 * * if the input has continued to be invalid since the model will stay `null` 27943 * * if the model is changed programmatically and not by a change to the input value 27944 * 27945 * 27946 * Note, this directive requires `ngModel` to be present. 27947 * 27948 * @element ANY 27949 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 27950 * in input value. 27951 * 27952 * @example 27953 * <example name="ngChange-directive" module="changeExample"> 27954 * <file name="index.html"> 27955 * <script> 27956 * angular.module('changeExample', []) 27957 * .controller('ExampleController', ['$scope', function($scope) { 27958 * $scope.counter = 0; 27959 * $scope.change = function() { 27960 * $scope.counter++; 27961 * }; 27962 * }]); 27963 * </script> 27964 * <div ng-controller="ExampleController"> 27965 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 27966 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 27967 * <label for="ng-change-example2">Confirmed</label><br /> 27968 * <tt>debug = {{confirmed}}</tt><br/> 27969 * <tt>counter = {{counter}}</tt><br/> 27970 * </div> 27971 * </file> 27972 * <file name="protractor.js" type="protractor"> 27973 * var counter = element(by.binding('counter')); 27974 * var debug = element(by.binding('confirmed')); 27975 * 27976 * it('should evaluate the expression if changing from view', function() { 27977 * expect(counter.getText()).toContain('0'); 27978 * 27979 * element(by.id('ng-change-example1')).click(); 27980 * 27981 * expect(counter.getText()).toContain('1'); 27982 * expect(debug.getText()).toContain('true'); 27983 * }); 27984 * 27985 * it('should not evaluate the expression if changing from model', function() { 27986 * element(by.id('ng-change-example2')).click(); 27987 27988 * expect(counter.getText()).toContain('0'); 27989 * expect(debug.getText()).toContain('true'); 27990 * }); 27991 * </file> 27992 * </example> 27993 */ 27994var ngChangeDirective = valueFn({ 27995 restrict: 'A', 27996 require: 'ngModel', 27997 link: function(scope, element, attr, ctrl) { 27998 ctrl.$viewChangeListeners.push(function() { 27999 scope.$eval(attr.ngChange); 28000 }); 28001 } 28002}); 28003 28004/* exported 28005 ngClassDirective, 28006 ngClassEvenDirective, 28007 ngClassOddDirective 28008*/ 28009 28010function classDirective(name, selector) { 28011 name = 'ngClass' + name; 28012 var indexWatchExpression; 28013 28014 return ['$parse', function($parse) { 28015 return { 28016 restrict: 'AC', 28017 link: function(scope, element, attr) { 28018 var classCounts = element.data('$classCounts'); 28019 var oldModulo = true; 28020 var oldClassString; 28021 28022 if (!classCounts) { 28023 // Use createMap() to prevent class assumptions involving property 28024 // names in Object.prototype 28025 classCounts = createMap(); 28026 element.data('$classCounts', classCounts); 28027 } 28028 28029 if (name !== 'ngClass') { 28030 if (!indexWatchExpression) { 28031 indexWatchExpression = $parse('$index', function moduloTwo($index) { 28032 // eslint-disable-next-line no-bitwise 28033 return $index & 1; 28034 }); 28035 } 28036 28037 scope.$watch(indexWatchExpression, ngClassIndexWatchAction); 28038 } 28039 28040 scope.$watch($parse(attr[name], toClassString), ngClassWatchAction); 28041 28042 function addClasses(classString) { 28043 classString = digestClassCounts(split(classString), 1); 28044 attr.$addClass(classString); 28045 } 28046 28047 function removeClasses(classString) { 28048 classString = digestClassCounts(split(classString), -1); 28049 attr.$removeClass(classString); 28050 } 28051 28052 function updateClasses(oldClassString, newClassString) { 28053 var oldClassArray = split(oldClassString); 28054 var newClassArray = split(newClassString); 28055 28056 var toRemoveArray = arrayDifference(oldClassArray, newClassArray); 28057 var toAddArray = arrayDifference(newClassArray, oldClassArray); 28058 28059 var toRemoveString = digestClassCounts(toRemoveArray, -1); 28060 var toAddString = digestClassCounts(toAddArray, 1); 28061 28062 attr.$addClass(toAddString); 28063 attr.$removeClass(toRemoveString); 28064 } 28065 28066 function digestClassCounts(classArray, count) { 28067 var classesToUpdate = []; 28068 28069 forEach(classArray, function(className) { 28070 if (count > 0 || classCounts[className]) { 28071 classCounts[className] = (classCounts[className] || 0) + count; 28072 if (classCounts[className] === +(count > 0)) { 28073 classesToUpdate.push(className); 28074 } 28075 } 28076 }); 28077 28078 return classesToUpdate.join(' '); 28079 } 28080 28081 function ngClassIndexWatchAction(newModulo) { 28082 // This watch-action should run before the `ngClassWatchAction()`, thus it 28083 // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the 28084 // `ngClassWatchAction()` will update the classes. 28085 if (newModulo === selector) { 28086 addClasses(oldClassString); 28087 } else { 28088 removeClasses(oldClassString); 28089 } 28090 28091 oldModulo = newModulo; 28092 } 28093 28094 function ngClassWatchAction(newClassString) { 28095 if (oldModulo === selector) { 28096 updateClasses(oldClassString, newClassString); 28097 } 28098 28099 oldClassString = newClassString; 28100 } 28101 } 28102 }; 28103 }]; 28104 28105 // Helpers 28106 function arrayDifference(tokens1, tokens2) { 28107 if (!tokens1 || !tokens1.length) return []; 28108 if (!tokens2 || !tokens2.length) return tokens1; 28109 28110 var values = []; 28111 28112 outer: 28113 for (var i = 0; i < tokens1.length; i++) { 28114 var token = tokens1[i]; 28115 for (var j = 0; j < tokens2.length; j++) { 28116 if (token === tokens2[j]) continue outer; 28117 } 28118 values.push(token); 28119 } 28120 28121 return values; 28122 } 28123 28124 function split(classString) { 28125 return classString && classString.split(' '); 28126 } 28127 28128 function toClassString(classValue) { 28129 if (!classValue) return classValue; 28130 28131 var classString = classValue; 28132 28133 if (isArray(classValue)) { 28134 classString = classValue.map(toClassString).join(' '); 28135 } else if (isObject(classValue)) { 28136 classString = Object.keys(classValue). 28137 filter(function(key) { return classValue[key]; }). 28138 join(' '); 28139 } else if (!isString(classValue)) { 28140 classString = classValue + ''; 28141 } 28142 28143 return classString; 28144 } 28145} 28146 28147/** 28148 * @ngdoc directive 28149 * @name ngClass 28150 * @restrict AC 28151 * @element ANY 28152 * 28153 * @description 28154 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 28155 * an expression that represents all classes to be added. 28156 * 28157 * The directive operates in three different ways, depending on which of three types the expression 28158 * evaluates to: 28159 * 28160 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 28161 * names. 28162 * 28163 * 2. If the expression evaluates to an object, then for each key-value pair of the 28164 * object with a truthy value the corresponding key is used as a class name. 28165 * 28166 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 28167 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 28168 * to give you more control over what CSS classes appear. See the code below for an example of this. 28169 * 28170 * 28171 * The directive won't add duplicate classes if a particular class was already set. 28172 * 28173 * When the expression changes, the previously added classes are removed and only then are the 28174 * new classes added. 28175 * 28176 * @knownIssue 28177 * You should not use {@link guide/interpolation interpolation} in the value of the `class` 28178 * attribute, when using the `ngClass` directive on the same element. 28179 * See {@link guide/interpolation#known-issues here} for more info. 28180 * 28181 * @animations 28182 * | Animation | Occurs | 28183 * |----------------------------------|-------------------------------------| 28184 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28185 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28186 * | {@link ng.$animate#setClass setClass} | just before classes are added and classes are removed from the element at the same time | 28187 * 28188 * ### ngClass and pre-existing CSS3 Transitions/Animations 28189 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 28190 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 28191 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 28192 to view the step by step details of {@link $animate#addClass $animate.addClass} and 28193 {@link $animate#removeClass $animate.removeClass}. 28194 * 28195 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 28196 * of the evaluation can be a string representing space delimited class 28197 * names, an array, or a map of class names to boolean values. In the case of a map, the 28198 * names of the properties whose values are truthy will be added as css classes to the 28199 * element. 28200 * 28201 * @example 28202 * ### Basic 28203 <example name="ng-class"> 28204 <file name="index.html"> 28205 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 28206 <label> 28207 <input type="checkbox" ng-model="deleted"> 28208 deleted (apply "strike" class) 28209 </label><br> 28210 <label> 28211 <input type="checkbox" ng-model="important"> 28212 important (apply "bold" class) 28213 </label><br> 28214 <label> 28215 <input type="checkbox" ng-model="error"> 28216 error (apply "has-error" class) 28217 </label> 28218 <hr> 28219 <p ng-class="style">Using String Syntax</p> 28220 <input type="text" ng-model="style" 28221 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 28222 <hr> 28223 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 28224 <input ng-model="style1" 28225 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 28226 <input ng-model="style2" 28227 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 28228 <input ng-model="style3" 28229 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 28230 <hr> 28231 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 28232 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 28233 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 28234 </file> 28235 <file name="style.css"> 28236 .strike { 28237 text-decoration: line-through; 28238 } 28239 .bold { 28240 font-weight: bold; 28241 } 28242 .red { 28243 color: red; 28244 } 28245 .has-error { 28246 color: red; 28247 background-color: yellow; 28248 } 28249 .orange { 28250 color: orange; 28251 } 28252 </file> 28253 <file name="protractor.js" type="protractor"> 28254 var ps = element.all(by.css('p')); 28255 28256 it('should let you toggle the class', function() { 28257 28258 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 28259 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 28260 28261 element(by.model('important')).click(); 28262 expect(ps.first().getAttribute('class')).toMatch(/bold/); 28263 28264 element(by.model('error')).click(); 28265 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 28266 }); 28267 28268 it('should let you toggle string example', function() { 28269 expect(ps.get(1).getAttribute('class')).toBe(''); 28270 element(by.model('style')).clear(); 28271 element(by.model('style')).sendKeys('red'); 28272 expect(ps.get(1).getAttribute('class')).toBe('red'); 28273 }); 28274 28275 it('array example should have 3 classes', function() { 28276 expect(ps.get(2).getAttribute('class')).toBe(''); 28277 element(by.model('style1')).sendKeys('bold'); 28278 element(by.model('style2')).sendKeys('strike'); 28279 element(by.model('style3')).sendKeys('red'); 28280 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 28281 }); 28282 28283 it('array with map example should have 2 classes', function() { 28284 expect(ps.last().getAttribute('class')).toBe(''); 28285 element(by.model('style4')).sendKeys('bold'); 28286 element(by.model('warning')).click(); 28287 expect(ps.last().getAttribute('class')).toBe('bold orange'); 28288 }); 28289 </file> 28290 </example> 28291 28292 @example 28293 ### Animations 28294 28295 The example below demonstrates how to perform animations using ngClass. 28296 28297 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class"> 28298 <file name="index.html"> 28299 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 28300 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 28301 <br> 28302 <span class="base-class" ng-class="myVar">Sample Text</span> 28303 </file> 28304 <file name="style.css"> 28305 .base-class { 28306 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28307 } 28308 28309 .base-class.my-class { 28310 color: red; 28311 font-size:3em; 28312 } 28313 </file> 28314 <file name="protractor.js" type="protractor"> 28315 it('should check ng-class', function() { 28316 expect(element(by.css('.base-class')).getAttribute('class')).not. 28317 toMatch(/my-class/); 28318 28319 element(by.id('setbtn')).click(); 28320 28321 expect(element(by.css('.base-class')).getAttribute('class')). 28322 toMatch(/my-class/); 28323 28324 element(by.id('clearbtn')).click(); 28325 28326 expect(element(by.css('.base-class')).getAttribute('class')).not. 28327 toMatch(/my-class/); 28328 }); 28329 </file> 28330 </example> 28331 */ 28332var ngClassDirective = classDirective('', true); 28333 28334/** 28335 * @ngdoc directive 28336 * @name ngClassOdd 28337 * @restrict AC 28338 * 28339 * @description 28340 * The `ngClassOdd` and `ngClassEven` directives work exactly as 28341 * {@link ng.directive:ngClass ngClass}, except they work in 28342 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 28343 * 28344 * This directive can be applied only within the scope of an 28345 * {@link ng.directive:ngRepeat ngRepeat}. 28346 * 28347 * @animations 28348 * | Animation | Occurs | 28349 * |----------------------------------|-------------------------------------| 28350 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28351 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28352 * 28353 * @element ANY 28354 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 28355 * of the evaluation can be a string representing space delimited class names or an array. 28356 * 28357 * @example 28358 <example name="ng-class-odd"> 28359 <file name="index.html"> 28360 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 28361 <li ng-repeat="name in names"> 28362 <span ng-class-odd="'odd'" ng-class-even="'even'"> 28363 {{name}} 28364 </span> 28365 </li> 28366 </ol> 28367 </file> 28368 <file name="style.css"> 28369 .odd { 28370 color: red; 28371 } 28372 .even { 28373 color: blue; 28374 } 28375 </file> 28376 <file name="protractor.js" type="protractor"> 28377 it('should check ng-class-odd and ng-class-even', function() { 28378 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 28379 toMatch(/odd/); 28380 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 28381 toMatch(/even/); 28382 }); 28383 </file> 28384 </example> 28385 * 28386 * <hr /> 28387 * @example 28388 * An example on how to implement animations using `ngClassOdd`: 28389 * 28390 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-odd-animate"> 28391 <file name="index.html"> 28392 <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']"> 28393 <button ng-click="items.unshift('Item ' + items.length)">Add item</button> 28394 <hr /> 28395 <table> 28396 <tr ng-repeat="item in items" ng-class-odd="'odd'"> 28397 <td>{{ item }}</td> 28398 </tr> 28399 </table> 28400 </div> 28401 </file> 28402 <file name="style.css"> 28403 .odd { 28404 background: rgba(255, 255, 0, 0.25); 28405 } 28406 28407 .odd-add, .odd-remove { 28408 transition: 1.5s; 28409 } 28410 </file> 28411 <file name="protractor.js" type="protractor"> 28412 it('should add new entries to the beginning of the list', function() { 28413 var button = element(by.buttonText('Add item')); 28414 var rows = element.all(by.repeater('item in items')); 28415 28416 expect(rows.count()).toBe(4); 28417 expect(rows.get(0).getText()).toBe('Item 3'); 28418 expect(rows.get(1).getText()).toBe('Item 2'); 28419 28420 button.click(); 28421 28422 expect(rows.count()).toBe(5); 28423 expect(rows.get(0).getText()).toBe('Item 4'); 28424 expect(rows.get(1).getText()).toBe('Item 3'); 28425 }); 28426 28427 it('should add odd class to odd entries', function() { 28428 var button = element(by.buttonText('Add item')); 28429 var rows = element.all(by.repeater('item in items')); 28430 28431 expect(rows.get(0).getAttribute('class')).toMatch(/odd/); 28432 expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/); 28433 28434 button.click(); 28435 28436 expect(rows.get(0).getAttribute('class')).toMatch(/odd/); 28437 expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/); 28438 }); 28439 </file> 28440 </example> 28441 */ 28442var ngClassOddDirective = classDirective('Odd', 0); 28443 28444/** 28445 * @ngdoc directive 28446 * @name ngClassEven 28447 * @restrict AC 28448 * 28449 * @description 28450 * The `ngClassOdd` and `ngClassEven` directives work exactly as 28451 * {@link ng.directive:ngClass ngClass}, except they work in 28452 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 28453 * 28454 * This directive can be applied only within the scope of an 28455 * {@link ng.directive:ngRepeat ngRepeat}. 28456 * 28457 * @animations 28458 * | Animation | Occurs | 28459 * |----------------------------------|-------------------------------------| 28460 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28461 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28462 * 28463 * @element ANY 28464 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 28465 * result of the evaluation can be a string representing space delimited class names or an array. 28466 * 28467 * @example 28468 <example name="ng-class-even"> 28469 <file name="index.html"> 28470 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 28471 <li ng-repeat="name in names"> 28472 <span ng-class-odd="'odd'" ng-class-even="'even'"> 28473 {{name}} 28474 </span> 28475 </li> 28476 </ol> 28477 </file> 28478 <file name="style.css"> 28479 .odd { 28480 color: red; 28481 } 28482 .even { 28483 color: blue; 28484 } 28485 </file> 28486 <file name="protractor.js" type="protractor"> 28487 it('should check ng-class-odd and ng-class-even', function() { 28488 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 28489 toMatch(/odd/); 28490 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 28491 toMatch(/even/); 28492 }); 28493 </file> 28494 </example> 28495 * 28496 * <hr /> 28497 * @example 28498 * An example on how to implement animations using `ngClassEven`: 28499 * 28500 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-even-animate"> 28501 <file name="index.html"> 28502 <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']"> 28503 <button ng-click="items.unshift('Item ' + items.length)">Add item</button> 28504 <hr /> 28505 <table> 28506 <tr ng-repeat="item in items" ng-class-even="'even'"> 28507 <td>{{ item }}</td> 28508 </tr> 28509 </table> 28510 </div> 28511 </file> 28512 <file name="style.css"> 28513 .even { 28514 background: rgba(255, 255, 0, 0.25); 28515 } 28516 28517 .even-add, .even-remove { 28518 transition: 1.5s; 28519 } 28520 </file> 28521 <file name="protractor.js" type="protractor"> 28522 it('should add new entries to the beginning of the list', function() { 28523 var button = element(by.buttonText('Add item')); 28524 var rows = element.all(by.repeater('item in items')); 28525 28526 expect(rows.count()).toBe(4); 28527 expect(rows.get(0).getText()).toBe('Item 3'); 28528 expect(rows.get(1).getText()).toBe('Item 2'); 28529 28530 button.click(); 28531 28532 expect(rows.count()).toBe(5); 28533 expect(rows.get(0).getText()).toBe('Item 4'); 28534 expect(rows.get(1).getText()).toBe('Item 3'); 28535 }); 28536 28537 it('should add even class to even entries', function() { 28538 var button = element(by.buttonText('Add item')); 28539 var rows = element.all(by.repeater('item in items')); 28540 28541 expect(rows.get(0).getAttribute('class')).not.toMatch(/even/); 28542 expect(rows.get(1).getAttribute('class')).toMatch(/even/); 28543 28544 button.click(); 28545 28546 expect(rows.get(0).getAttribute('class')).not.toMatch(/even/); 28547 expect(rows.get(1).getAttribute('class')).toMatch(/even/); 28548 }); 28549 </file> 28550 </example> 28551 */ 28552var ngClassEvenDirective = classDirective('Even', 1); 28553 28554/** 28555 * @ngdoc directive 28556 * @name ngCloak 28557 * @restrict AC 28558 * 28559 * @description 28560 * The `ngCloak` directive is used to prevent the AngularJS html template from being briefly 28561 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 28562 * directive to avoid the undesirable flicker effect caused by the html template display. 28563 * 28564 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 28565 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 28566 * of the browser view. 28567 * 28568 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 28569 * `angular.min.js`. 28570 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 28571 * 28572 * ```css 28573 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 28574 * display: none !important; 28575 * } 28576 * ``` 28577 * 28578 * When this css rule is loaded by the browser, all html elements (including their children) that 28579 * are tagged with the `ngCloak` directive are hidden. When AngularJS encounters this directive 28580 * during the compilation of the template it deletes the `ngCloak` element attribute, making 28581 * the compiled element visible. 28582 * 28583 * For the best result, the `angular.js` script must be loaded in the head section of the html 28584 * document; alternatively, the css rule above must be included in the external stylesheet of the 28585 * application. 28586 * 28587 * @element ANY 28588 * 28589 * @example 28590 <example name="ng-cloak"> 28591 <file name="index.html"> 28592 <div id="template1" ng-cloak>{{ 'hello' }}</div> 28593 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 28594 </file> 28595 <file name="protractor.js" type="protractor"> 28596 it('should remove the template directive and css class', function() { 28597 expect($('#template1').getAttribute('ng-cloak')). 28598 toBeNull(); 28599 expect($('#template2').getAttribute('ng-cloak')). 28600 toBeNull(); 28601 }); 28602 </file> 28603 </example> 28604 * 28605 */ 28606var ngCloakDirective = ngDirective({ 28607 compile: function(element, attr) { 28608 attr.$set('ngCloak', undefined); 28609 element.removeClass('ng-cloak'); 28610 } 28611}); 28612 28613/** 28614 * @ngdoc directive 28615 * @name ngController 28616 * 28617 * @description 28618 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 28619 * supports the principles behind the Model-View-Controller design pattern. 28620 * 28621 * MVC components in angular: 28622 * 28623 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties 28624 * are accessed through bindings. 28625 * * View — The template (HTML with data bindings) that is rendered into the View. 28626 * * Controller — The `ngController` directive specifies a Controller class; the class contains business 28627 * logic behind the application to decorate the scope with functions and values 28628 * 28629 * Note that you can also attach controllers to the DOM by declaring it in a route definition 28630 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 28631 * again using `ng-controller` in the template itself. This will cause the controller to be attached 28632 * and executed twice. 28633 * 28634 * @element ANY 28635 * @scope 28636 * @priority 500 28637 * @param {expression} ngController Name of a constructor function registered with the current 28638 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 28639 * that on the current scope evaluates to a constructor function. 28640 * 28641 * The controller instance can be published into a scope property by specifying 28642 * `ng-controller="as propertyName"`. 28643 * 28644 * @example 28645 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 28646 * greeting are methods declared on the controller (see source tab). These methods can 28647 * easily be called from the AngularJS markup. Any changes to the data are automatically reflected 28648 * in the View without the need for a manual update. 28649 * 28650 * Two different declaration styles are included below: 28651 * 28652 * * one binds methods and properties directly onto the controller using `this`: 28653 * `ng-controller="SettingsController1 as settings"` 28654 * * one injects `$scope` into the controller: 28655 * `ng-controller="SettingsController2"` 28656 * 28657 * The second option is more common in the AngularJS community, and is generally used in boilerplates 28658 * and in this guide. However, there are advantages to binding properties directly to the controller 28659 * and avoiding scope. 28660 * 28661 * * Using `controller as` makes it obvious which controller you are accessing in the template when 28662 * multiple controllers apply to an element. 28663 * * If you are writing your controllers as classes you have easier access to the properties and 28664 * methods, which will appear on the scope, from inside the controller code. 28665 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 28666 * inheritance masking primitives. 28667 * 28668 * This example demonstrates the `controller as` syntax. 28669 * 28670 * <example name="ngControllerAs" module="controllerAsExample"> 28671 * <file name="index.html"> 28672 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 28673 * <label>Name: <input type="text" ng-model="settings.name"/></label> 28674 * <button ng-click="settings.greet()">greet</button><br/> 28675 * Contact: 28676 * <ul> 28677 * <li ng-repeat="contact in settings.contacts"> 28678 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 28679 * <option>phone</option> 28680 * <option>email</option> 28681 * </select> 28682 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 28683 * <button ng-click="settings.clearContact(contact)">clear</button> 28684 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 28685 * </li> 28686 * <li><button ng-click="settings.addContact()">add</button></li> 28687 * </ul> 28688 * </div> 28689 * </file> 28690 * <file name="app.js"> 28691 * angular.module('controllerAsExample', []) 28692 * .controller('SettingsController1', SettingsController1); 28693 * 28694 * function SettingsController1() { 28695 * this.name = 'John Smith'; 28696 * this.contacts = [ 28697 * {type: 'phone', value: '408 555 1212'}, 28698 * {type: 'email', value: '[email protected]'} 28699 * ]; 28700 * } 28701 * 28702 * SettingsController1.prototype.greet = function() { 28703 * alert(this.name); 28704 * }; 28705 * 28706 * SettingsController1.prototype.addContact = function() { 28707 * this.contacts.push({type: 'email', value: '[email protected]'}); 28708 * }; 28709 * 28710 * SettingsController1.prototype.removeContact = function(contactToRemove) { 28711 * var index = this.contacts.indexOf(contactToRemove); 28712 * this.contacts.splice(index, 1); 28713 * }; 28714 * 28715 * SettingsController1.prototype.clearContact = function(contact) { 28716 * contact.type = 'phone'; 28717 * contact.value = ''; 28718 * }; 28719 * </file> 28720 * <file name="protractor.js" type="protractor"> 28721 * it('should check controller as', function() { 28722 * var container = element(by.id('ctrl-as-exmpl')); 28723 * expect(container.element(by.model('settings.name')) 28724 * .getAttribute('value')).toBe('John Smith'); 28725 * 28726 * var firstRepeat = 28727 * container.element(by.repeater('contact in settings.contacts').row(0)); 28728 * var secondRepeat = 28729 * container.element(by.repeater('contact in settings.contacts').row(1)); 28730 * 28731 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28732 * .toBe('408 555 1212'); 28733 * 28734 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 28735 * .toBe('[email protected]'); 28736 * 28737 * firstRepeat.element(by.buttonText('clear')).click(); 28738 * 28739 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28740 * .toBe(''); 28741 * 28742 * container.element(by.buttonText('add')).click(); 28743 * 28744 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 28745 * .element(by.model('contact.value')) 28746 * .getAttribute('value')) 28747 * .toBe('[email protected]'); 28748 * }); 28749 * </file> 28750 * </example> 28751 * 28752 * This example demonstrates the "attach to `$scope`" style of controller. 28753 * 28754 * <example name="ngController" module="controllerExample"> 28755 * <file name="index.html"> 28756 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 28757 * <label>Name: <input type="text" ng-model="name"/></label> 28758 * <button ng-click="greet()">greet</button><br/> 28759 * Contact: 28760 * <ul> 28761 * <li ng-repeat="contact in contacts"> 28762 * <select ng-model="contact.type" id="select_{{$index}}"> 28763 * <option>phone</option> 28764 * <option>email</option> 28765 * </select> 28766 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 28767 * <button ng-click="clearContact(contact)">clear</button> 28768 * <button ng-click="removeContact(contact)">X</button> 28769 * </li> 28770 * <li>[ <button ng-click="addContact()">add</button> ]</li> 28771 * </ul> 28772 * </div> 28773 * </file> 28774 * <file name="app.js"> 28775 * angular.module('controllerExample', []) 28776 * .controller('SettingsController2', ['$scope', SettingsController2]); 28777 * 28778 * function SettingsController2($scope) { 28779 * $scope.name = 'John Smith'; 28780 * $scope.contacts = [ 28781 * {type:'phone', value:'408 555 1212'}, 28782 * {type:'email', value:'[email protected]'} 28783 * ]; 28784 * 28785 * $scope.greet = function() { 28786 * alert($scope.name); 28787 * }; 28788 * 28789 * $scope.addContact = function() { 28790 * $scope.contacts.push({type:'email', value:'[email protected]'}); 28791 * }; 28792 * 28793 * $scope.removeContact = function(contactToRemove) { 28794 * var index = $scope.contacts.indexOf(contactToRemove); 28795 * $scope.contacts.splice(index, 1); 28796 * }; 28797 * 28798 * $scope.clearContact = function(contact) { 28799 * contact.type = 'phone'; 28800 * contact.value = ''; 28801 * }; 28802 * } 28803 * </file> 28804 * <file name="protractor.js" type="protractor"> 28805 * it('should check controller', function() { 28806 * var container = element(by.id('ctrl-exmpl')); 28807 * 28808 * expect(container.element(by.model('name')) 28809 * .getAttribute('value')).toBe('John Smith'); 28810 * 28811 * var firstRepeat = 28812 * container.element(by.repeater('contact in contacts').row(0)); 28813 * var secondRepeat = 28814 * container.element(by.repeater('contact in contacts').row(1)); 28815 * 28816 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28817 * .toBe('408 555 1212'); 28818 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 28819 * .toBe('[email protected]'); 28820 * 28821 * firstRepeat.element(by.buttonText('clear')).click(); 28822 * 28823 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 28824 * .toBe(''); 28825 * 28826 * container.element(by.buttonText('add')).click(); 28827 * 28828 * expect(container.element(by.repeater('contact in contacts').row(2)) 28829 * .element(by.model('contact.value')) 28830 * .getAttribute('value')) 28831 * .toBe('[email protected]'); 28832 * }); 28833 * </file> 28834 *</example> 28835 28836 */ 28837var ngControllerDirective = [function() { 28838 return { 28839 restrict: 'A', 28840 scope: true, 28841 controller: '@', 28842 priority: 500 28843 }; 28844}]; 28845 28846/** 28847 * @ngdoc directive 28848 * @name ngCsp 28849 * 28850 * @restrict A 28851 * @element ANY 28852 * @description 28853 * 28854 * AngularJS has some features that can conflict with certain restrictions that are applied when using 28855 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 28856 * 28857 * If you intend to implement CSP with these rules then you must tell AngularJS not to use these 28858 * features. 28859 * 28860 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 28861 * 28862 * 28863 * The following default rules in CSP affect AngularJS: 28864 * 28865 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute 28866 * code from strings is forbidden. AngularJS makes use of this in the {@link $parse} service to 28867 * provide a 30% increase in the speed of evaluating AngularJS expressions. (This CSP rule can be 28868 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would 28869 * weaken the protections offered by CSP.) 28870 * 28871 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden. 28872 * This prevents apps from injecting custom styles directly into the document. AngularJS makes use of 28873 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these 28874 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css` 28875 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but 28876 * it is generally not recommended as it would weaken the protections offered by CSP.) 28877 * 28878 * If you do not provide `ngCsp` then AngularJS tries to autodetect if CSP is blocking dynamic code 28879 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically 28880 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a 28881 * CSP error to be logged in the console: 28882 * 28883 * ``` 28884 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 28885 * script in the following Content Security Policy directive: "default-src 'self'". Note that 28886 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 28887 * ``` 28888 * 28889 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 28890 * directive on an element of the HTML document that appears before the `<script>` tag that loads 28891 * the `angular.js` file. 28892 * 28893 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 28894 * 28895 * You can specify which of the CSP related AngularJS features should be deactivated by providing 28896 * a value for the `ng-csp` attribute. The options are as follows: 28897 * 28898 * * no-inline-style: this stops AngularJS from injecting CSS styles into the DOM 28899 * 28900 * * no-unsafe-eval: this stops AngularJS from optimizing $parse with unsafe eval of strings 28901 * 28902 * You can use these values in the following combinations: 28903 * 28904 * 28905 * * No declaration means that AngularJS will assume that you can do inline styles, but it will do 28906 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous 28907 * versions of AngularJS. 28908 * 28909 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell AngularJS to deactivate both inline 28910 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous 28911 * versions of AngularJS. 28912 * 28913 * * Specifying only `no-unsafe-eval` tells AngularJS that we must not use eval, but that we can 28914 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 28915 * 28916 * * Specifying only `no-inline-style` tells AngularJS that we must not inject styles, but that we can 28917 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 28918 * 28919 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells AngularJS that we must not inject 28920 * styles nor use eval, which is the same as an empty: ng-csp. 28921 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 28922 * 28923 * @example 28924 * 28925 * This example shows how to apply the `ngCsp` directive to the `html` tag. 28926 ```html 28927 <!doctype html> 28928 <html ng-app ng-csp> 28929 ... 28930 ... 28931 </html> 28932 ``` 28933 28934 <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! --> 28935 <example name="example.csp" module="cspExample" ng-csp="true"> 28936 <file name="index.html"> 28937 <div ng-controller="MainController as ctrl"> 28938 <div> 28939 <button ng-click="ctrl.inc()" id="inc">Increment</button> 28940 <span id="counter"> 28941 {{ctrl.counter}} 28942 </span> 28943 </div> 28944 28945 <div> 28946 <button ng-click="ctrl.evil()" id="evil">Evil</button> 28947 <span id="evilError"> 28948 {{ctrl.evilError}} 28949 </span> 28950 </div> 28951 </div> 28952 </file> 28953 <file name="script.js"> 28954 angular.module('cspExample', []) 28955 .controller('MainController', function MainController() { 28956 this.counter = 0; 28957 this.inc = function() { 28958 this.counter++; 28959 }; 28960 this.evil = function() { 28961 try { 28962 eval('1+2'); // eslint-disable-line no-eval 28963 } catch (e) { 28964 this.evilError = e.message; 28965 } 28966 }; 28967 }); 28968 </file> 28969 <file name="protractor.js" type="protractor"> 28970 var util, webdriver; 28971 28972 var incBtn = element(by.id('inc')); 28973 var counter = element(by.id('counter')); 28974 var evilBtn = element(by.id('evil')); 28975 var evilError = element(by.id('evilError')); 28976 28977 function getAndClearSevereErrors() { 28978 return browser.manage().logs().get('browser').then(function(browserLog) { 28979 return browserLog.filter(function(logEntry) { 28980 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 28981 }); 28982 }); 28983 } 28984 28985 function clearErrors() { 28986 getAndClearSevereErrors(); 28987 } 28988 28989 function expectNoErrors() { 28990 getAndClearSevereErrors().then(function(filteredLog) { 28991 expect(filteredLog.length).toEqual(0); 28992 if (filteredLog.length) { 28993 console.log('browser console errors: ' + util.inspect(filteredLog)); 28994 } 28995 }); 28996 } 28997 28998 function expectError(regex) { 28999 getAndClearSevereErrors().then(function(filteredLog) { 29000 var found = false; 29001 filteredLog.forEach(function(log) { 29002 if (log.message.match(regex)) { 29003 found = true; 29004 } 29005 }); 29006 if (!found) { 29007 throw new Error('expected an error that matches ' + regex); 29008 } 29009 }); 29010 } 29011 29012 beforeEach(function() { 29013 util = require('util'); 29014 webdriver = require('selenium-webdriver'); 29015 }); 29016 29017 // For now, we only test on Chrome, 29018 // as Safari does not load the page with Protractor's injected scripts, 29019 // and Firefox webdriver always disables content security policy (#6358) 29020 if (browser.params.browser !== 'chrome') { 29021 return; 29022 } 29023 29024 it('should not report errors when the page is loaded', function() { 29025 // clear errors so we are not dependent on previous tests 29026 clearErrors(); 29027 // Need to reload the page as the page is already loaded when 29028 // we come here 29029 browser.driver.getCurrentUrl().then(function(url) { 29030 browser.get(url); 29031 }); 29032 expectNoErrors(); 29033 }); 29034 29035 it('should evaluate expressions', function() { 29036 expect(counter.getText()).toEqual('0'); 29037 incBtn.click(); 29038 expect(counter.getText()).toEqual('1'); 29039 expectNoErrors(); 29040 }); 29041 29042 it('should throw and report an error when using "eval"', function() { 29043 evilBtn.click(); 29044 expect(evilError.getText()).toMatch(/Content Security Policy/); 29045 expectError(/Content Security Policy/); 29046 }); 29047 </file> 29048 </example> 29049 */ 29050 29051// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while 29052// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()` 29053// fn that looks for the `ng-csp` attribute anywhere in the current doc. 29054 29055/** 29056 * @ngdoc directive 29057 * @name ngClick 29058 * @restrict A 29059 * @element ANY 29060 * @priority 0 29061 * 29062 * @description 29063 * The ngClick directive allows you to specify custom behavior when 29064 * an element is clicked. 29065 * 29066 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 29067 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 29068 * 29069 * @example 29070 <example name="ng-click"> 29071 <file name="index.html"> 29072 <button ng-click="count = count + 1" ng-init="count=0"> 29073 Increment 29074 </button> 29075 <span> 29076 count: {{count}} 29077 </span> 29078 </file> 29079 <file name="protractor.js" type="protractor"> 29080 it('should check ng-click', function() { 29081 expect(element(by.binding('count')).getText()).toMatch('0'); 29082 element(by.css('button')).click(); 29083 expect(element(by.binding('count')).getText()).toMatch('1'); 29084 }); 29085 </file> 29086 </example> 29087 */ 29088/* 29089 * A collection of directives that allows creation of custom event handlers that are defined as 29090 * AngularJS expressions and are compiled and executed within the current scope. 29091 */ 29092var ngEventDirectives = {}; 29093 29094// For events that might fire synchronously during DOM manipulation 29095// we need to execute their event handlers asynchronously using $evalAsync, 29096// so that they are not executed in an inconsistent state. 29097var forceAsyncEvents = { 29098 'blur': true, 29099 'focus': true 29100}; 29101forEach( 29102 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 29103 function(eventName) { 29104 var directiveName = directiveNormalize('ng-' + eventName); 29105 ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) { 29106 return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]); 29107 }]; 29108 } 29109); 29110 29111function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) { 29112 return { 29113 restrict: 'A', 29114 compile: function($element, attr) { 29115 // NOTE: 29116 // We expose the powerful `$event` object on the scope that provides access to the Window, 29117 // etc. This is OK, because expressions are not sandboxed any more (and the expression 29118 // sandbox was never meant to be a security feature anyway). 29119 var fn = $parse(attr[directiveName]); 29120 return function ngEventHandler(scope, element) { 29121 element.on(eventName, function(event) { 29122 var callback = function() { 29123 fn(scope, {$event: event}); 29124 }; 29125 29126 if (!$rootScope.$$phase) { 29127 scope.$apply(callback); 29128 } else if (forceAsync) { 29129 scope.$evalAsync(callback); 29130 } else { 29131 try { 29132 callback(); 29133 } catch (error) { 29134 $exceptionHandler(error); 29135 } 29136 } 29137 }); 29138 }; 29139 } 29140 }; 29141} 29142 29143/** 29144 * @ngdoc directive 29145 * @name ngDblclick 29146 * @restrict A 29147 * @element ANY 29148 * @priority 0 29149 * 29150 * @description 29151 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 29152 * 29153 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 29154 * a dblclick. (The Event object is available as `$event`) 29155 * 29156 * @example 29157 <example name="ng-dblclick"> 29158 <file name="index.html"> 29159 <button ng-dblclick="count = count + 1" ng-init="count=0"> 29160 Increment (on double click) 29161 </button> 29162 count: {{count}} 29163 </file> 29164 </example> 29165 */ 29166 29167 29168/** 29169 * @ngdoc directive 29170 * @name ngMousedown 29171 * @restrict A 29172 * @element ANY 29173 * @priority 0 29174 * 29175 * @description 29176 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 29177 * 29178 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 29179 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 29180 * 29181 * @example 29182 <example name="ng-mousedown"> 29183 <file name="index.html"> 29184 <button ng-mousedown="count = count + 1" ng-init="count=0"> 29185 Increment (on mouse down) 29186 </button> 29187 count: {{count}} 29188 </file> 29189 </example> 29190 */ 29191 29192 29193/** 29194 * @ngdoc directive 29195 * @name ngMouseup 29196 * @restrict A 29197 * @element ANY 29198 * @priority 0 29199 * 29200 * @description 29201 * Specify custom behavior on mouseup event. 29202 * 29203 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 29204 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 29205 * 29206 * @example 29207 <example name="ng-mouseup"> 29208 <file name="index.html"> 29209 <button ng-mouseup="count = count + 1" ng-init="count=0"> 29210 Increment (on mouse up) 29211 </button> 29212 count: {{count}} 29213 </file> 29214 </example> 29215 */ 29216 29217/** 29218 * @ngdoc directive 29219 * @name ngMouseover 29220 * @restrict A 29221 * @element ANY 29222 * @priority 0 29223 * 29224 * @description 29225 * Specify custom behavior on mouseover event. 29226 * 29227 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 29228 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 29229 * 29230 * @example 29231 <example name="ng-mouseover"> 29232 <file name="index.html"> 29233 <button ng-mouseover="count = count + 1" ng-init="count=0"> 29234 Increment (when mouse is over) 29235 </button> 29236 count: {{count}} 29237 </file> 29238 </example> 29239 */ 29240 29241 29242/** 29243 * @ngdoc directive 29244 * @name ngMouseenter 29245 * @restrict A 29246 * @element ANY 29247 * @priority 0 29248 * 29249 * @description 29250 * Specify custom behavior on mouseenter event. 29251 * 29252 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 29253 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 29254 * 29255 * @example 29256 <example name="ng-mouseenter"> 29257 <file name="index.html"> 29258 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 29259 Increment (when mouse enters) 29260 </button> 29261 count: {{count}} 29262 </file> 29263 </example> 29264 */ 29265 29266 29267/** 29268 * @ngdoc directive 29269 * @name ngMouseleave 29270 * @restrict A 29271 * @element ANY 29272 * @priority 0 29273 * 29274 * @description 29275 * Specify custom behavior on mouseleave event. 29276 * 29277 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 29278 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 29279 * 29280 * @example 29281 <example name="ng-mouseleave"> 29282 <file name="index.html"> 29283 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 29284 Increment (when mouse leaves) 29285 </button> 29286 count: {{count}} 29287 </file> 29288 </example> 29289 */ 29290 29291 29292/** 29293 * @ngdoc directive 29294 * @name ngMousemove 29295 * @restrict A 29296 * @element ANY 29297 * @priority 0 29298 * 29299 * @description 29300 * Specify custom behavior on mousemove event. 29301 * 29302 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 29303 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 29304 * 29305 * @example 29306 <example name="ng-mousemove"> 29307 <file name="index.html"> 29308 <button ng-mousemove="count = count + 1" ng-init="count=0"> 29309 Increment (when mouse moves) 29310 </button> 29311 count: {{count}} 29312 </file> 29313 </example> 29314 */ 29315 29316 29317/** 29318 * @ngdoc directive 29319 * @name ngKeydown 29320 * @restrict A 29321 * @element ANY 29322 * @priority 0 29323 * 29324 * @description 29325 * Specify custom behavior on keydown event. 29326 * 29327 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 29328 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 29329 * 29330 * @example 29331 <example name="ng-keydown"> 29332 <file name="index.html"> 29333 <input ng-keydown="count = count + 1" ng-init="count=0"> 29334 key down count: {{count}} 29335 </file> 29336 </example> 29337 */ 29338 29339 29340/** 29341 * @ngdoc directive 29342 * @name ngKeyup 29343 * @restrict A 29344 * @element ANY 29345 * @priority 0 29346 * 29347 * @description 29348 * Specify custom behavior on keyup event. 29349 * 29350 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 29351 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 29352 * 29353 * @example 29354 <example name="ng-keyup"> 29355 <file name="index.html"> 29356 <p>Typing in the input box below updates the key count</p> 29357 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 29358 29359 <p>Typing in the input box below updates the keycode</p> 29360 <input ng-keyup="event=$event"> 29361 <p>event keyCode: {{ event.keyCode }}</p> 29362 <p>event altKey: {{ event.altKey }}</p> 29363 </file> 29364 </example> 29365 */ 29366 29367 29368/** 29369 * @ngdoc directive 29370 * @name ngKeypress 29371 * @restrict A 29372 * @element ANY 29373 * 29374 * @description 29375 * Specify custom behavior on keypress event. 29376 * 29377 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 29378 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 29379 * and can be interrogated for keyCode, altKey, etc.) 29380 * 29381 * @example 29382 <example name="ng-keypress"> 29383 <file name="index.html"> 29384 <input ng-keypress="count = count + 1" ng-init="count=0"> 29385 key press count: {{count}} 29386 </file> 29387 </example> 29388 */ 29389 29390 29391/** 29392 * @ngdoc directive 29393 * @name ngSubmit 29394 * @restrict A 29395 * @element form 29396 * @priority 0 29397 * 29398 * @description 29399 * Enables binding AngularJS expressions to onsubmit events. 29400 * 29401 * Additionally it prevents the default action (which for form means sending the request to the 29402 * server and reloading the current page), but only if the form does not contain `action`, 29403 * `data-action`, or `x-action` attributes. 29404 * 29405 * <div class="alert alert-warning"> 29406 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 29407 * `ngSubmit` handlers together. See the 29408 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 29409 * for a detailed discussion of when `ngSubmit` may be triggered. 29410 * </div> 29411 * 29412 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 29413 * ({@link guide/expression#-event- Event object is available as `$event`}) 29414 * 29415 * @example 29416 <example module="submitExample" name="ng-submit"> 29417 <file name="index.html"> 29418 <script> 29419 angular.module('submitExample', []) 29420 .controller('ExampleController', ['$scope', function($scope) { 29421 $scope.list = []; 29422 $scope.text = 'hello'; 29423 $scope.submit = function() { 29424 if ($scope.text) { 29425 $scope.list.push(this.text); 29426 $scope.text = ''; 29427 } 29428 }; 29429 }]); 29430 </script> 29431 <form ng-submit="submit()" ng-controller="ExampleController"> 29432 Enter text and hit enter: 29433 <input type="text" ng-model="text" name="text" /> 29434 <input type="submit" id="submit" value="Submit" /> 29435 <pre>list={{list}}</pre> 29436 </form> 29437 </file> 29438 <file name="protractor.js" type="protractor"> 29439 it('should check ng-submit', function() { 29440 expect(element(by.binding('list')).getText()).toBe('list=[]'); 29441 element(by.css('#submit')).click(); 29442 expect(element(by.binding('list')).getText()).toContain('hello'); 29443 expect(element(by.model('text')).getAttribute('value')).toBe(''); 29444 }); 29445 it('should ignore empty strings', function() { 29446 expect(element(by.binding('list')).getText()).toBe('list=[]'); 29447 element(by.css('#submit')).click(); 29448 element(by.css('#submit')).click(); 29449 expect(element(by.binding('list')).getText()).toContain('hello'); 29450 }); 29451 </file> 29452 </example> 29453 */ 29454 29455/** 29456 * @ngdoc directive 29457 * @name ngFocus 29458 * @restrict A 29459 * @element window, input, select, textarea, a 29460 * @priority 0 29461 * 29462 * @description 29463 * Specify custom behavior on focus event. 29464 * 29465 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 29466 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 29467 * during an `$apply` to ensure a consistent state. 29468 * 29469 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 29470 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 29471 * 29472 * @example 29473 * See {@link ng.directive:ngClick ngClick} 29474 */ 29475 29476/** 29477 * @ngdoc directive 29478 * @name ngBlur 29479 * @restrict A 29480 * @element window, input, select, textarea, a 29481 * @priority 0 29482 * 29483 * @description 29484 * Specify custom behavior on blur event. 29485 * 29486 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 29487 * an element has lost focus. 29488 * 29489 * Note: As the `blur` event is executed synchronously also during DOM manipulations 29490 * (e.g. removing a focussed input), 29491 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 29492 * during an `$apply` to ensure a consistent state. 29493 * 29494 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 29495 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 29496 * 29497 * @example 29498 * See {@link ng.directive:ngClick ngClick} 29499 */ 29500 29501/** 29502 * @ngdoc directive 29503 * @name ngCopy 29504 * @restrict A 29505 * @element window, input, select, textarea, a 29506 * @priority 0 29507 * 29508 * @description 29509 * Specify custom behavior on copy event. 29510 * 29511 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 29512 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 29513 * 29514 * @example 29515 <example name="ng-copy"> 29516 <file name="index.html"> 29517 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 29518 copied: {{copied}} 29519 </file> 29520 </example> 29521 */ 29522 29523/** 29524 * @ngdoc directive 29525 * @name ngCut 29526 * @restrict A 29527 * @element window, input, select, textarea, a 29528 * @priority 0 29529 * 29530 * @description 29531 * Specify custom behavior on cut event. 29532 * 29533 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 29534 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 29535 * 29536 * @example 29537 <example name="ng-cut"> 29538 <file name="index.html"> 29539 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 29540 cut: {{cut}} 29541 </file> 29542 </example> 29543 */ 29544 29545/** 29546 * @ngdoc directive 29547 * @name ngPaste 29548 * @restrict A 29549 * @element window, input, select, textarea, a 29550 * @priority 0 29551 * 29552 * @description 29553 * Specify custom behavior on paste event. 29554 * 29555 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 29556 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 29557 * 29558 * @example 29559 <example name="ng-paste"> 29560 <file name="index.html"> 29561 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 29562 pasted: {{paste}} 29563 </file> 29564 </example> 29565 */ 29566 29567/** 29568 * @ngdoc directive 29569 * @name ngIf 29570 * @restrict A 29571 * @multiElement 29572 * 29573 * @description 29574 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 29575 * {expression}. If the expression assigned to `ngIf` evaluates to a false 29576 * value then the element is removed from the DOM, otherwise a clone of the 29577 * element is reinserted into the DOM. 29578 * 29579 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 29580 * element in the DOM rather than changing its visibility via the `display` css property. A common 29581 * case when this difference is significant is when using css selectors that rely on an element's 29582 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. 29583 * 29584 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 29585 * is created when the element is restored. The scope created within `ngIf` inherits from 29586 * its parent scope using 29587 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 29588 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 29589 * a javascript primitive defined in the parent scope. In this case any modifications made to the 29590 * variable within the child scope will override (hide) the value in the parent scope. 29591 * 29592 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 29593 * is if an element's class attribute is directly modified after it's compiled, using something like 29594 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 29595 * the added class will be lost because the original compiled state is used to regenerate the element. 29596 * 29597 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 29598 * and `leave` effects. 29599 * 29600 * @animations 29601 * | Animation | Occurs | 29602 * |----------------------------------|-------------------------------------| 29603 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container | 29604 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM | 29605 * 29606 * @element ANY 29607 * @scope 29608 * @priority 600 29609 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 29610 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 29611 * element is added to the DOM tree. 29612 * 29613 * @example 29614 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-if"> 29615 <file name="index.html"> 29616 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 29617 Show when checked: 29618 <span ng-if="checked" class="animate-if"> 29619 This is removed when the checkbox is unchecked. 29620 </span> 29621 </file> 29622 <file name="animations.css"> 29623 .animate-if { 29624 background:white; 29625 border:1px solid black; 29626 padding:10px; 29627 } 29628 29629 .animate-if.ng-enter, .animate-if.ng-leave { 29630 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 29631 } 29632 29633 .animate-if.ng-enter, 29634 .animate-if.ng-leave.ng-leave-active { 29635 opacity:0; 29636 } 29637 29638 .animate-if.ng-leave, 29639 .animate-if.ng-enter.ng-enter-active { 29640 opacity:1; 29641 } 29642 </file> 29643 </example> 29644 */ 29645var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { 29646 return { 29647 multiElement: true, 29648 transclude: 'element', 29649 priority: 600, 29650 terminal: true, 29651 restrict: 'A', 29652 $$tlb: true, 29653 link: function($scope, $element, $attr, ctrl, $transclude) { 29654 var block, childScope, previousElements; 29655 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 29656 29657 if (value) { 29658 if (!childScope) { 29659 $transclude(function(clone, newScope) { 29660 childScope = newScope; 29661 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); 29662 // Note: We only need the first/last node of the cloned nodes. 29663 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 29664 // by a directive with templateUrl when its template arrives. 29665 block = { 29666 clone: clone 29667 }; 29668 $animate.enter(clone, $element.parent(), $element); 29669 }); 29670 } 29671 } else { 29672 if (previousElements) { 29673 previousElements.remove(); 29674 previousElements = null; 29675 } 29676 if (childScope) { 29677 childScope.$destroy(); 29678 childScope = null; 29679 } 29680 if (block) { 29681 previousElements = getBlockNodes(block.clone); 29682 $animate.leave(previousElements).done(function(response) { 29683 if (response !== false) previousElements = null; 29684 }); 29685 block = null; 29686 } 29687 } 29688 }); 29689 } 29690 }; 29691}]; 29692 29693/** 29694 * @ngdoc directive 29695 * @name ngInclude 29696 * @restrict ECA 29697 * @scope 29698 * @priority -400 29699 * 29700 * @description 29701 * Fetches, compiles and includes an external HTML fragment. 29702 * 29703 * By default, the template URL is restricted to the same domain and protocol as the 29704 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 29705 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 29706 * you may either add them to your {@link ng.$sceDelegateProvider#trustedResourceUrlList trusted 29707 * resource URL list} or {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to 29708 * AngularJS's {@link ng.$sce Strict Contextual Escaping}. 29709 * 29710 * In addition, the browser's 29711 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 29712 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 29713 * policy may further restrict whether the template is successfully loaded. 29714 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 29715 * access on some browsers. 29716 * 29717 * @animations 29718 * | Animation | Occurs | 29719 * |----------------------------------|-------------------------------------| 29720 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include | 29721 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include | 29722 * 29723 * The enter and leave animation occur concurrently. 29724 * 29725 * @param {string} ngInclude|src AngularJS expression evaluating to URL. If the source is a string constant, 29726 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 29727 * @param {string=} onload Expression to evaluate when a new partial is loaded. 29728 * <div class="alert alert-warning"> 29729 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 29730 * a function with the name on the window element, which will usually throw a 29731 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 29732 * different form that {@link guide/directive#normalization matches} `onload`. 29733 * </div> 29734 * 29735 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 29736 * $anchorScroll} to scroll the viewport after the content is loaded. 29737 * 29738 * - If the attribute is not set, disable scrolling. 29739 * - If the attribute is set without value, enable scrolling. 29740 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 29741 * 29742 * @example 29743 <example module="includeExample" deps="angular-animate.js" animations="true" name="ng-include"> 29744 <file name="index.html"> 29745 <div ng-controller="ExampleController"> 29746 <select ng-model="template" ng-options="t.name for t in templates"> 29747 <option value="">(blank)</option> 29748 </select> 29749 url of the template: <code>{{template.url}}</code> 29750 <hr/> 29751 <div class="slide-animate-container"> 29752 <div class="slide-animate" ng-include="template.url"></div> 29753 </div> 29754 </div> 29755 </file> 29756 <file name="script.js"> 29757 angular.module('includeExample', ['ngAnimate']) 29758 .controller('ExampleController', ['$scope', function($scope) { 29759 $scope.templates = 29760 [{ name: 'template1.html', url: 'template1.html'}, 29761 { name: 'template2.html', url: 'template2.html'}]; 29762 $scope.template = $scope.templates[0]; 29763 }]); 29764 </file> 29765 <file name="template1.html"> 29766 Content of template1.html 29767 </file> 29768 <file name="template2.html"> 29769 Content of template2.html 29770 </file> 29771 <file name="animations.css"> 29772 .slide-animate-container { 29773 position:relative; 29774 background:white; 29775 border:1px solid black; 29776 height:40px; 29777 overflow:hidden; 29778 } 29779 29780 .slide-animate { 29781 padding:10px; 29782 } 29783 29784 .slide-animate.ng-enter, .slide-animate.ng-leave { 29785 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 29786 29787 position:absolute; 29788 top:0; 29789 left:0; 29790 right:0; 29791 bottom:0; 29792 display:block; 29793 padding:10px; 29794 } 29795 29796 .slide-animate.ng-enter { 29797 top:-50px; 29798 } 29799 .slide-animate.ng-enter.ng-enter-active { 29800 top:0; 29801 } 29802 29803 .slide-animate.ng-leave { 29804 top:0; 29805 } 29806 .slide-animate.ng-leave.ng-leave-active { 29807 top:50px; 29808 } 29809 </file> 29810 <file name="protractor.js" type="protractor"> 29811 var templateSelect = element(by.model('template')); 29812 var includeElem = element(by.css('[ng-include]')); 29813 29814 it('should load template1.html', function() { 29815 expect(includeElem.getText()).toMatch(/Content of template1.html/); 29816 }); 29817 29818 it('should load template2.html', function() { 29819 if (browser.params.browser === 'firefox') { 29820 // Firefox can't handle using selects 29821 // See https://github.com/angular/protractor/issues/480 29822 return; 29823 } 29824 templateSelect.click(); 29825 templateSelect.all(by.css('option')).get(2).click(); 29826 expect(includeElem.getText()).toMatch(/Content of template2.html/); 29827 }); 29828 29829 it('should change to blank', function() { 29830 if (browser.params.browser === 'firefox') { 29831 // Firefox can't handle using selects 29832 return; 29833 } 29834 templateSelect.click(); 29835 templateSelect.all(by.css('option')).get(0).click(); 29836 expect(includeElem.isPresent()).toBe(false); 29837 }); 29838 </file> 29839 </example> 29840 */ 29841 29842 29843/** 29844 * @ngdoc event 29845 * @name ngInclude#$includeContentRequested 29846 * @eventType emit on the scope ngInclude was declared in 29847 * @description 29848 * Emitted every time the ngInclude content is requested. 29849 * 29850 * @param {Object} angularEvent Synthetic event object. 29851 * @param {String} src URL of content to load. 29852 */ 29853 29854 29855/** 29856 * @ngdoc event 29857 * @name ngInclude#$includeContentLoaded 29858 * @eventType emit on the current ngInclude scope 29859 * @description 29860 * Emitted every time the ngInclude content is reloaded. 29861 * 29862 * @param {Object} angularEvent Synthetic event object. 29863 * @param {String} src URL of content to load. 29864 */ 29865 29866 29867/** 29868 * @ngdoc event 29869 * @name ngInclude#$includeContentError 29870 * @eventType emit on the scope ngInclude was declared in 29871 * @description 29872 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 29873 * 29874 * @param {Object} angularEvent Synthetic event object. 29875 * @param {String} src URL of content to load. 29876 */ 29877var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 29878 function($templateRequest, $anchorScroll, $animate) { 29879 return { 29880 restrict: 'ECA', 29881 priority: 400, 29882 terminal: true, 29883 transclude: 'element', 29884 controller: angular.noop, 29885 compile: function(element, attr) { 29886 var srcExp = attr.ngInclude || attr.src, 29887 onloadExp = attr.onload || '', 29888 autoScrollExp = attr.autoscroll; 29889 29890 return function(scope, $element, $attr, ctrl, $transclude) { 29891 var changeCounter = 0, 29892 currentScope, 29893 previousElement, 29894 currentElement; 29895 29896 var cleanupLastIncludeContent = function() { 29897 if (previousElement) { 29898 previousElement.remove(); 29899 previousElement = null; 29900 } 29901 if (currentScope) { 29902 currentScope.$destroy(); 29903 currentScope = null; 29904 } 29905 if (currentElement) { 29906 $animate.leave(currentElement).done(function(response) { 29907 if (response !== false) previousElement = null; 29908 }); 29909 previousElement = currentElement; 29910 currentElement = null; 29911 } 29912 }; 29913 29914 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 29915 var afterAnimation = function(response) { 29916 if (response !== false && isDefined(autoScrollExp) && 29917 (!autoScrollExp || scope.$eval(autoScrollExp))) { 29918 $anchorScroll(); 29919 } 29920 }; 29921 var thisChangeId = ++changeCounter; 29922 29923 if (src) { 29924 //set the 2nd param to true to ignore the template request error so that the inner 29925 //contents and scope can be cleaned up. 29926 $templateRequest(src, true).then(function(response) { 29927 if (scope.$$destroyed) return; 29928 29929 if (thisChangeId !== changeCounter) return; 29930 var newScope = scope.$new(); 29931 ctrl.template = response; 29932 29933 // Note: This will also link all children of ng-include that were contained in the original 29934 // html. If that content contains controllers, ... they could pollute/change the scope. 29935 // However, using ng-include on an element with additional content does not make sense... 29936 // Note: We can't remove them in the cloneAttchFn of $transclude as that 29937 // function is called before linking the content, which would apply child 29938 // directives to non existing elements. 29939 var clone = $transclude(newScope, function(clone) { 29940 cleanupLastIncludeContent(); 29941 $animate.enter(clone, null, $element).done(afterAnimation); 29942 }); 29943 29944 currentScope = newScope; 29945 currentElement = clone; 29946 29947 currentScope.$emit('$includeContentLoaded', src); 29948 scope.$eval(onloadExp); 29949 }, function() { 29950 if (scope.$$destroyed) return; 29951 29952 if (thisChangeId === changeCounter) { 29953 cleanupLastIncludeContent(); 29954 scope.$emit('$includeContentError', src); 29955 } 29956 }); 29957 scope.$emit('$includeContentRequested', src); 29958 } else { 29959 cleanupLastIncludeContent(); 29960 ctrl.template = null; 29961 } 29962 }); 29963 }; 29964 } 29965 }; 29966}]; 29967 29968// This directive is called during the $transclude call of the first `ngInclude` directive. 29969// It will replace and compile the content of the element with the loaded template. 29970// We need this directive so that the element content is already filled when 29971// the link function of another directive on the same element as ngInclude 29972// is called. 29973var ngIncludeFillContentDirective = ['$compile', 29974 function($compile) { 29975 return { 29976 restrict: 'ECA', 29977 priority: -400, 29978 require: 'ngInclude', 29979 link: function(scope, $element, $attr, ctrl) { 29980 if (toString.call($element[0]).match(/SVG/)) { 29981 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 29982 // support innerHTML, so detect this here and try to generate the contents 29983 // specially. 29984 $element.empty(); 29985 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope, 29986 function namespaceAdaptedClone(clone) { 29987 $element.append(clone); 29988 }, {futureParentElement: $element}); 29989 return; 29990 } 29991 29992 $element.html(ctrl.template); 29993 $compile($element.contents())(scope); 29994 } 29995 }; 29996 }]; 29997 29998/** 29999 * @ngdoc directive 30000 * @name ngInit 30001 * @restrict AC 30002 * @priority 450 30003 * @element ANY 30004 * 30005 * @param {expression} ngInit {@link guide/expression Expression} to eval. 30006 * 30007 * @description 30008 * The `ngInit` directive allows you to evaluate an expression in the 30009 * current scope. 30010 * 30011 * <div class="alert alert-danger"> 30012 * This directive can be abused to add unnecessary amounts of logic into your templates. 30013 * There are only a few appropriate uses of `ngInit`: 30014 * <ul> 30015 * <li>aliasing special properties of {@link ng.directive:ngRepeat `ngRepeat`}, 30016 * as seen in the demo below.</li> 30017 * <li>initializing data during development, or for examples, as seen throughout these docs.</li> 30018 * <li>injecting data via server side scripting.</li> 30019 * </ul> 30020 * 30021 * Besides these few cases, you should use {@link guide/component Components} or 30022 * {@link guide/controller Controllers} rather than `ngInit` to initialize values on a scope. 30023 * </div> 30024 * 30025 * <div class="alert alert-warning"> 30026 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 30027 * sure you have parentheses to ensure correct operator precedence: 30028 * <pre class="prettyprint"> 30029 * `<div ng-init="test1 = ($index | toString)"></div>` 30030 * </pre> 30031 * </div> 30032 * 30033 * @example 30034 <example module="initExample" name="ng-init"> 30035 <file name="index.html"> 30036 <script> 30037 angular.module('initExample', []) 30038 .controller('ExampleController', ['$scope', function($scope) { 30039 $scope.list = [['a', 'b'], ['c', 'd']]; 30040 }]); 30041 </script> 30042 <div ng-controller="ExampleController"> 30043 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 30044 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 30045 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 30046 </div> 30047 </div> 30048 </div> 30049 </file> 30050 <file name="protractor.js" type="protractor"> 30051 it('should alias index positions', function() { 30052 var elements = element.all(by.css('.example-init')); 30053 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 30054 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 30055 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 30056 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 30057 }); 30058 </file> 30059 </example> 30060 */ 30061var ngInitDirective = ngDirective({ 30062 priority: 450, 30063 compile: function() { 30064 return { 30065 pre: function(scope, element, attrs) { 30066 scope.$eval(attrs.ngInit); 30067 } 30068 }; 30069 } 30070}); 30071 30072/** 30073 * @ngdoc directive 30074 * @name ngList 30075 * @restrict A 30076 * @priority 100 30077 * 30078 * @param {string=} ngList optional delimiter that should be used to split the value. 30079 * 30080 * @description 30081 * Text input that converts between a delimited string and an array of strings. The default 30082 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 30083 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 30084 * 30085 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 30086 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 30087 * list item is respected. This implies that the user of the directive is responsible for 30088 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 30089 * tab or newline character. 30090 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 30091 * when joining the list items back together) and whitespace around each list item is stripped 30092 * before it is added to the model. 30093 * 30094 * @example 30095 * ### Validation 30096 * 30097 * <example name="ngList-directive" module="listExample"> 30098 * <file name="app.js"> 30099 * angular.module('listExample', []) 30100 * .controller('ExampleController', ['$scope', function($scope) { 30101 * $scope.names = ['morpheus', 'neo', 'trinity']; 30102 * }]); 30103 * </file> 30104 * <file name="index.html"> 30105 * <form name="myForm" ng-controller="ExampleController"> 30106 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 30107 * <span role="alert"> 30108 * <span class="error" ng-show="myForm.namesInput.$error.required"> 30109 * Required!</span> 30110 * </span> 30111 * <br> 30112 * <tt>names = {{names}}</tt><br/> 30113 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 30114 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 30115 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 30116 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 30117 * </form> 30118 * </file> 30119 * <file name="protractor.js" type="protractor"> 30120 * var listInput = element(by.model('names')); 30121 * var names = element(by.exactBinding('names')); 30122 * var valid = element(by.binding('myForm.namesInput.$valid')); 30123 * var error = element(by.css('span.error')); 30124 * 30125 * it('should initialize to model', function() { 30126 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 30127 * expect(valid.getText()).toContain('true'); 30128 * expect(error.getCssValue('display')).toBe('none'); 30129 * }); 30130 * 30131 * it('should be invalid if empty', function() { 30132 * listInput.clear(); 30133 * listInput.sendKeys(''); 30134 * 30135 * expect(names.getText()).toContain(''); 30136 * expect(valid.getText()).toContain('false'); 30137 * expect(error.getCssValue('display')).not.toBe('none'); 30138 * }); 30139 * </file> 30140 * </example> 30141 * 30142 * @example 30143 * ### Splitting on newline 30144 * 30145 * <example name="ngList-directive-newlines"> 30146 * <file name="index.html"> 30147 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 30148 * <pre>{{ list | json }}</pre> 30149 * </file> 30150 * <file name="protractor.js" type="protractor"> 30151 * it("should split the text by newlines", function() { 30152 * var listInput = element(by.model('list')); 30153 * var output = element(by.binding('list | json')); 30154 * listInput.sendKeys('abc\ndef\nghi'); 30155 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 30156 * }); 30157 * </file> 30158 * </example> 30159 * 30160 */ 30161var ngListDirective = function() { 30162 return { 30163 restrict: 'A', 30164 priority: 100, 30165 require: 'ngModel', 30166 link: function(scope, element, attr, ctrl) { 30167 var ngList = attr.ngList || ', '; 30168 var trimValues = attr.ngTrim !== 'false'; 30169 var separator = trimValues ? trim(ngList) : ngList; 30170 30171 var parse = function(viewValue) { 30172 // If the viewValue is invalid (say required but empty) it will be `undefined` 30173 if (isUndefined(viewValue)) return; 30174 30175 var list = []; 30176 30177 if (viewValue) { 30178 forEach(viewValue.split(separator), function(value) { 30179 if (value) list.push(trimValues ? trim(value) : value); 30180 }); 30181 } 30182 30183 return list; 30184 }; 30185 30186 ctrl.$parsers.push(parse); 30187 ctrl.$formatters.push(function(value) { 30188 if (isArray(value)) { 30189 return value.join(ngList); 30190 } 30191 30192 return undefined; 30193 }); 30194 30195 // Override the standard $isEmpty because an empty array means the input is empty. 30196 ctrl.$isEmpty = function(value) { 30197 return !value || !value.length; 30198 }; 30199 } 30200 }; 30201}; 30202 30203/* global VALID_CLASS: true, 30204 INVALID_CLASS: true, 30205 PRISTINE_CLASS: true, 30206 DIRTY_CLASS: true, 30207 UNTOUCHED_CLASS: true, 30208 TOUCHED_CLASS: true, 30209 PENDING_CLASS: true, 30210 addSetValidityMethod: true, 30211 setupValidity: true, 30212 defaultModelOptions: false 30213*/ 30214 30215 30216var VALID_CLASS = 'ng-valid', 30217 INVALID_CLASS = 'ng-invalid', 30218 PRISTINE_CLASS = 'ng-pristine', 30219 DIRTY_CLASS = 'ng-dirty', 30220 UNTOUCHED_CLASS = 'ng-untouched', 30221 TOUCHED_CLASS = 'ng-touched', 30222 EMPTY_CLASS = 'ng-empty', 30223 NOT_EMPTY_CLASS = 'ng-not-empty'; 30224 30225var ngModelMinErr = minErr('ngModel'); 30226 30227/** 30228 * @ngdoc type 30229 * @name ngModel.NgModelController 30230 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 30231 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 30232 * is set. 30233 * 30234 * @property {*} $modelValue The value in the model that the control is bound to. 30235 * 30236 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 30237 * the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue 30238 `$viewValue`} from the DOM, usually via user input. 30239 See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation. 30240 Note that the `$parsers` are not called when the bound ngModel expression changes programmatically. 30241 30242 The functions are called in array order, each passing 30243 its return value through to the next. The last return value is forwarded to the 30244 {@link ngModel.NgModelController#$validators `$validators`} collection. 30245 30246 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 30247 `$viewValue`}. 30248 30249 Returning `undefined` from a parser means a parse error occurred. In that case, 30250 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 30251 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 30252 is set to `true`. The parse error is stored in `ngModel.$error.parse`. 30253 30254 This simple example shows a parser that would convert text input value to lowercase: 30255 * ```js 30256 * function parse(value) { 30257 * if (value) { 30258 * return value.toLowerCase(); 30259 * } 30260 * } 30261 * ngModelController.$parsers.push(parse); 30262 * ``` 30263 30264 * 30265 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 30266 the bound ngModel expression changes programmatically. The `$formatters` are not called when the 30267 value of the control is changed by user interaction. 30268 30269 Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue 30270 `$modelValue`} for display in the control. 30271 30272 The functions are called in reverse array order, each passing the value through to the 30273 next. The last return value is used as the actual DOM value. 30274 30275 This simple example shows a formatter that would convert the model value to uppercase: 30276 30277 * ```js 30278 * function format(value) { 30279 * if (value) { 30280 * return value.toUpperCase(); 30281 * } 30282 * } 30283 * ngModel.$formatters.push(format); 30284 * ``` 30285 * 30286 * @property {Object.<string, function>} $validators A collection of validators that are applied 30287 * whenever the model value changes. The key value within the object refers to the name of the 30288 * validator while the function refers to the validation operation. The validation operation is 30289 * provided with the model value as an argument and must return a true or false value depending 30290 * on the response of that validation. 30291 * 30292 * ```js 30293 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 30294 * var value = modelValue || viewValue; 30295 * return /[0-9]+/.test(value) && 30296 * /[a-z]+/.test(value) && 30297 * /[A-Z]+/.test(value) && 30298 * /\W+/.test(value); 30299 * }; 30300 * ``` 30301 * 30302 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 30303 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 30304 * is expected to return a promise when it is run during the model validation process. Once the promise 30305 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 30306 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 30307 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 30308 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 30309 * will only run once all synchronous validators have passed. 30310 * 30311 * Please note that if $http is used then it is important that the server returns a success HTTP response code 30312 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 30313 * 30314 * ```js 30315 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 30316 * var value = modelValue || viewValue; 30317 * 30318 * // Lookup user by username 30319 * return $http.get('/api/users/' + value). 30320 * then(function resolved() { 30321 * //username exists, this means validation fails 30322 * return $q.reject('exists'); 30323 * }, function rejected() { 30324 * //username does not exist, therefore this validation passes 30325 * return true; 30326 * }); 30327 * }; 30328 * ``` 30329 * 30330 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever 30331 * a change to {@link ngModel.NgModelController#$viewValue `$viewValue`} has caused a change 30332 * to {@link ngModel.NgModelController#$modelValue `$modelValue`}. 30333 * It is called with no arguments, and its return value is ignored. 30334 * This can be used in place of additional $watches against the model value. 30335 * 30336 * @property {Object} $error An object hash with all failing validator ids as keys. 30337 * @property {Object} $pending An object hash with all pending validator ids as keys. 30338 * 30339 * @property {boolean} $untouched True if control has not lost focus yet. 30340 * @property {boolean} $touched True if control has lost focus. 30341 * @property {boolean} $pristine True if user has not interacted with the control yet. 30342 * @property {boolean} $dirty True if user has already interacted with the control. 30343 * @property {boolean} $valid True if there is no error. 30344 * @property {boolean} $invalid True if at least one error on the control. 30345 * @property {string} $name The name attribute of the control. 30346 * 30347 * @description 30348 * 30349 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 30350 * The controller contains services for data-binding, validation, CSS updates, and value formatting 30351 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 30352 * listening to DOM events. 30353 * Such DOM related logic should be provided by other directives which make use of 30354 * `NgModelController` for data-binding to control elements. 30355 * AngularJS provides this DOM logic for most {@link input `input`} elements. 30356 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 30357 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 30358 * 30359 * @example 30360 * ### Custom Control Example 30361 * This example shows how to use `NgModelController` with a custom control to achieve 30362 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 30363 * collaborate together to achieve the desired result. 30364 * 30365 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 30366 * contents be edited in place by the user. 30367 * 30368 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 30369 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 30370 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 30371 * that content using the `$sce` service. 30372 * 30373 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 30374 <file name="style.css"> 30375 [contenteditable] { 30376 border: 1px solid black; 30377 background-color: white; 30378 min-height: 20px; 30379 } 30380 30381 .ng-invalid { 30382 border: 1px solid red; 30383 } 30384 30385 </file> 30386 <file name="script.js"> 30387 angular.module('customControl', ['ngSanitize']). 30388 directive('contenteditable', ['$sce', function($sce) { 30389 return { 30390 restrict: 'A', // only activate on element attribute 30391 require: '?ngModel', // get a hold of NgModelController 30392 link: function(scope, element, attrs, ngModel) { 30393 if (!ngModel) return; // do nothing if no ng-model 30394 30395 // Specify how UI should be updated 30396 ngModel.$render = function() { 30397 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 30398 }; 30399 30400 // Listen for change events to enable binding 30401 element.on('blur keyup change', function() { 30402 scope.$evalAsync(read); 30403 }); 30404 read(); // initialize 30405 30406 // Write data to the model 30407 function read() { 30408 var html = element.html(); 30409 // When we clear the content editable the browser leaves a <br> behind 30410 // If strip-br attribute is provided then we strip this out 30411 if (attrs.stripBr && html === '<br>') { 30412 html = ''; 30413 } 30414 ngModel.$setViewValue(html); 30415 } 30416 } 30417 }; 30418 }]); 30419 </file> 30420 <file name="index.html"> 30421 <form name="myForm"> 30422 <div contenteditable 30423 name="myWidget" ng-model="userContent" 30424 strip-br="true" 30425 required>Change me!</div> 30426 <span ng-show="myForm.myWidget.$error.required">Required!</span> 30427 <hr> 30428 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 30429 </form> 30430 </file> 30431 <file name="protractor.js" type="protractor"> 30432 it('should data-bind and become invalid', function() { 30433 if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') { 30434 // SafariDriver can't handle contenteditable 30435 // and Firefox driver can't clear contenteditables very well 30436 return; 30437 } 30438 var contentEditable = element(by.css('[contenteditable]')); 30439 var content = 'Change me!'; 30440 30441 expect(contentEditable.getText()).toEqual(content); 30442 30443 contentEditable.clear(); 30444 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 30445 expect(contentEditable.getText()).toEqual(''); 30446 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 30447 }); 30448 </file> 30449 * </example> 30450 * 30451 * 30452 */ 30453NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate']; 30454function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) { 30455 this.$viewValue = Number.NaN; 30456 this.$modelValue = Number.NaN; 30457 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 30458 this.$validators = {}; 30459 this.$asyncValidators = {}; 30460 this.$parsers = []; 30461 this.$formatters = []; 30462 this.$viewChangeListeners = []; 30463 this.$untouched = true; 30464 this.$touched = false; 30465 this.$pristine = true; 30466 this.$dirty = false; 30467 this.$valid = true; 30468 this.$invalid = false; 30469 this.$error = {}; // keep invalid keys here 30470 this.$$success = {}; // keep valid keys here 30471 this.$pending = undefined; // keep pending keys here 30472 this.$name = $interpolate($attr.name || '', false)($scope); 30473 this.$$parentForm = nullFormCtrl; 30474 this.$options = defaultModelOptions; 30475 this.$$updateEvents = ''; 30476 // Attach the correct context to the event handler function for updateOn 30477 this.$$updateEventHandler = this.$$updateEventHandler.bind(this); 30478 30479 this.$$parsedNgModel = $parse($attr.ngModel); 30480 this.$$parsedNgModelAssign = this.$$parsedNgModel.assign; 30481 this.$$ngModelGet = this.$$parsedNgModel; 30482 this.$$ngModelSet = this.$$parsedNgModelAssign; 30483 this.$$pendingDebounce = null; 30484 this.$$parserValid = undefined; 30485 this.$$parserName = 'parse'; 30486 30487 this.$$currentValidationRunId = 0; 30488 30489 this.$$scope = $scope; 30490 this.$$rootScope = $scope.$root; 30491 this.$$attr = $attr; 30492 this.$$element = $element; 30493 this.$$animate = $animate; 30494 this.$$timeout = $timeout; 30495 this.$$parse = $parse; 30496 this.$$q = $q; 30497 this.$$exceptionHandler = $exceptionHandler; 30498 30499 setupValidity(this); 30500 setupModelWatcher(this); 30501} 30502 30503NgModelController.prototype = { 30504 $$initGetterSetters: function() { 30505 if (this.$options.getOption('getterSetter')) { 30506 var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'), 30507 invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)'); 30508 30509 this.$$ngModelGet = function($scope) { 30510 var modelValue = this.$$parsedNgModel($scope); 30511 if (isFunction(modelValue)) { 30512 modelValue = invokeModelGetter($scope); 30513 } 30514 return modelValue; 30515 }; 30516 this.$$ngModelSet = function($scope, newValue) { 30517 if (isFunction(this.$$parsedNgModel($scope))) { 30518 invokeModelSetter($scope, {$$$p: newValue}); 30519 } else { 30520 this.$$parsedNgModelAssign($scope, newValue); 30521 } 30522 }; 30523 } else if (!this.$$parsedNgModel.assign) { 30524 throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}', 30525 this.$$attr.ngModel, startingTag(this.$$element)); 30526 } 30527 }, 30528 30529 30530 /** 30531 * @ngdoc method 30532 * @name ngModel.NgModelController#$render 30533 * 30534 * @description 30535 * Called when the view needs to be updated. It is expected that the user of the ng-model 30536 * directive will implement this method. 30537 * 30538 * The `$render()` method is invoked in the following situations: 30539 * 30540 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 30541 * committed value then `$render()` is called to update the input control. 30542 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 30543 * the `$viewValue` are different from last time. 30544 * 30545 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 30546 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` 30547 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 30548 * invoked if you only change a property on the objects. 30549 */ 30550 $render: noop, 30551 30552 /** 30553 * @ngdoc method 30554 * @name ngModel.NgModelController#$isEmpty 30555 * 30556 * @description 30557 * This is called when we need to determine if the value of an input is empty. 30558 * 30559 * For instance, the required directive does this to work out if the input has data or not. 30560 * 30561 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 30562 * 30563 * You can override this for input directives whose concept of being empty is different from the 30564 * default. The `checkboxInputType` directive does this because in its case a value of `false` 30565 * implies empty. 30566 * 30567 * @param {*} value The value of the input to check for emptiness. 30568 * @returns {boolean} True if `value` is "empty". 30569 */ 30570 $isEmpty: function(value) { 30571 // eslint-disable-next-line no-self-compare 30572 return isUndefined(value) || value === '' || value === null || value !== value; 30573 }, 30574 30575 $$updateEmptyClasses: function(value) { 30576 if (this.$isEmpty(value)) { 30577 this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS); 30578 this.$$animate.addClass(this.$$element, EMPTY_CLASS); 30579 } else { 30580 this.$$animate.removeClass(this.$$element, EMPTY_CLASS); 30581 this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS); 30582 } 30583 }, 30584 30585 /** 30586 * @ngdoc method 30587 * @name ngModel.NgModelController#$setPristine 30588 * 30589 * @description 30590 * Sets the control to its pristine state. 30591 * 30592 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 30593 * state (`ng-pristine` class). A model is considered to be pristine when the control 30594 * has not been changed from when first compiled. 30595 */ 30596 $setPristine: function() { 30597 this.$dirty = false; 30598 this.$pristine = true; 30599 this.$$animate.removeClass(this.$$element, DIRTY_CLASS); 30600 this.$$animate.addClass(this.$$element, PRISTINE_CLASS); 30601 }, 30602 30603 /** 30604 * @ngdoc method 30605 * @name ngModel.NgModelController#$setDirty 30606 * 30607 * @description 30608 * Sets the control to its dirty state. 30609 * 30610 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 30611 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 30612 * from when first compiled. 30613 */ 30614 $setDirty: function() { 30615 this.$dirty = true; 30616 this.$pristine = false; 30617 this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); 30618 this.$$animate.addClass(this.$$element, DIRTY_CLASS); 30619 this.$$parentForm.$setDirty(); 30620 }, 30621 30622 /** 30623 * @ngdoc method 30624 * @name ngModel.NgModelController#$setUntouched 30625 * 30626 * @description 30627 * Sets the control to its untouched state. 30628 * 30629 * This method can be called to remove the `ng-touched` class and set the control to its 30630 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 30631 * by default, however this function can be used to restore that state if the model has 30632 * already been touched by the user. 30633 */ 30634 $setUntouched: function() { 30635 this.$touched = false; 30636 this.$untouched = true; 30637 this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS); 30638 }, 30639 30640 /** 30641 * @ngdoc method 30642 * @name ngModel.NgModelController#$setTouched 30643 * 30644 * @description 30645 * Sets the control to its touched state. 30646 * 30647 * This method can be called to remove the `ng-untouched` class and set the control to its 30648 * touched state (`ng-touched` class). A model is considered to be touched when the user has 30649 * first focused the control element and then shifted focus away from the control (blur event). 30650 */ 30651 $setTouched: function() { 30652 this.$touched = true; 30653 this.$untouched = false; 30654 this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS); 30655 }, 30656 30657 /** 30658 * @ngdoc method 30659 * @name ngModel.NgModelController#$rollbackViewValue 30660 * 30661 * @description 30662 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 30663 * which may be caused by a pending debounced event or because the input is waiting for some 30664 * future event. 30665 * 30666 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 30667 * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of 30668 * sync with the ngModel's `$modelValue`. 30669 * 30670 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 30671 * and reset the input to the last committed view value. 30672 * 30673 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 30674 * programmatically before these debounced/future events have resolved/occurred, because AngularJS's 30675 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 30676 * 30677 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 30678 * input which may have such events pending. This is important in order to make sure that the 30679 * input field will be updated with the new model value and any pending operations are cancelled. 30680 * 30681 * @example 30682 * <example name="ng-model-cancel-update" module="cancel-update-example"> 30683 * <file name="app.js"> 30684 * angular.module('cancel-update-example', []) 30685 * 30686 * .controller('CancelUpdateController', ['$scope', function($scope) { 30687 * $scope.model = {value1: '', value2: ''}; 30688 * 30689 * $scope.setEmpty = function(e, value, rollback) { 30690 * if (e.keyCode === 27) { 30691 * e.preventDefault(); 30692 * if (rollback) { 30693 * $scope.myForm[value].$rollbackViewValue(); 30694 * } 30695 * $scope.model[value] = ''; 30696 * } 30697 * }; 30698 * }]); 30699 * </file> 30700 * <file name="index.html"> 30701 * <div ng-controller="CancelUpdateController"> 30702 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 30703 * empty them. Follow these steps and observe the difference:</p> 30704 * <ol> 30705 * <li>Type something in the input. You will see that the model is not yet updated</li> 30706 * <li>Press the Escape key. 30707 * <ol> 30708 * <li> In the first example, nothing happens, because the model is already '', and no 30709 * update is detected. If you blur the input, the model will be set to the current view. 30710 * </li> 30711 * <li> In the second example, the pending update is cancelled, and the input is set back 30712 * to the last committed view value (''). Blurring the input does nothing. 30713 * </li> 30714 * </ol> 30715 * </li> 30716 * </ol> 30717 * 30718 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 30719 * <div> 30720 * <p id="inputDescription1">Without $rollbackViewValue():</p> 30721 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 30722 * ng-keydown="setEmpty($event, 'value1')"> 30723 * value1: "{{ model.value1 }}" 30724 * </div> 30725 * 30726 * <div> 30727 * <p id="inputDescription2">With $rollbackViewValue():</p> 30728 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 30729 * ng-keydown="setEmpty($event, 'value2', true)"> 30730 * value2: "{{ model.value2 }}" 30731 * </div> 30732 * </form> 30733 * </div> 30734 * </file> 30735 <file name="style.css"> 30736 div { 30737 display: table-cell; 30738 } 30739 div:nth-child(1) { 30740 padding-right: 30px; 30741 } 30742 30743 </file> 30744 * </example> 30745 */ 30746 $rollbackViewValue: function() { 30747 this.$$timeout.cancel(this.$$pendingDebounce); 30748 this.$viewValue = this.$$lastCommittedViewValue; 30749 this.$render(); 30750 }, 30751 30752 /** 30753 * @ngdoc method 30754 * @name ngModel.NgModelController#$validate 30755 * 30756 * @description 30757 * Runs each of the registered validators (first synchronous validators and then 30758 * asynchronous validators). 30759 * If the validity changes to invalid, the model will be set to `undefined`, 30760 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 30761 * If the validity changes to valid, it will set the model to the last available valid 30762 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 30763 */ 30764 $validate: function() { 30765 30766 // ignore $validate before model is initialized 30767 if (isNumberNaN(this.$modelValue)) { 30768 return; 30769 } 30770 30771 var viewValue = this.$$lastCommittedViewValue; 30772 // Note: we use the $$rawModelValue as $modelValue might have been 30773 // set to undefined during a view -> model update that found validation 30774 // errors. We can't parse the view here, since that could change 30775 // the model although neither viewValue nor the model on the scope changed 30776 var modelValue = this.$$rawModelValue; 30777 30778 var prevValid = this.$valid; 30779 var prevModelValue = this.$modelValue; 30780 30781 var allowInvalid = this.$options.getOption('allowInvalid'); 30782 30783 var that = this; 30784 this.$$runValidators(modelValue, viewValue, function(allValid) { 30785 // If there was no change in validity, don't update the model 30786 // This prevents changing an invalid modelValue to undefined 30787 if (!allowInvalid && prevValid !== allValid) { 30788 // Note: Don't check this.$valid here, as we could have 30789 // external validators (e.g. calculated on the server), 30790 // that just call $setValidity and need the model value 30791 // to calculate their validity. 30792 that.$modelValue = allValid ? modelValue : undefined; 30793 30794 if (that.$modelValue !== prevModelValue) { 30795 that.$$writeModelToScope(); 30796 } 30797 } 30798 }); 30799 }, 30800 30801 $$runValidators: function(modelValue, viewValue, doneCallback) { 30802 this.$$currentValidationRunId++; 30803 var localValidationRunId = this.$$currentValidationRunId; 30804 var that = this; 30805 30806 // check parser error 30807 if (!processParseErrors()) { 30808 validationDone(false); 30809 return; 30810 } 30811 if (!processSyncValidators()) { 30812 validationDone(false); 30813 return; 30814 } 30815 processAsyncValidators(); 30816 30817 function processParseErrors() { 30818 var errorKey = that.$$parserName; 30819 30820 if (isUndefined(that.$$parserValid)) { 30821 setValidity(errorKey, null); 30822 } else { 30823 if (!that.$$parserValid) { 30824 forEach(that.$validators, function(v, name) { 30825 setValidity(name, null); 30826 }); 30827 forEach(that.$asyncValidators, function(v, name) { 30828 setValidity(name, null); 30829 }); 30830 } 30831 30832 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 30833 setValidity(errorKey, that.$$parserValid); 30834 return that.$$parserValid; 30835 } 30836 return true; 30837 } 30838 30839 function processSyncValidators() { 30840 var syncValidatorsValid = true; 30841 forEach(that.$validators, function(validator, name) { 30842 var result = Boolean(validator(modelValue, viewValue)); 30843 syncValidatorsValid = syncValidatorsValid && result; 30844 setValidity(name, result); 30845 }); 30846 if (!syncValidatorsValid) { 30847 forEach(that.$asyncValidators, function(v, name) { 30848 setValidity(name, null); 30849 }); 30850 return false; 30851 } 30852 return true; 30853 } 30854 30855 function processAsyncValidators() { 30856 var validatorPromises = []; 30857 var allValid = true; 30858 forEach(that.$asyncValidators, function(validator, name) { 30859 var promise = validator(modelValue, viewValue); 30860 if (!isPromiseLike(promise)) { 30861 throw ngModelMinErr('nopromise', 30862 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise); 30863 } 30864 setValidity(name, undefined); 30865 validatorPromises.push(promise.then(function() { 30866 setValidity(name, true); 30867 }, function() { 30868 allValid = false; 30869 setValidity(name, false); 30870 })); 30871 }); 30872 if (!validatorPromises.length) { 30873 validationDone(true); 30874 } else { 30875 that.$$q.all(validatorPromises).then(function() { 30876 validationDone(allValid); 30877 }, noop); 30878 } 30879 } 30880 30881 function setValidity(name, isValid) { 30882 if (localValidationRunId === that.$$currentValidationRunId) { 30883 that.$setValidity(name, isValid); 30884 } 30885 } 30886 30887 function validationDone(allValid) { 30888 if (localValidationRunId === that.$$currentValidationRunId) { 30889 30890 doneCallback(allValid); 30891 } 30892 } 30893 }, 30894 30895 /** 30896 * @ngdoc method 30897 * @name ngModel.NgModelController#$commitViewValue 30898 * 30899 * @description 30900 * Commit a pending update to the `$modelValue`. 30901 * 30902 * Updates may be pending by a debounced event or because the input is waiting for a some future 30903 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 30904 * usually handles calling this in response to input events. 30905 */ 30906 $commitViewValue: function() { 30907 var viewValue = this.$viewValue; 30908 30909 this.$$timeout.cancel(this.$$pendingDebounce); 30910 30911 // If the view value has not changed then we should just exit, except in the case where there is 30912 // a native validator on the element. In this case the validation state may have changed even though 30913 // the viewValue has stayed empty. 30914 if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) { 30915 return; 30916 } 30917 this.$$updateEmptyClasses(viewValue); 30918 this.$$lastCommittedViewValue = viewValue; 30919 30920 // change to dirty 30921 if (this.$pristine) { 30922 this.$setDirty(); 30923 } 30924 this.$$parseAndValidate(); 30925 }, 30926 30927 $$parseAndValidate: function() { 30928 var viewValue = this.$$lastCommittedViewValue; 30929 var modelValue = viewValue; 30930 var that = this; 30931 30932 this.$$parserValid = isUndefined(modelValue) ? undefined : true; 30933 30934 // Reset any previous parse error 30935 this.$setValidity(this.$$parserName, null); 30936 this.$$parserName = 'parse'; 30937 30938 if (this.$$parserValid) { 30939 for (var i = 0; i < this.$parsers.length; i++) { 30940 modelValue = this.$parsers[i](modelValue); 30941 if (isUndefined(modelValue)) { 30942 this.$$parserValid = false; 30943 break; 30944 } 30945 } 30946 } 30947 if (isNumberNaN(this.$modelValue)) { 30948 // this.$modelValue has not been touched yet... 30949 this.$modelValue = this.$$ngModelGet(this.$$scope); 30950 } 30951 var prevModelValue = this.$modelValue; 30952 var allowInvalid = this.$options.getOption('allowInvalid'); 30953 this.$$rawModelValue = modelValue; 30954 30955 if (allowInvalid) { 30956 this.$modelValue = modelValue; 30957 writeToModelIfNeeded(); 30958 } 30959 30960 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 30961 // This can happen if e.g. $setViewValue is called from inside a parser 30962 this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) { 30963 if (!allowInvalid) { 30964 // Note: Don't check this.$valid here, as we could have 30965 // external validators (e.g. calculated on the server), 30966 // that just call $setValidity and need the model value 30967 // to calculate their validity. 30968 that.$modelValue = allValid ? modelValue : undefined; 30969 writeToModelIfNeeded(); 30970 } 30971 }); 30972 30973 function writeToModelIfNeeded() { 30974 if (that.$modelValue !== prevModelValue) { 30975 that.$$writeModelToScope(); 30976 } 30977 } 30978 }, 30979 30980 $$writeModelToScope: function() { 30981 this.$$ngModelSet(this.$$scope, this.$modelValue); 30982 forEach(this.$viewChangeListeners, function(listener) { 30983 try { 30984 listener(); 30985 } catch (e) { 30986 // eslint-disable-next-line no-invalid-this 30987 this.$$exceptionHandler(e); 30988 } 30989 }, this); 30990 }, 30991 30992 /** 30993 * @ngdoc method 30994 * @name ngModel.NgModelController#$setViewValue 30995 * 30996 * @description 30997 * Update the view value. 30998 * 30999 * This method should be called when a control wants to change the view value; typically, 31000 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 31001 * directive calls it when the value of the input changes and {@link ng.directive:select select} 31002 * calls it when an option is selected. 31003 * 31004 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 31005 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 31006 * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and 31007 * `$asyncValidators` are called and the value is applied to `$modelValue`. 31008 * Finally, the value is set to the **expression** specified in the `ng-model` attribute and 31009 * all the registered change listeners, in the `$viewChangeListeners` list are called. 31010 * 31011 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 31012 * and the `default` trigger is not listed, all those actions will remain pending until one of the 31013 * `updateOn` events is triggered on the DOM element. 31014 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 31015 * directive is used with a custom debounce for this particular event. 31016 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 31017 * is specified, once the timer runs out. 31018 * 31019 * When used with standard inputs, the view value will always be a string (which is in some cases 31020 * parsed into another type, such as a `Date` object for `input[date]`.) 31021 * However, custom controls might also pass objects to this method. In this case, we should make 31022 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 31023 * perform a deep watch of objects, it only looks for a change of identity. If you only change 31024 * the property of the object then ngModel will not realize that the object has changed and 31025 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 31026 * not change properties of the copy once it has been passed to `$setViewValue`. 31027 * Otherwise you may cause the model value on the scope to change incorrectly. 31028 * 31029 * <div class="alert alert-info"> 31030 * In any case, the value passed to the method should always reflect the current value 31031 * of the control. For example, if you are calling `$setViewValue` for an input element, 31032 * you should pass the input DOM value. Otherwise, the control and the scope model become 31033 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 31034 * the control's DOM value in any way. If we want to change the control's DOM value 31035 * programmatically, we should update the `ngModel` scope expression. Its new value will be 31036 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 31037 * to update the DOM, and finally call `$validate` on it. 31038 * </div> 31039 * 31040 * @param {*} value value from the view. 31041 * @param {string} trigger Event that triggered the update. 31042 */ 31043 $setViewValue: function(value, trigger) { 31044 this.$viewValue = value; 31045 if (this.$options.getOption('updateOnDefault')) { 31046 this.$$debounceViewValueCommit(trigger); 31047 } 31048 }, 31049 31050 $$debounceViewValueCommit: function(trigger) { 31051 var debounceDelay = this.$options.getOption('debounce'); 31052 31053 if (isNumber(debounceDelay[trigger])) { 31054 debounceDelay = debounceDelay[trigger]; 31055 } else if (isNumber(debounceDelay['default']) && 31056 this.$options.getOption('updateOn').indexOf(trigger) === -1 31057 ) { 31058 debounceDelay = debounceDelay['default']; 31059 } else if (isNumber(debounceDelay['*'])) { 31060 debounceDelay = debounceDelay['*']; 31061 } 31062 31063 this.$$timeout.cancel(this.$$pendingDebounce); 31064 var that = this; 31065 if (debounceDelay > 0) { // this fails if debounceDelay is an object 31066 this.$$pendingDebounce = this.$$timeout(function() { 31067 that.$commitViewValue(); 31068 }, debounceDelay); 31069 } else if (this.$$rootScope.$$phase) { 31070 this.$commitViewValue(); 31071 } else { 31072 this.$$scope.$apply(function() { 31073 that.$commitViewValue(); 31074 }); 31075 } 31076 }, 31077 31078 /** 31079 * @ngdoc method 31080 * 31081 * @name ngModel.NgModelController#$overrideModelOptions 31082 * 31083 * @description 31084 * 31085 * Override the current model options settings programmatically. 31086 * 31087 * The previous `ModelOptions` value will not be modified. Instead, a 31088 * new `ModelOptions` object will inherit from the previous one overriding 31089 * or inheriting settings that are defined in the given parameter. 31090 * 31091 * See {@link ngModelOptions} for information about what options can be specified 31092 * and how model option inheritance works. 31093 * 31094 * <div class="alert alert-warning"> 31095 * **Note:** this function only affects the options set on the `ngModelController`, 31096 * and not the options on the {@link ngModelOptions} directive from which they might have been 31097 * obtained initially. 31098 * </div> 31099 * 31100 * <div class="alert alert-danger"> 31101 * **Note:** it is not possible to override the `getterSetter` option. 31102 * </div> 31103 * 31104 * @param {Object} options a hash of settings to override the previous options 31105 * 31106 */ 31107 $overrideModelOptions: function(options) { 31108 this.$options = this.$options.createChild(options); 31109 this.$$setUpdateOnEvents(); 31110 }, 31111 31112 /** 31113 * @ngdoc method 31114 * 31115 * @name ngModel.NgModelController#$processModelValue 31116 31117 * @description 31118 * 31119 * Runs the model -> view pipeline on the current 31120 * {@link ngModel.NgModelController#$modelValue $modelValue}. 31121 * 31122 * The following actions are performed by this method: 31123 * 31124 * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters} 31125 * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue} 31126 * - the `ng-empty` or `ng-not-empty` class is set on the element 31127 * - if the `$viewValue` has changed: 31128 * - {@link ngModel.NgModelController#$render $render} is called on the control 31129 * - the {@link ngModel.NgModelController#$validators $validators} are run and 31130 * the validation status is set. 31131 * 31132 * This method is called by ngModel internally when the bound scope value changes. 31133 * Application developers usually do not have to call this function themselves. 31134 * 31135 * This function can be used when the `$viewValue` or the rendered DOM value are not correctly 31136 * formatted and the `$modelValue` must be run through the `$formatters` again. 31137 * 31138 * @example 31139 * Consider a text input with an autocomplete list (for fruit), where the items are 31140 * objects with a name and an id. 31141 * A user enters `ap` and then selects `Apricot` from the list. 31142 * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`, 31143 * but the rendered value will still be `ap`. 31144 * The widget can then call `ctrl.$processModelValue()` to run the model -> view 31145 * pipeline again, which formats the object to the string `Apricot`, 31146 * then updates the `$viewValue`, and finally renders it in the DOM. 31147 * 31148 * <example module="inputExample" name="ng-model-process"> 31149 <file name="index.html"> 31150 <div ng-controller="inputController" style="display: flex;"> 31151 <div style="margin-right: 30px;"> 31152 Search Fruit: 31153 <basic-autocomplete items="items" on-select="selectedFruit = item"></basic-autocomplete> 31154 </div> 31155 <div> 31156 Model:<br> 31157 <pre>{{selectedFruit | json}}</pre> 31158 </div> 31159 </div> 31160 </file> 31161 <file name="app.js"> 31162 angular.module('inputExample', []) 31163 .controller('inputController', function($scope) { 31164 $scope.items = [ 31165 {name: 'Apricot', id: 443}, 31166 {name: 'Clementine', id: 972}, 31167 {name: 'Durian', id: 169}, 31168 {name: 'Jackfruit', id: 982}, 31169 {name: 'Strawberry', id: 863} 31170 ]; 31171 }) 31172 .component('basicAutocomplete', { 31173 bindings: { 31174 items: '<', 31175 onSelect: '&' 31176 }, 31177 templateUrl: 'autocomplete.html', 31178 controller: function($element, $scope) { 31179 var that = this; 31180 var ngModel; 31181 31182 that.$postLink = function() { 31183 ngModel = $element.find('input').controller('ngModel'); 31184 31185 ngModel.$formatters.push(function(value) { 31186 return (value && value.name) || value; 31187 }); 31188 31189 ngModel.$parsers.push(function(value) { 31190 var match = value; 31191 for (var i = 0; i < that.items.length; i++) { 31192 if (that.items[i].name === value) { 31193 match = that.items[i]; 31194 break; 31195 } 31196 } 31197 31198 return match; 31199 }); 31200 }; 31201 31202 that.selectItem = function(item) { 31203 ngModel.$setViewValue(item); 31204 ngModel.$processModelValue(); 31205 that.onSelect({item: item}); 31206 }; 31207 } 31208 }); 31209 </file> 31210 <file name="autocomplete.html"> 31211 <div> 31212 <input type="search" ng-model="$ctrl.searchTerm" /> 31213 <ul> 31214 <li ng-repeat="item in $ctrl.items | filter:$ctrl.searchTerm"> 31215 <button ng-click="$ctrl.selectItem(item)">{{ item.name }}</button> 31216 </li> 31217 </ul> 31218 </div> 31219 </file> 31220 * </example> 31221 * 31222 */ 31223 $processModelValue: function() { 31224 var viewValue = this.$$format(); 31225 31226 if (this.$viewValue !== viewValue) { 31227 this.$$updateEmptyClasses(viewValue); 31228 this.$viewValue = this.$$lastCommittedViewValue = viewValue; 31229 this.$render(); 31230 // It is possible that model and view value have been updated during render 31231 this.$$runValidators(this.$modelValue, this.$viewValue, noop); 31232 } 31233 }, 31234 31235 /** 31236 * This method is called internally to run the $formatters on the $modelValue 31237 */ 31238 $$format: function() { 31239 var formatters = this.$formatters, 31240 idx = formatters.length; 31241 31242 var viewValue = this.$modelValue; 31243 while (idx--) { 31244 viewValue = formatters[idx](viewValue); 31245 } 31246 31247 return viewValue; 31248 }, 31249 31250 /** 31251 * This method is called internally when the bound scope value changes. 31252 */ 31253 $$setModelValue: function(modelValue) { 31254 this.$modelValue = this.$$rawModelValue = modelValue; 31255 this.$$parserValid = undefined; 31256 this.$processModelValue(); 31257 }, 31258 31259 $$setUpdateOnEvents: function() { 31260 if (this.$$updateEvents) { 31261 this.$$element.off(this.$$updateEvents, this.$$updateEventHandler); 31262 } 31263 31264 this.$$updateEvents = this.$options.getOption('updateOn'); 31265 if (this.$$updateEvents) { 31266 this.$$element.on(this.$$updateEvents, this.$$updateEventHandler); 31267 } 31268 }, 31269 31270 $$updateEventHandler: function(ev) { 31271 this.$$debounceViewValueCommit(ev && ev.type); 31272 } 31273}; 31274 31275function setupModelWatcher(ctrl) { 31276 // model -> value 31277 // Note: we cannot use a normal scope.$watch as we want to detect the following: 31278 // 1. scope value is 'a' 31279 // 2. user enters 'b' 31280 // 3. ng-change kicks in and reverts scope value to 'a' 31281 // -> scope value did not change since the last digest as 31282 // ng-change executes in apply phase 31283 // 4. view should be changed back to 'a' 31284 ctrl.$$scope.$watch(function ngModelWatch(scope) { 31285 var modelValue = ctrl.$$ngModelGet(scope); 31286 31287 // if scope model value and ngModel value are out of sync 31288 // This cannot be moved to the action function, because it would not catch the 31289 // case where the model is changed in the ngChange function or the model setter 31290 if (modelValue !== ctrl.$modelValue && 31291 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 31292 // eslint-disable-next-line no-self-compare 31293 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 31294 ) { 31295 ctrl.$$setModelValue(modelValue); 31296 } 31297 31298 return modelValue; 31299 }); 31300} 31301 31302/** 31303 * @ngdoc method 31304 * @name ngModel.NgModelController#$setValidity 31305 * 31306 * @description 31307 * Change the validity state, and notify the form. 31308 * 31309 * This method can be called within $parsers/$formatters or a custom validation implementation. 31310 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 31311 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 31312 * 31313 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 31314 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 31315 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 31316 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 31317 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 31318 * classes and can be bound to as `{{ someForm.someControl.$error.myError }}`. 31319 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 31320 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 31321 * Skipped is used by AngularJS when validators do not run because of parse errors and 31322 * when `$asyncValidators` do not run because any of the `$validators` failed. 31323 */ 31324addSetValidityMethod({ 31325 clazz: NgModelController, 31326 set: function(object, property) { 31327 object[property] = true; 31328 }, 31329 unset: function(object, property) { 31330 delete object[property]; 31331 } 31332}); 31333 31334 31335/** 31336 * @ngdoc directive 31337 * @name ngModel 31338 * @restrict A 31339 * @priority 1 31340 * @param {expression} ngModel assignable {@link guide/expression Expression} to bind to. 31341 * 31342 * @description 31343 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 31344 * property on the scope using {@link ngModel.NgModelController NgModelController}, 31345 * which is created and exposed by this directive. 31346 * 31347 * `ngModel` is responsible for: 31348 * 31349 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 31350 * require. 31351 * - Providing validation behavior (i.e. required, number, email, url). 31352 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 31353 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 31354 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 31355 * - Registering the control with its parent {@link ng.directive:form form}. 31356 * 31357 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 31358 * current scope. If the property doesn't already exist on this scope, it will be created 31359 * implicitly and added to the scope. 31360 * 31361 * For best practices on using `ngModel`, see: 31362 * 31363 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 31364 * 31365 * For basic examples, how to use `ngModel`, see: 31366 * 31367 * - {@link ng.directive:input input} 31368 * - {@link input[text] text} 31369 * - {@link input[checkbox] checkbox} 31370 * - {@link input[radio] radio} 31371 * - {@link input[number] number} 31372 * - {@link input[email] email} 31373 * - {@link input[url] url} 31374 * - {@link input[date] date} 31375 * - {@link input[datetime-local] datetime-local} 31376 * - {@link input[time] time} 31377 * - {@link input[month] month} 31378 * - {@link input[week] week} 31379 * - {@link ng.directive:select select} 31380 * - {@link ng.directive:textarea textarea} 31381 * 31382 * ## Complex Models (objects or collections) 31383 * 31384 * By default, `ngModel` watches the model by reference, not value. This is important to know when 31385 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the 31386 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. 31387 * 31388 * The model must be assigned an entirely new object or collection before a re-rendering will occur. 31389 * 31390 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression 31391 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or 31392 * if the select is given the `multiple` attribute. 31393 * 31394 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the 31395 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still 31396 * not trigger a re-rendering of the model. 31397 * 31398 * ## CSS classes 31399 * The following CSS classes are added and removed on the associated input/select/textarea element 31400 * depending on the validity of the model. 31401 * 31402 * - `ng-valid`: the model is valid 31403 * - `ng-invalid`: the model is invalid 31404 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 31405 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 31406 * - `ng-pristine`: the control hasn't been interacted with yet 31407 * - `ng-dirty`: the control has been interacted with 31408 * - `ng-touched`: the control has been blurred 31409 * - `ng-untouched`: the control hasn't been blurred 31410 * - `ng-pending`: any `$asyncValidators` are unfulfilled 31411 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 31412 * by the {@link ngModel.NgModelController#$isEmpty} method 31413 * - `ng-not-empty`: the view contains a non-empty value 31414 * 31415 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 31416 * 31417 * @animations 31418 * Animations within models are triggered when any of the associated CSS classes are added and removed 31419 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 31420 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 31421 * The animations that are triggered within ngModel are similar to how they work in ngClass and 31422 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 31423 * 31424 * The following example shows a simple way to utilize CSS transitions to style an input element 31425 * that has been rendered as invalid after it has been validated: 31426 * 31427 * <pre> 31428 * //be sure to include ngAnimate as a module to hook into more 31429 * //advanced animations 31430 * .my-input { 31431 * transition:0.5s linear all; 31432 * background: white; 31433 * } 31434 * .my-input.ng-invalid { 31435 * background: red; 31436 * color:white; 31437 * } 31438 * </pre> 31439 * 31440 * @example 31441 * ### Basic Usage 31442 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample" name="ng-model"> 31443 <file name="index.html"> 31444 <script> 31445 angular.module('inputExample', []) 31446 .controller('ExampleController', ['$scope', function($scope) { 31447 $scope.val = '1'; 31448 }]); 31449 </script> 31450 <style> 31451 .my-input { 31452 transition:all linear 0.5s; 31453 background: transparent; 31454 } 31455 .my-input.ng-invalid { 31456 color:white; 31457 background: red; 31458 } 31459 </style> 31460 <p id="inputDescription"> 31461 Update input to see transitions when valid/invalid. 31462 Integer is a valid value. 31463 </p> 31464 <form name="testForm" ng-controller="ExampleController"> 31465 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 31466 aria-describedby="inputDescription" /> 31467 </form> 31468 </file> 31469 * </example> 31470 * 31471 * @example 31472 * ### Binding to a getter/setter 31473 * 31474 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 31475 * function that returns a representation of the model when called with zero arguments, and sets 31476 * the internal state of a model when called with an argument. It's sometimes useful to use this 31477 * for models that have an internal representation that's different from what the model exposes 31478 * to the view. 31479 * 31480 * <div class="alert alert-success"> 31481 * **Best Practice:** It's best to keep getters fast because AngularJS is likely to call them more 31482 * frequently than other parts of your code. 31483 * </div> 31484 * 31485 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 31486 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 31487 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 31488 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 31489 * 31490 * The following example shows how to use `ngModel` with a getter/setter: 31491 * 31492 * @example 31493 * <example name="ngModel-getter-setter" module="getterSetterExample"> 31494 <file name="index.html"> 31495 <div ng-controller="ExampleController"> 31496 <form name="userForm"> 31497 <label>Name: 31498 <input type="text" name="userName" 31499 ng-model="user.name" 31500 ng-model-options="{ getterSetter: true }" /> 31501 </label> 31502 </form> 31503 <pre>user.name = <span ng-bind="user.name()"></span></pre> 31504 </div> 31505 </file> 31506 <file name="app.js"> 31507 angular.module('getterSetterExample', []) 31508 .controller('ExampleController', ['$scope', function($scope) { 31509 var _name = 'Brian'; 31510 $scope.user = { 31511 name: function(newName) { 31512 // Note that newName can be undefined for two reasons: 31513 // 1. Because it is called as a getter and thus called with no arguments 31514 // 2. Because the property should actually be set to undefined. This happens e.g. if the 31515 // input is invalid 31516 return arguments.length ? (_name = newName) : _name; 31517 } 31518 }; 31519 }]); 31520 </file> 31521 * </example> 31522 */ 31523var ngModelDirective = ['$rootScope', function($rootScope) { 31524 return { 31525 restrict: 'A', 31526 require: ['ngModel', '^?form', '^?ngModelOptions'], 31527 controller: NgModelController, 31528 // Prelink needs to run before any input directive 31529 // so that we can set the NgModelOptions in NgModelController 31530 // before anyone else uses it. 31531 priority: 1, 31532 compile: function ngModelCompile(element) { 31533 // Setup initial state of the control 31534 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 31535 31536 return { 31537 pre: function ngModelPreLink(scope, element, attr, ctrls) { 31538 var modelCtrl = ctrls[0], 31539 formCtrl = ctrls[1] || modelCtrl.$$parentForm, 31540 optionsCtrl = ctrls[2]; 31541 31542 if (optionsCtrl) { 31543 modelCtrl.$options = optionsCtrl.$options; 31544 } 31545 31546 modelCtrl.$$initGetterSetters(); 31547 31548 // notify others, especially parent forms 31549 formCtrl.$addControl(modelCtrl); 31550 31551 attr.$observe('name', function(newValue) { 31552 if (modelCtrl.$name !== newValue) { 31553 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 31554 } 31555 }); 31556 31557 scope.$on('$destroy', function() { 31558 modelCtrl.$$parentForm.$removeControl(modelCtrl); 31559 }); 31560 }, 31561 post: function ngModelPostLink(scope, element, attr, ctrls) { 31562 var modelCtrl = ctrls[0]; 31563 modelCtrl.$$setUpdateOnEvents(); 31564 31565 function setTouched() { 31566 modelCtrl.$setTouched(); 31567 } 31568 31569 element.on('blur', function() { 31570 if (modelCtrl.$touched) return; 31571 31572 if ($rootScope.$$phase) { 31573 scope.$evalAsync(setTouched); 31574 } else { 31575 scope.$apply(setTouched); 31576 } 31577 }); 31578 } 31579 }; 31580 } 31581 }; 31582}]; 31583 31584/* exported defaultModelOptions */ 31585var defaultModelOptions; 31586var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 31587 31588/** 31589 * @ngdoc type 31590 * @name ModelOptions 31591 * @description 31592 * A container for the options set by the {@link ngModelOptions} directive 31593 */ 31594function ModelOptions(options) { 31595 this.$$options = options; 31596} 31597 31598ModelOptions.prototype = { 31599 31600 /** 31601 * @ngdoc method 31602 * @name ModelOptions#getOption 31603 * @param {string} name the name of the option to retrieve 31604 * @returns {*} the value of the option 31605 * @description 31606 * Returns the value of the given option 31607 */ 31608 getOption: function(name) { 31609 return this.$$options[name]; 31610 }, 31611 31612 /** 31613 * @ngdoc method 31614 * @name ModelOptions#createChild 31615 * @param {Object} options a hash of options for the new child that will override the parent's options 31616 * @return {ModelOptions} a new `ModelOptions` object initialized with the given options. 31617 */ 31618 createChild: function(options) { 31619 var inheritAll = false; 31620 31621 // make a shallow copy 31622 options = extend({}, options); 31623 31624 // Inherit options from the parent if specified by the value `"$inherit"` 31625 forEach(options, /** @this */ function(option, key) { 31626 if (option === '$inherit') { 31627 if (key === '*') { 31628 inheritAll = true; 31629 } else { 31630 options[key] = this.$$options[key]; 31631 // `updateOn` is special so we must also inherit the `updateOnDefault` option 31632 if (key === 'updateOn') { 31633 options.updateOnDefault = this.$$options.updateOnDefault; 31634 } 31635 } 31636 } else { 31637 if (key === 'updateOn') { 31638 // If the `updateOn` property contains the `default` event then we have to remove 31639 // it from the event list and set the `updateOnDefault` flag. 31640 options.updateOnDefault = false; 31641 options[key] = trim(option.replace(DEFAULT_REGEXP, function() { 31642 options.updateOnDefault = true; 31643 return ' '; 31644 })); 31645 } 31646 } 31647 }, this); 31648 31649 if (inheritAll) { 31650 // We have a property of the form: `"*": "$inherit"` 31651 delete options['*']; 31652 defaults(options, this.$$options); 31653 } 31654 31655 // Finally add in any missing defaults 31656 defaults(options, defaultModelOptions.$$options); 31657 31658 return new ModelOptions(options); 31659 } 31660}; 31661 31662 31663defaultModelOptions = new ModelOptions({ 31664 updateOn: '', 31665 updateOnDefault: true, 31666 debounce: 0, 31667 getterSetter: false, 31668 allowInvalid: false, 31669 timezone: null 31670}); 31671 31672 31673/** 31674 * @ngdoc directive 31675 * @name ngModelOptions 31676 * @restrict A 31677 * @priority 10 31678 * 31679 * @description 31680 * This directive allows you to modify the behaviour of {@link ngModel} directives within your 31681 * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel} 31682 * directives will use the options of their nearest `ngModelOptions` ancestor. 31683 * 31684 * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as 31685 * an AngularJS expression. This expression should evaluate to an object, whose properties contain 31686 * the settings. For example: `<div ng-model-options="{ debounce: 100 }"`. 31687 * 31688 * ## Inheriting Options 31689 * 31690 * You can specify that an `ngModelOptions` setting should be inherited from a parent `ngModelOptions` 31691 * directive by giving it the value of `"$inherit"`. 31692 * Then it will inherit that setting from the first `ngModelOptions` directive found by traversing up the 31693 * DOM tree. If there is no ancestor element containing an `ngModelOptions` directive then default settings 31694 * will be used. 31695 * 31696 * For example given the following fragment of HTML 31697 * 31698 * 31699 * ```html 31700 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }"> 31701 * <form ng-model-options="{ updateOn: 'blur', allowInvalid: '$inherit' }"> 31702 * <input ng-model-options="{ updateOn: 'default', allowInvalid: '$inherit' }" /> 31703 * </form> 31704 * </div> 31705 * ``` 31706 * 31707 * the `input` element will have the following settings 31708 * 31709 * ```js 31710 * { allowInvalid: true, updateOn: 'default', debounce: 0 } 31711 * ``` 31712 * 31713 * Notice that the `debounce` setting was not inherited and used the default value instead. 31714 * 31715 * You can specify that all undefined settings are automatically inherited from an ancestor by 31716 * including a property with key of `"*"` and value of `"$inherit"`. 31717 * 31718 * For example given the following fragment of HTML 31719 * 31720 * 31721 * ```html 31722 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }"> 31723 * <form ng-model-options="{ updateOn: 'blur', "*": '$inherit' }"> 31724 * <input ng-model-options="{ updateOn: 'default', "*": '$inherit' }" /> 31725 * </form> 31726 * </div> 31727 * ``` 31728 * 31729 * the `input` element will have the following settings 31730 * 31731 * ```js 31732 * { allowInvalid: true, updateOn: 'default', debounce: 200 } 31733 * ``` 31734 * 31735 * Notice that the `debounce` setting now inherits the value from the outer `<div>` element. 31736 * 31737 * If you are creating a reusable component then you should be careful when using `"*": "$inherit"` 31738 * since you may inadvertently inherit a setting in the future that changes the behavior of your component. 31739 * 31740 * 31741 * ## Triggering and debouncing model updates 31742 * 31743 * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will 31744 * trigger a model update and/or a debouncing delay so that the actual update only takes place when 31745 * a timer expires; this timer will be reset after another change takes place. 31746 * 31747 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 31748 * be different from the value in the actual model. This means that if you update the model you 31749 * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in 31750 * order to make sure it is synchronized with the model and that any debounced action is canceled. 31751 * 31752 * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue} 31753 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 31754 * important because `form` controllers are published to the related scope under the name in their 31755 * `name` attribute. 31756 * 31757 * Any pending changes will take place immediately when an enclosing form is submitted via the 31758 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 31759 * to have access to the updated model. 31760 * 31761 * ### Overriding immediate updates 31762 * 31763 * The following example shows how to override immediate updates. Changes on the inputs within the 31764 * form will update the model only when the control loses focus (blur event). If `escape` key is 31765 * pressed while the input field is focused, the value is reset to the value in the current model. 31766 * 31767 * <example name="ngModelOptions-directive-blur" module="optionsExample"> 31768 * <file name="index.html"> 31769 * <div ng-controller="ExampleController"> 31770 * <form name="userForm"> 31771 * <label> 31772 * Name: 31773 * <input type="text" name="userName" 31774 * ng-model="user.name" 31775 * ng-model-options="{ updateOn: 'blur' }" 31776 * ng-keyup="cancel($event)" /> 31777 * </label><br /> 31778 * <label> 31779 * Other data: 31780 * <input type="text" ng-model="user.data" /> 31781 * </label><br /> 31782 * </form> 31783 * <pre>user.name = <span ng-bind="user.name"></span></pre> 31784 * </div> 31785 * </file> 31786 * <file name="app.js"> 31787 * angular.module('optionsExample', []) 31788 * .controller('ExampleController', ['$scope', function($scope) { 31789 * $scope.user = { name: 'say', data: '' }; 31790 * 31791 * $scope.cancel = function(e) { 31792 * if (e.keyCode === 27) { 31793 * $scope.userForm.userName.$rollbackViewValue(); 31794 * } 31795 * }; 31796 * }]); 31797 * </file> 31798 * <file name="protractor.js" type="protractor"> 31799 * var model = element(by.binding('user.name')); 31800 * var input = element(by.model('user.name')); 31801 * var other = element(by.model('user.data')); 31802 * 31803 * it('should allow custom events', function() { 31804 * input.sendKeys(' hello'); 31805 * input.click(); 31806 * expect(model.getText()).toEqual('say'); 31807 * other.click(); 31808 * expect(model.getText()).toEqual('say hello'); 31809 * }); 31810 * 31811 * it('should $rollbackViewValue when model changes', function() { 31812 * input.sendKeys(' hello'); 31813 * expect(input.getAttribute('value')).toEqual('say hello'); 31814 * input.sendKeys(protractor.Key.ESCAPE); 31815 * expect(input.getAttribute('value')).toEqual('say'); 31816 * other.click(); 31817 * expect(model.getText()).toEqual('say'); 31818 * }); 31819 * </file> 31820 * </example> 31821 * 31822 * ### Debouncing updates 31823 * 31824 * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change. 31825 * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 31826 * 31827 * <example name="ngModelOptions-directive-debounce" module="optionsExample"> 31828 * <file name="index.html"> 31829 * <div ng-controller="ExampleController"> 31830 * <form name="userForm"> 31831 * Name: 31832 * <input type="text" name="userName" 31833 * ng-model="user.name" 31834 * ng-model-options="{ debounce: 1000 }" /> 31835 * <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br /> 31836 * </form> 31837 * <pre>user.name = <span ng-bind="user.name"></span></pre> 31838 * </div> 31839 * </file> 31840 * <file name="app.js"> 31841 * angular.module('optionsExample', []) 31842 * .controller('ExampleController', ['$scope', function($scope) { 31843 * $scope.user = { name: 'say' }; 31844 * }]); 31845 * </file> 31846 * </example> 31847 * 31848 * ### Default events, extra triggers, and catch-all debounce values 31849 * 31850 * This example shows the relationship between "default" update events and 31851 * additional `updateOn` triggers. 31852 * 31853 * `default` events are those that are bound to the control, and when fired, update the `$viewValue` 31854 * via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed 31855 * in `updateOn` is considered a "default" event, since different control types have different 31856 * default events. 31857 * 31858 * The control in this example updates by "default", "click", and "blur", with different `debounce` 31859 * values. You can see that "click" doesn't have an individual `debounce` value - 31860 * therefore it uses the `*` debounce value. 31861 * 31862 * There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue} 31863 * directly with a "custom" event. Since "custom" is not defined in the `updateOn` list, 31864 * it is considered a "default" event and will update the 31865 * control if "default" is defined in `updateOn`, and will receive the "default" debounce value. 31866 * Note that this is just to illustrate how custom controls would possibly call `$setViewValue`. 31867 * 31868 * You can change the `updateOn` and `debounce` configuration to test different scenarios. This 31869 * is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}. 31870 * 31871 <example name="ngModelOptions-advanced" module="optionsExample"> 31872 <file name="index.html"> 31873 <model-update-demo></model-update-demo> 31874 </file> 31875 <file name="app.js"> 31876 angular.module('optionsExample', []) 31877 .component('modelUpdateDemo', { 31878 templateUrl: 'template.html', 31879 controller: function() { 31880 this.name = 'Chinua'; 31881 31882 this.options = { 31883 updateOn: 'default blur click', 31884 debounce: { 31885 default: 2000, 31886 blur: 0, 31887 '*': 1000 31888 } 31889 }; 31890 31891 this.updateEvents = function() { 31892 var eventList = this.options.updateOn.split(' '); 31893 eventList.push('*'); 31894 var events = {}; 31895 31896 for (var i = 0; i < eventList.length; i++) { 31897 events[eventList[i]] = this.options.debounce[eventList[i]]; 31898 } 31899 31900 this.events = events; 31901 }; 31902 31903 this.updateOptions = function() { 31904 var options = angular.extend(this.options, { 31905 updateOn: Object.keys(this.events).join(' ').replace('*', ''), 31906 debounce: this.events 31907 }); 31908 31909 this.form.input.$overrideModelOptions(options); 31910 }; 31911 31912 // Initialize the event form 31913 this.updateEvents(); 31914 } 31915 }); 31916 </file> 31917 <file name="template.html"> 31918 <form name="$ctrl.form"> 31919 Input: <input type="text" name="input" ng-model="$ctrl.name" ng-model-options="$ctrl.options" /> 31920 </form> 31921 Model: <tt>{{$ctrl.name}}</tt> 31922 <hr> 31923 <button ng-click="$ctrl.form.input.$setViewValue('some value', 'custom')">Trigger setViewValue with 'some value' and 'custom' event</button> 31924 31925 <hr> 31926 <form ng-submit="$ctrl.updateOptions()"> 31927 <b>updateOn</b><br> 31928 <input type="text" ng-model="$ctrl.options.updateOn" ng-change="$ctrl.updateEvents()" ng-model-options="{debounce: 500}"> 31929 31930 <table> 31931 <tr> 31932 <th>Option</th> 31933 <th>Debounce value</th> 31934 </tr> 31935 <tr ng-repeat="(key, value) in $ctrl.events"> 31936 <td>{{key}}</td> 31937 <td><input type="number" ng-model="$ctrl.events[key]" /></td> 31938 </tr> 31939 </table> 31940 31941 <br> 31942 <input type="submit" value="Update options"> 31943 </form> 31944 </file> 31945 </example> 31946 * 31947 * 31948 * ## Model updates and validation 31949 * 31950 * The default behaviour in `ngModel` is that the model value is set to `undefined` when the 31951 * validation determines that the value is invalid. By setting the `allowInvalid` property to true, 31952 * the model will still be updated even if the value is invalid. 31953 * 31954 * 31955 * ## Connecting to the scope 31956 * 31957 * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression 31958 * on the scope refers to a "getter/setter" function rather than the value itself. 31959 * 31960 * The following example shows how to bind to getter/setters: 31961 * 31962 * <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 31963 * <file name="index.html"> 31964 * <div ng-controller="ExampleController"> 31965 * <form name="userForm"> 31966 * <label> 31967 * Name: 31968 * <input type="text" name="userName" 31969 * ng-model="user.name" 31970 * ng-model-options="{ getterSetter: true }" /> 31971 * </label> 31972 * </form> 31973 * <pre>user.name = <span ng-bind="user.name()"></span></pre> 31974 * </div> 31975 * </file> 31976 * <file name="app.js"> 31977 * angular.module('getterSetterExample', []) 31978 * .controller('ExampleController', ['$scope', function($scope) { 31979 * var _name = 'Brian'; 31980 * $scope.user = { 31981 * name: function(newName) { 31982 * return angular.isDefined(newName) ? (_name = newName) : _name; 31983 * } 31984 * }; 31985 * }]); 31986 * </file> 31987 * </example> 31988 * 31989 * 31990 * ## Programmatically changing options 31991 * 31992 * The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not 31993 * watched for changes. However, it is possible to override the options on a single 31994 * {@link ngModel.NgModelController} instance with 31995 * {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}. 31996 * See also the example for 31997 * {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values 31998 * Default events, extra triggers, and catch-all debounce values}. 31999 * 32000 * 32001 * ## Specifying timezones 32002 * 32003 * You can specify the timezone that date/time input directives expect by providing its name in the 32004 * `timezone` property. 32005 * 32006 * 32007 * ## Formatting the value of `time` and `datetime-local` 32008 * 32009 * With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value 32010 * that is displayed in the control. Note that browsers may apply their own formatting 32011 * in the user interface. 32012 * 32013 <example name="ngModelOptions-time-format" module="timeExample"> 32014 <file name="index.html"> 32015 <time-example></time-example> 32016 </file> 32017 <file name="script.js"> 32018 angular.module('timeExample', []) 32019 .component('timeExample', { 32020 templateUrl: 'timeExample.html', 32021 controller: function() { 32022 this.time = new Date(1970, 0, 1, 14, 57, 0); 32023 32024 this.options = { 32025 timeSecondsFormat: 'ss', 32026 timeStripZeroSeconds: true 32027 }; 32028 32029 this.optionChange = function() { 32030 this.timeForm.timeFormatted.$overrideModelOptions(this.options); 32031 this.time = new Date(this.time); 32032 }; 32033 } 32034 }); 32035 </file> 32036 <file name="timeExample.html"> 32037 <form name="$ctrl.timeForm"> 32038 <strong>Default</strong>: 32039 <input type="time" ng-model="$ctrl.time" step="any" /><br> 32040 <strong>With options</strong>: 32041 <input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" /> 32042 <br> 32043 32044 Options:<br> 32045 <code>timeSecondsFormat</code>: 32046 <input 32047 type="text" 32048 ng-model="$ctrl.options.timeSecondsFormat" 32049 ng-change="$ctrl.optionChange()"> 32050 <br> 32051 <code>timeStripZeroSeconds</code>: 32052 <input 32053 type="checkbox" 32054 ng-model="$ctrl.options.timeStripZeroSeconds" 32055 ng-change="$ctrl.optionChange()"> 32056 </form> 32057 </file> 32058 * </example> 32059 * 32060 * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and 32061 * and its descendents. 32062 * 32063 * **General options**: 32064 * 32065 * - `updateOn`: string specifying which event should the input be bound to. You can set several 32066 * events using an space delimited list. There is a special event called `default` that 32067 * matches the default events belonging to the control. These are the events that are bound to 32068 * the control, and when fired, update the `$viewValue` via `$setViewValue`. 32069 * 32070 * `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event, 32071 * since different control types use different default events. 32072 * 32073 * See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates 32074 * Triggering and debouncing model updates}. 32075 * 32076 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 32077 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 32078 * custom value for each event. For example: 32079 * ``` 32080 * ng-model-options="{ 32081 * updateOn: 'default blur', 32082 * debounce: { 'default': 500, 'blur': 0 } 32083 * }" 32084 * ``` 32085 * You can use the `*` key to specify a debounce value that applies to all events that are not 32086 * specifically listed. In the following example, `mouseup` would have a debounce delay of 1000: 32087 * ``` 32088 * ng-model-options="{ 32089 * updateOn: 'default blur mouseup', 32090 * debounce: { 'default': 500, 'blur': 0, '*': 1000 } 32091 * }" 32092 * ``` 32093 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 32094 * not validate correctly instead of the default behavior of setting the model to undefined. 32095 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 32096 * `ngModel` as getters/setters. 32097 * 32098 * 32099 * **Input-type specific options**: 32100 * 32101 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 32102 * `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the 32103 * continental US time zone abbreviations, but for general use, use a time zone offset, for 32104 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 32105 * If not specified, the timezone of the browser will be used. 32106 * Note that changing the timezone will have no effect on the current date, and is only applied after 32107 * the next input / model change. 32108 * 32109 * - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and 32110 * milliseconds. The option follows the format string of {@link date date filter}. 32111 * By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds). 32112 * The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both 32113 * seconds and milliseconds. 32114 * Note that browsers that support `time` and `datetime-local` require the hour and minutes 32115 * part of the time string, and may show the value differently in the user interface. 32116 * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}. 32117 * 32118 * - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the 32119 * seconds and milliseconds from the formatted value if they are zero. This option is applied 32120 * after `timeSecondsFormat`. 32121 * This option can be used to make the formatting consistent over different browsers, as some 32122 * browsers with support for `time` will natively hide the milliseconds and 32123 * seconds if they are zero, but others won't, and browsers that don't implement these input 32124 * types will always show the full string. 32125 * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}. 32126 * 32127 */ 32128var ngModelOptionsDirective = function() { 32129 NgModelOptionsController.$inject = ['$attrs', '$scope']; 32130 function NgModelOptionsController($attrs, $scope) { 32131 this.$$attrs = $attrs; 32132 this.$$scope = $scope; 32133 } 32134 NgModelOptionsController.prototype = { 32135 $onInit: function() { 32136 var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions; 32137 var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions); 32138 32139 this.$options = parentOptions.createChild(modelOptionsDefinition); 32140 } 32141 }; 32142 32143 return { 32144 restrict: 'A', 32145 // ngModelOptions needs to run before ngModel and input directives 32146 priority: 10, 32147 require: {parentCtrl: '?^^ngModelOptions'}, 32148 bindToController: true, 32149 controller: NgModelOptionsController 32150 }; 32151}; 32152 32153 32154// shallow copy over values from `src` that are not already specified on `dst` 32155function defaults(dst, src) { 32156 forEach(src, function(value, key) { 32157 if (!isDefined(dst[key])) { 32158 dst[key] = value; 32159 } 32160 }); 32161} 32162 32163/** 32164 * @ngdoc directive 32165 * @name ngNonBindable 32166 * @restrict AC 32167 * @priority 1000 32168 * @element ANY 32169 * 32170 * @description 32171 * The `ngNonBindable` directive tells AngularJS not to compile or bind the contents of the current 32172 * DOM element, including directives on the element itself that have a lower priority than 32173 * `ngNonBindable`. This is useful if the element contains what appears to be AngularJS directives 32174 * and bindings but which should be ignored by AngularJS. This could be the case if you have a site 32175 * that displays snippets of code, for instance. 32176 * 32177 * @example 32178 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 32179 * but the one wrapped in `ngNonBindable` is left alone. 32180 * 32181 <example name="ng-non-bindable"> 32182 <file name="index.html"> 32183 <div>Normal: {{1 + 2}}</div> 32184 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 32185 </file> 32186 <file name="protractor.js" type="protractor"> 32187 it('should check ng-non-bindable', function() { 32188 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 32189 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 32190 }); 32191 </file> 32192 </example> 32193 */ 32194var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 32195 32196/* exported ngOptionsDirective */ 32197 32198/* global jqLiteRemove */ 32199 32200var ngOptionsMinErr = minErr('ngOptions'); 32201 32202/** 32203 * @ngdoc directive 32204 * @name ngOptions 32205 * @restrict A 32206 * 32207 * @description 32208 * 32209 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 32210 * elements for the `<select>` element using the array or object obtained by evaluating the 32211 * `ngOptions` comprehension expression. 32212 * 32213 * In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements instead of 32214 * `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits: 32215 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 32216 * comprehension expression 32217 * - reduced memory consumption by not creating a new scope for each repeated instance 32218 * - increased render speed by creating the options in a documentFragment instead of individually 32219 * 32220 * When an item in the `<select>` menu is selected, the array element or object property 32221 * represented by the selected option will be bound to the model identified by the `ngModel` 32222 * directive. 32223 * 32224 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 32225 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 32226 * option. See example below for demonstration. 32227 * 32228 * ## Complex Models (objects or collections) 32229 * 32230 * By default, `ngModel` watches the model by reference, not value. This is important to know when 32231 * binding the select to a model that is an object or a collection. 32232 * 32233 * One issue occurs if you want to preselect an option. For example, if you set 32234 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 32235 * because the objects are not identical. So by default, you should always reference the item in your collection 32236 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 32237 * 32238 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 32239 * of the item not by reference, but by the result of the `track by` expression. For example, if your 32240 * collection items have an id property, you would `track by item.id`. 32241 * 32242 * A different issue with objects or collections is that ngModel won't detect if an object property or 32243 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 32244 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 32245 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 32246 * has not changed identity, but only a property on the object or an item in the collection changes. 32247 * 32248 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 32249 * if the model is an array). This means that changing a property deeper than the first level inside the 32250 * object/collection will not trigger a re-rendering. 32251 * 32252 * ## `select` **`as`** 32253 * 32254 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 32255 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 32256 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 32257 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 32258 * 32259 * 32260 * ### `select` **`as`** and **`track by`** 32261 * 32262 * <div class="alert alert-warning"> 32263 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 32264 * </div> 32265 * 32266 * Given this array of items on the $scope: 32267 * 32268 * ```js 32269 * $scope.items = [{ 32270 * id: 1, 32271 * label: 'aLabel', 32272 * subItem: { name: 'aSubItem' } 32273 * }, { 32274 * id: 2, 32275 * label: 'bLabel', 32276 * subItem: { name: 'bSubItem' } 32277 * }]; 32278 * ``` 32279 * 32280 * This will work: 32281 * 32282 * ```html 32283 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 32284 * ``` 32285 * ```js 32286 * $scope.selected = $scope.items[0]; 32287 * ``` 32288 * 32289 * but this will not work: 32290 * 32291 * ```html 32292 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 32293 * ``` 32294 * ```js 32295 * $scope.selected = $scope.items[0].subItem; 32296 * ``` 32297 * 32298 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 32299 * `items` array. Because the selected option has been set programmatically in the controller, the 32300 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 32301 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 32302 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 32303 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 32304 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 32305 * 32306 * 32307 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 32308 * @param {comprehension_expression} ngOptions in one of the following forms: 32309 * 32310 * * for array data sources: 32311 * * `label` **`for`** `value` **`in`** `array` 32312 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 32313 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 32314 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 32315 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 32316 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 32317 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 32318 * (for including a filter with `track by`) 32319 * * for object data sources: 32320 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 32321 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 32322 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 32323 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 32324 * * `select` **`as`** `label` **`group by`** `group` 32325 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 32326 * * `select` **`as`** `label` **`disable when`** `disable` 32327 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 32328 * 32329 * Where: 32330 * 32331 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 32332 * * `value`: local variable which will refer to each item in the `array` or each property value 32333 * of `object` during iteration. 32334 * * `key`: local variable which will refer to a property name in `object` during iteration. 32335 * * `label`: The result of this expression will be the label for `<option>` element. The 32336 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 32337 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 32338 * element. If not specified, `select` expression will default to `value`. 32339 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 32340 * DOM element. 32341 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 32342 * element. Return `true` to disable. 32343 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 32344 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 32345 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 32346 * even when the options are recreated (e.g. reloaded from the server). 32347 * @param {string=} name Property name of the form under which the control is published. 32348 * @param {string=} required The control is considered valid only if value is entered. 32349 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 32350 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 32351 * `required` when you want to data-bind to the `required` attribute. 32352 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the 32353 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive. 32354 * 32355 * @example 32356 <example module="selectExample" name="select"> 32357 <file name="index.html"> 32358 <script> 32359 angular.module('selectExample', []) 32360 .controller('ExampleController', ['$scope', function($scope) { 32361 $scope.colors = [ 32362 {name:'black', shade:'dark'}, 32363 {name:'white', shade:'light', notAnOption: true}, 32364 {name:'red', shade:'dark'}, 32365 {name:'blue', shade:'dark', notAnOption: true}, 32366 {name:'yellow', shade:'light', notAnOption: false} 32367 ]; 32368 $scope.myColor = $scope.colors[2]; // red 32369 }]); 32370 </script> 32371 <div ng-controller="ExampleController"> 32372 <ul> 32373 <li ng-repeat="color in colors"> 32374 <label>Name: <input ng-model="color.name"></label> 32375 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 32376 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 32377 </li> 32378 <li> 32379 <button ng-click="colors.push({})">add</button> 32380 </li> 32381 </ul> 32382 <hr/> 32383 <label>Color (null not allowed): 32384 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 32385 </label><br/> 32386 <label>Color (null allowed): 32387 <span class="nullable"> 32388 <select ng-model="myColor" ng-options="color.name for color in colors"> 32389 <option value="">-- choose color --</option> 32390 </select> 32391 </span></label><br/> 32392 32393 <label>Color grouped by shade: 32394 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 32395 </select> 32396 </label><br/> 32397 32398 <label>Color grouped by shade, with some disabled: 32399 <select ng-model="myColor" 32400 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 32401 </select> 32402 </label><br/> 32403 32404 32405 32406 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 32407 <br/> 32408 <hr/> 32409 Currently selected: {{ {selected_color:myColor} }} 32410 <div style="border:solid 1px black; height:20px" 32411 ng-style="{'background-color':myColor.name}"> 32412 </div> 32413 </div> 32414 </file> 32415 <file name="protractor.js" type="protractor"> 32416 it('should check ng-options', function() { 32417 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 32418 element.all(by.model('myColor')).first().click(); 32419 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 32420 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 32421 element(by.css('.nullable select[ng-model="myColor"]')).click(); 32422 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 32423 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 32424 }); 32425 </file> 32426 </example> 32427 */ 32428 32429/* eslint-disable max-len */ 32430// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555000000000666666666666600000007777777777777000000000000000888888888800000000000000000009999999999 32431var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/; 32432 // 1: value expression (valueFn) 32433 // 2: label expression (displayFn) 32434 // 3: group by expression (groupByFn) 32435 // 4: disable when expression (disableWhenFn) 32436 // 5: array item variable name 32437 // 6: object item key variable name 32438 // 7: object item value variable name 32439 // 8: collection expression 32440 // 9: track by expression 32441/* eslint-enable */ 32442 32443 32444var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) { 32445 32446 function parseOptionsExpression(optionsExp, selectElement, scope) { 32447 32448 var match = optionsExp.match(NG_OPTIONS_REGEXP); 32449 if (!(match)) { 32450 throw ngOptionsMinErr('iexp', 32451 'Expected expression in form of ' + 32452 '\'_select_ (as _label_)? for (_key_,)?_value_ in _collection_\'' + 32453 ' but got \'{0}\'. Element: {1}', 32454 optionsExp, startingTag(selectElement)); 32455 } 32456 32457 // Extract the parts from the ngOptions expression 32458 32459 // The variable name for the value of the item in the collection 32460 var valueName = match[5] || match[7]; 32461 // The variable name for the key of the item in the collection 32462 var keyName = match[6]; 32463 32464 // An expression that generates the viewValue for an option if there is a label expression 32465 var selectAs = / as /.test(match[0]) && match[1]; 32466 // An expression that is used to track the id of each object in the options collection 32467 var trackBy = match[9]; 32468 // An expression that generates the viewValue for an option if there is no label expression 32469 var valueFn = $parse(match[2] ? match[1] : valueName); 32470 var selectAsFn = selectAs && $parse(selectAs); 32471 var viewValueFn = selectAsFn || valueFn; 32472 var trackByFn = trackBy && $parse(trackBy); 32473 32474 // Get the value by which we are going to track the option 32475 // if we have a trackFn then use that (passing scope and locals) 32476 // otherwise just hash the given viewValue 32477 var getTrackByValueFn = trackBy ? 32478 function(value, locals) { return trackByFn(scope, locals); } : 32479 function getHashOfValue(value) { return hashKey(value); }; 32480 var getTrackByValue = function(value, key) { 32481 return getTrackByValueFn(value, getLocals(value, key)); 32482 }; 32483 32484 var displayFn = $parse(match[2] || match[1]); 32485 var groupByFn = $parse(match[3] || ''); 32486 var disableWhenFn = $parse(match[4] || ''); 32487 var valuesFn = $parse(match[8]); 32488 32489 var locals = {}; 32490 var getLocals = keyName ? function(value, key) { 32491 locals[keyName] = key; 32492 locals[valueName] = value; 32493 return locals; 32494 } : function(value) { 32495 locals[valueName] = value; 32496 return locals; 32497 }; 32498 32499 32500 function Option(selectValue, viewValue, label, group, disabled) { 32501 this.selectValue = selectValue; 32502 this.viewValue = viewValue; 32503 this.label = label; 32504 this.group = group; 32505 this.disabled = disabled; 32506 } 32507 32508 function getOptionValuesKeys(optionValues) { 32509 var optionValuesKeys; 32510 32511 if (!keyName && isArrayLike(optionValues)) { 32512 optionValuesKeys = optionValues; 32513 } else { 32514 // if object, extract keys, in enumeration order, unsorted 32515 optionValuesKeys = []; 32516 for (var itemKey in optionValues) { 32517 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 32518 optionValuesKeys.push(itemKey); 32519 } 32520 } 32521 } 32522 return optionValuesKeys; 32523 } 32524 32525 return { 32526 trackBy: trackBy, 32527 getTrackByValue: getTrackByValue, 32528 getWatchables: $parse(valuesFn, function(optionValues) { 32529 // Create a collection of things that we would like to watch (watchedArray) 32530 // so that they can all be watched using a single $watchCollection 32531 // that only runs the handler once if anything changes 32532 var watchedArray = []; 32533 optionValues = optionValues || []; 32534 32535 var optionValuesKeys = getOptionValuesKeys(optionValues); 32536 var optionValuesLength = optionValuesKeys.length; 32537 for (var index = 0; index < optionValuesLength; index++) { 32538 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 32539 var value = optionValues[key]; 32540 32541 var locals = getLocals(value, key); 32542 var selectValue = getTrackByValueFn(value, locals); 32543 watchedArray.push(selectValue); 32544 32545 // Only need to watch the displayFn if there is a specific label expression 32546 if (match[2] || match[1]) { 32547 var label = displayFn(scope, locals); 32548 watchedArray.push(label); 32549 } 32550 32551 // Only need to watch the disableWhenFn if there is a specific disable expression 32552 if (match[4]) { 32553 var disableWhen = disableWhenFn(scope, locals); 32554 watchedArray.push(disableWhen); 32555 } 32556 } 32557 return watchedArray; 32558 }), 32559 32560 getOptions: function() { 32561 32562 var optionItems = []; 32563 var selectValueMap = {}; 32564 32565 // The option values were already computed in the `getWatchables` fn, 32566 // which must have been called to trigger `getOptions` 32567 var optionValues = valuesFn(scope) || []; 32568 var optionValuesKeys = getOptionValuesKeys(optionValues); 32569 var optionValuesLength = optionValuesKeys.length; 32570 32571 for (var index = 0; index < optionValuesLength; index++) { 32572 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 32573 var value = optionValues[key]; 32574 var locals = getLocals(value, key); 32575 var viewValue = viewValueFn(scope, locals); 32576 var selectValue = getTrackByValueFn(viewValue, locals); 32577 var label = displayFn(scope, locals); 32578 var group = groupByFn(scope, locals); 32579 var disabled = disableWhenFn(scope, locals); 32580 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 32581 32582 optionItems.push(optionItem); 32583 selectValueMap[selectValue] = optionItem; 32584 } 32585 32586 return { 32587 items: optionItems, 32588 selectValueMap: selectValueMap, 32589 getOptionFromViewValue: function(value) { 32590 return selectValueMap[getTrackByValue(value)]; 32591 }, 32592 getViewValueFromOption: function(option) { 32593 // If the viewValue could be an object that may be mutated by the application, 32594 // we need to make a copy and not return the reference to the value on the option. 32595 return trackBy ? copy(option.viewValue) : option.viewValue; 32596 } 32597 }; 32598 } 32599 }; 32600 } 32601 32602 32603 // Support: IE 9 only 32604 // We can't just jqLite('<option>') since jqLite is not smart enough 32605 // to create it in <select> and IE barfs otherwise. 32606 var optionTemplate = window.document.createElement('option'), 32607 optGroupTemplate = window.document.createElement('optgroup'); 32608 32609 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 32610 32611 var selectCtrl = ctrls[0]; 32612 var ngModelCtrl = ctrls[1]; 32613 var multiple = attr.multiple; 32614 32615 // The emptyOption allows the application developer to provide their own custom "empty" 32616 // option when the viewValue does not match any of the option values. 32617 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 32618 if (children[i].value === '') { 32619 selectCtrl.hasEmptyOption = true; 32620 selectCtrl.emptyOption = children.eq(i); 32621 break; 32622 } 32623 } 32624 32625 // The empty option will be compiled and rendered before we first generate the options 32626 selectElement.empty(); 32627 32628 var providedEmptyOption = !!selectCtrl.emptyOption; 32629 32630 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 32631 unknownOption.val('?'); 32632 32633 var options; 32634 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 32635 // This stores the newly created options before they are appended to the select. 32636 // Since the contents are removed from the fragment when it is appended, 32637 // we only need to create it once. 32638 var listFragment = $document[0].createDocumentFragment(); 32639 32640 // Overwrite the implementation. ngOptions doesn't use hashes 32641 selectCtrl.generateUnknownOptionValue = function(val) { 32642 return '?'; 32643 }; 32644 32645 // Update the controller methods for multiple selectable options 32646 if (!multiple) { 32647 32648 selectCtrl.writeValue = function writeNgOptionsValue(value) { 32649 // The options might not be defined yet when ngModel tries to render 32650 if (!options) return; 32651 32652 var selectedOption = selectElement[0].options[selectElement[0].selectedIndex]; 32653 var option = options.getOptionFromViewValue(value); 32654 32655 // Make sure to remove the selected attribute from the previously selected option 32656 // Otherwise, screen readers might get confused 32657 if (selectedOption) selectedOption.removeAttribute('selected'); 32658 32659 if (option) { 32660 // Don't update the option when it is already selected. 32661 // For example, the browser will select the first option by default. In that case, 32662 // most properties are set automatically - except the `selected` attribute, which we 32663 // set always 32664 32665 if (selectElement[0].value !== option.selectValue) { 32666 selectCtrl.removeUnknownOption(); 32667 32668 selectElement[0].value = option.selectValue; 32669 option.element.selected = true; 32670 } 32671 32672 option.element.setAttribute('selected', 'selected'); 32673 } else { 32674 selectCtrl.selectUnknownOrEmptyOption(value); 32675 } 32676 }; 32677 32678 selectCtrl.readValue = function readNgOptionsValue() { 32679 32680 var selectedOption = options.selectValueMap[selectElement.val()]; 32681 32682 if (selectedOption && !selectedOption.disabled) { 32683 selectCtrl.unselectEmptyOption(); 32684 selectCtrl.removeUnknownOption(); 32685 return options.getViewValueFromOption(selectedOption); 32686 } 32687 return null; 32688 }; 32689 32690 // If we are using `track by` then we must watch the tracked value on the model 32691 // since ngModel only watches for object identity change 32692 // FIXME: When a user selects an option, this watch will fire needlessly 32693 if (ngOptions.trackBy) { 32694 scope.$watch( 32695 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 32696 function() { ngModelCtrl.$render(); } 32697 ); 32698 } 32699 32700 } else { 32701 32702 selectCtrl.writeValue = function writeNgOptionsMultiple(values) { 32703 // The options might not be defined yet when ngModel tries to render 32704 if (!options) return; 32705 32706 // Only set `<option>.selected` if necessary, in order to prevent some browsers from 32707 // scrolling to `<option>` elements that are outside the `<select>` element's viewport. 32708 var selectedOptions = values && values.map(getAndUpdateSelectedOption) || []; 32709 32710 options.items.forEach(function(option) { 32711 if (option.element.selected && !includes(selectedOptions, option)) { 32712 option.element.selected = false; 32713 } 32714 }); 32715 }; 32716 32717 32718 selectCtrl.readValue = function readNgOptionsMultiple() { 32719 var selectedValues = selectElement.val() || [], 32720 selections = []; 32721 32722 forEach(selectedValues, function(value) { 32723 var option = options.selectValueMap[value]; 32724 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 32725 }); 32726 32727 return selections; 32728 }; 32729 32730 // If we are using `track by` then we must watch these tracked values on the model 32731 // since ngModel only watches for object identity change 32732 if (ngOptions.trackBy) { 32733 32734 scope.$watchCollection(function() { 32735 if (isArray(ngModelCtrl.$viewValue)) { 32736 return ngModelCtrl.$viewValue.map(function(value) { 32737 return ngOptions.getTrackByValue(value); 32738 }); 32739 } 32740 }, function() { 32741 ngModelCtrl.$render(); 32742 }); 32743 32744 } 32745 } 32746 32747 if (providedEmptyOption) { 32748 32749 // compile the element since there might be bindings in it 32750 $compile(selectCtrl.emptyOption)(scope); 32751 32752 selectElement.prepend(selectCtrl.emptyOption); 32753 32754 if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) { 32755 // This means the empty option has currently no actual DOM node, probably because 32756 // it has been modified by a transclusion directive. 32757 selectCtrl.hasEmptyOption = false; 32758 32759 // Redefine the registerOption function, which will catch 32760 // options that are added by ngIf etc. (rendering of the node is async because of 32761 // lazy transclusion) 32762 selectCtrl.registerOption = function(optionScope, optionEl) { 32763 if (optionEl.val() === '') { 32764 selectCtrl.hasEmptyOption = true; 32765 selectCtrl.emptyOption = optionEl; 32766 selectCtrl.emptyOption.removeClass('ng-scope'); 32767 // This ensures the new empty option is selected if previously no option was selected 32768 ngModelCtrl.$render(); 32769 32770 optionEl.on('$destroy', function() { 32771 var needsRerender = selectCtrl.$isEmptyOptionSelected(); 32772 32773 selectCtrl.hasEmptyOption = false; 32774 selectCtrl.emptyOption = undefined; 32775 32776 if (needsRerender) ngModelCtrl.$render(); 32777 }); 32778 } 32779 }; 32780 32781 } else { 32782 // remove the class, which is added automatically because we recompile the element and it 32783 // becomes the compilation root 32784 selectCtrl.emptyOption.removeClass('ng-scope'); 32785 } 32786 32787 } 32788 32789 // We will re-render the option elements if the option values or labels change 32790 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 32791 32792 // ------------------------------------------------------------------ // 32793 32794 function addOptionElement(option, parent) { 32795 var optionElement = optionTemplate.cloneNode(false); 32796 parent.appendChild(optionElement); 32797 updateOptionElement(option, optionElement); 32798 } 32799 32800 function getAndUpdateSelectedOption(viewValue) { 32801 var option = options.getOptionFromViewValue(viewValue); 32802 var element = option && option.element; 32803 32804 if (element && !element.selected) element.selected = true; 32805 32806 return option; 32807 } 32808 32809 function updateOptionElement(option, element) { 32810 option.element = element; 32811 element.disabled = option.disabled; 32812 // Support: IE 11 only, Edge 12-13 only 32813 // NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive 32814 // selects in certain circumstances when multiple selects are next to each other and display 32815 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 32816 // See https://github.com/angular/angular.js/issues/11314 for more info. 32817 // This is unfortunately untestable with unit / e2e tests 32818 if (option.label !== element.label) { 32819 element.label = option.label; 32820 element.textContent = option.label; 32821 } 32822 element.value = option.selectValue; 32823 } 32824 32825 function updateOptions() { 32826 var previousValue = options && selectCtrl.readValue(); 32827 32828 // We must remove all current options, but cannot simply set innerHTML = null 32829 // since the providedEmptyOption might have an ngIf on it that inserts comments which we 32830 // must preserve. 32831 // Instead, iterate over the current option elements and remove them or their optgroup 32832 // parents 32833 if (options) { 32834 32835 for (var i = options.items.length - 1; i >= 0; i--) { 32836 var option = options.items[i]; 32837 if (isDefined(option.group)) { 32838 jqLiteRemove(option.element.parentNode); 32839 } else { 32840 jqLiteRemove(option.element); 32841 } 32842 } 32843 } 32844 32845 options = ngOptions.getOptions(); 32846 32847 var groupElementMap = {}; 32848 32849 options.items.forEach(function addOption(option) { 32850 var groupElement; 32851 32852 if (isDefined(option.group)) { 32853 32854 // This option is to live in a group 32855 // See if we have already created this group 32856 groupElement = groupElementMap[option.group]; 32857 32858 if (!groupElement) { 32859 32860 groupElement = optGroupTemplate.cloneNode(false); 32861 listFragment.appendChild(groupElement); 32862 32863 // Update the label on the group element 32864 // "null" is special cased because of Safari 32865 groupElement.label = option.group === null ? 'null' : option.group; 32866 32867 // Store it for use later 32868 groupElementMap[option.group] = groupElement; 32869 } 32870 32871 addOptionElement(option, groupElement); 32872 32873 } else { 32874 32875 // This option is not in a group 32876 addOptionElement(option, listFragment); 32877 } 32878 }); 32879 32880 selectElement[0].appendChild(listFragment); 32881 32882 ngModelCtrl.$render(); 32883 32884 // Check to see if the value has changed due to the update to the options 32885 if (!ngModelCtrl.$isEmpty(previousValue)) { 32886 var nextValue = selectCtrl.readValue(); 32887 var isNotPrimitive = ngOptions.trackBy || multiple; 32888 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 32889 ngModelCtrl.$setViewValue(nextValue); 32890 ngModelCtrl.$render(); 32891 } 32892 } 32893 } 32894 } 32895 32896 return { 32897 restrict: 'A', 32898 terminal: true, 32899 require: ['select', 'ngModel'], 32900 link: { 32901 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 32902 // Deactivate the SelectController.register method to prevent 32903 // option directives from accidentally registering themselves 32904 // (and unwanted $destroy handlers etc.) 32905 ctrls[0].registerOption = noop; 32906 }, 32907 post: ngOptionsPostLink 32908 } 32909 }; 32910}]; 32911 32912/** 32913 * @ngdoc directive 32914 * @name ngPluralize 32915 * @restrict EA 32916 * 32917 * @description 32918 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 32919 * These rules are bundled with angular.js, but can be overridden 32920 * (see {@link guide/i18n AngularJS i18n} dev guide). You configure ngPluralize directive 32921 * by specifying the mappings between 32922 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 32923 * and the strings to be displayed. 32924 * 32925 * ## Plural categories and explicit number rules 32926 * There are two 32927 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 32928 * in AngularJS's default en-US locale: "one" and "other". 32929 * 32930 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 32931 * any number that is not 1), an explicit number rule can only match one number. For example, the 32932 * explicit number rule for "3" matches the number 3. There are examples of plural categories 32933 * and explicit number rules throughout the rest of this documentation. 32934 * 32935 * ## Configuring ngPluralize 32936 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 32937 * You can also provide an optional attribute, `offset`. 32938 * 32939 * The value of the `count` attribute can be either a string or an {@link guide/expression 32940 * AngularJS expression}; these are evaluated on the current scope for its bound value. 32941 * 32942 * The `when` attribute specifies the mappings between plural categories and the actual 32943 * string to be displayed. The value of the attribute should be a JSON object. 32944 * 32945 * The following example shows how to configure ngPluralize: 32946 * 32947 * ```html 32948 * <ng-pluralize count="personCount" 32949 when="{'0': 'Nobody is viewing.', 32950 * 'one': '1 person is viewing.', 32951 * 'other': '{} people are viewing.'}"> 32952 * </ng-pluralize> 32953 *``` 32954 * 32955 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 32956 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 32957 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 32958 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 32959 * show "a dozen people are viewing". 32960 * 32961 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 32962 * into pluralized strings. In the previous example, AngularJS will replace `{}` with 32963 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 32964 * for <span ng-non-bindable>{{numberExpression}}</span>. 32965 * 32966 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 32967 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 32968 * 32969 * ## Configuring ngPluralize with offset 32970 * The `offset` attribute allows further customization of pluralized text, which can result in 32971 * a better user experience. For example, instead of the message "4 people are viewing this document", 32972 * you might display "John, Kate and 2 others are viewing this document". 32973 * The offset attribute allows you to offset a number by any desired value. 32974 * Let's take a look at an example: 32975 * 32976 * ```html 32977 * <ng-pluralize count="personCount" offset=2 32978 * when="{'0': 'Nobody is viewing.', 32979 * '1': '{{person1}} is viewing.', 32980 * '2': '{{person1}} and {{person2}} are viewing.', 32981 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 32982 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 32983 * </ng-pluralize> 32984 * ``` 32985 * 32986 * Notice that we are still using two plural categories(one, other), but we added 32987 * three explicit number rules 0, 1 and 2. 32988 * When one person, perhaps John, views the document, "John is viewing" will be shown. 32989 * When three people view the document, no explicit number rule is found, so 32990 * an offset of 2 is taken off 3, and AngularJS uses 1 to decide the plural category. 32991 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 32992 * is shown. 32993 * 32994 * Note that when you specify offsets, you must provide explicit number rules for 32995 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 32996 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 32997 * plural categories "one" and "other". 32998 * 32999 * @param {string|expression} count The variable to be bound to. 33000 * @param {string} when The mapping between plural category to its corresponding strings. 33001 * @param {number=} offset Offset to deduct from the total number. 33002 * 33003 * @example 33004 <example module="pluralizeExample" name="ng-pluralize"> 33005 <file name="index.html"> 33006 <script> 33007 angular.module('pluralizeExample', []) 33008 .controller('ExampleController', ['$scope', function($scope) { 33009 $scope.person1 = 'Igor'; 33010 $scope.person2 = 'Misko'; 33011 $scope.personCount = 1; 33012 }]); 33013 </script> 33014 <div ng-controller="ExampleController"> 33015 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 33016 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 33017 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 33018 33019 <!--- Example with simple pluralization rules for en locale ---> 33020 Without Offset: 33021 <ng-pluralize count="personCount" 33022 when="{'0': 'Nobody is viewing.', 33023 'one': '1 person is viewing.', 33024 'other': '{} people are viewing.'}"> 33025 </ng-pluralize><br> 33026 33027 <!--- Example with offset ---> 33028 With Offset(2): 33029 <ng-pluralize count="personCount" offset=2 33030 when="{'0': 'Nobody is viewing.', 33031 '1': '{{person1}} is viewing.', 33032 '2': '{{person1}} and {{person2}} are viewing.', 33033 'one': '{{person1}}, {{person2}} and one other person are viewing.', 33034 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 33035 </ng-pluralize> 33036 </div> 33037 </file> 33038 <file name="protractor.js" type="protractor"> 33039 it('should show correct pluralized string', function() { 33040 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 33041 var withOffset = element.all(by.css('ng-pluralize')).get(1); 33042 var countInput = element(by.model('personCount')); 33043 33044 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 33045 expect(withOffset.getText()).toEqual('Igor is viewing.'); 33046 33047 countInput.clear(); 33048 countInput.sendKeys('0'); 33049 33050 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 33051 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 33052 33053 countInput.clear(); 33054 countInput.sendKeys('2'); 33055 33056 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 33057 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 33058 33059 countInput.clear(); 33060 countInput.sendKeys('3'); 33061 33062 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 33063 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 33064 33065 countInput.clear(); 33066 countInput.sendKeys('4'); 33067 33068 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 33069 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 33070 }); 33071 it('should show data-bound names', function() { 33072 var withOffset = element.all(by.css('ng-pluralize')).get(1); 33073 var personCount = element(by.model('personCount')); 33074 var person1 = element(by.model('person1')); 33075 var person2 = element(by.model('person2')); 33076 personCount.clear(); 33077 personCount.sendKeys('4'); 33078 person1.clear(); 33079 person1.sendKeys('Di'); 33080 person2.clear(); 33081 person2.sendKeys('Vojta'); 33082 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 33083 }); 33084 </file> 33085 </example> 33086 */ 33087var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 33088 var BRACE = /{}/g, 33089 IS_WHEN = /^when(Minus)?(.+)$/; 33090 33091 return { 33092 link: function(scope, element, attr) { 33093 var numberExp = attr.count, 33094 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 33095 offset = attr.offset || 0, 33096 whens = scope.$eval(whenExp) || {}, 33097 whensExpFns = {}, 33098 startSymbol = $interpolate.startSymbol(), 33099 endSymbol = $interpolate.endSymbol(), 33100 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 33101 watchRemover = angular.noop, 33102 lastCount; 33103 33104 forEach(attr, function(expression, attributeName) { 33105 var tmpMatch = IS_WHEN.exec(attributeName); 33106 if (tmpMatch) { 33107 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 33108 whens[whenKey] = element.attr(attr.$attr[attributeName]); 33109 } 33110 }); 33111 forEach(whens, function(expression, key) { 33112 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 33113 33114 }); 33115 33116 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 33117 var count = parseFloat(newVal); 33118 var countIsNaN = isNumberNaN(count); 33119 33120 if (!countIsNaN && !(count in whens)) { 33121 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 33122 // Otherwise, check it against pluralization rules in $locale service. 33123 count = $locale.pluralCat(count - offset); 33124 } 33125 33126 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 33127 // In JS `NaN !== NaN`, so we have to explicitly check. 33128 if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) { 33129 watchRemover(); 33130 var whenExpFn = whensExpFns[count]; 33131 if (isUndefined(whenExpFn)) { 33132 if (newVal != null) { 33133 $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp); 33134 } 33135 watchRemover = noop; 33136 updateElementText(); 33137 } else { 33138 watchRemover = scope.$watch(whenExpFn, updateElementText); 33139 } 33140 lastCount = count; 33141 } 33142 }); 33143 33144 function updateElementText(newText) { 33145 element.text(newText || ''); 33146 } 33147 } 33148 }; 33149}]; 33150 33151/** 33152 * @ngdoc directive 33153 * @name ngRef 33154 * @restrict A 33155 * 33156 * @description 33157 * The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive) 33158 * to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM 33159 * element to the scope. 33160 * 33161 * If the element with `ngRef` is destroyed `null` is assigned to the property. 33162 * 33163 * Note that if you want to assign from a child into the parent scope, you must initialize the 33164 * target property on the parent scope, otherwise `ngRef` will assign on the child scope. 33165 * This commonly happens when assigning elements or components wrapped in {@link ngIf} or 33166 * {@link ngRepeat}. See the second example below. 33167 * 33168 * 33169 * @element ANY 33170 * @param {string} ngRef property name - A valid AngularJS expression identifier to which the 33171 * controller or jqlite-wrapped DOM element will be bound. 33172 * @param {string=} ngRefRead read value - The name of a directive (or component) on this element, 33173 * or the special string `$element`. If a name is provided, `ngRef` will 33174 * assign the matching controller. If `$element` is provided, the element 33175 * itself is assigned (even if a controller is available). 33176 * 33177 * 33178 * @example 33179 * ### Simple toggle 33180 * This example shows how the controller of the component toggle 33181 * is reused in the template through the scope to use its logic. 33182 * <example name="ng-ref-component" module="myApp"> 33183 * <file name="index.html"> 33184 * <my-toggle ng-ref="myToggle"></my-toggle> 33185 * <button ng-click="myToggle.toggle()">Toggle</button> 33186 * <div ng-show="myToggle.isOpen()"> 33187 * You are using a component in the same template to show it. 33188 * </div> 33189 * </file> 33190 * <file name="index.js"> 33191 * angular.module('myApp', []) 33192 * .component('myToggle', { 33193 * controller: function ToggleController() { 33194 * var opened = false; 33195 * this.isOpen = function() { return opened; }; 33196 * this.toggle = function() { opened = !opened; }; 33197 * } 33198 * }); 33199 * </file> 33200 * <file name="protractor.js" type="protractor"> 33201 * it('should publish the toggle into the scope', function() { 33202 * var toggle = element(by.buttonText('Toggle')); 33203 * expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false); 33204 * toggle.click(); 33205 * expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true); 33206 * }); 33207 * </file> 33208 * </example> 33209 * 33210 * @example 33211 * ### ngRef inside scopes 33212 * This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components 33213 * are assigned to the scope of `myRoot`, because the `toggles` property has been initialized. 33214 * The repeated `myToggle` components are published to the child scopes created by `ngRepeat`. 33215 * `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope, 33216 * because the target property has not been initialized on the `myRoot` component controller. 33217 * 33218 * <example name="ng-ref-scopes" module="myApp"> 33219 * <file name="index.html"> 33220 * <my-root></my-root> 33221 * </file> 33222 * <file name="index.js"> 33223 * angular.module('myApp', []) 33224 * .component('myRoot', { 33225 * templateUrl: 'root.html', 33226 * controller: function() { 33227 * this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope 33228 * } 33229 * }) 33230 * .component('myToggle', { 33231 * template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>', 33232 * transclude: true, 33233 * controller: function ToggleController() { 33234 * var opened = false; 33235 * this.isOpen = function() { return opened; }; 33236 * this.toggle = function() { opened = !opened; }; 33237 * } 33238 * }) 33239 * .component('myWrapper', { 33240 * transclude: true, 33241 * template: '<strong>myWrapper</strong>' + 33242 * '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' + 33243 * '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>' 33244 * }); 33245 * </file> 33246 * <file name="root.html"> 33247 * <strong>myRoot</strong> 33248 * <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle> 33249 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div> 33250 * <div><em>wrappers assigned to root</em><br> 33251 * <div ng-repeat="wrapper in $ctrl.wrappers"> 33252 * wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}} 33253 * </div> 33254 * 33255 * <ul> 33256 * <li ng-repeat="(index, value) in [1,2,3]"> 33257 * <strong>ngRepeat</strong> 33258 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div> 33259 * <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper> 33260 * </li> 33261 * </ul> 33262 * 33263 * <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's 33264 * assigned to the child scope created by ngIf. 33265 * </div> 33266 * <div ng-if="true"> 33267 <strong>ngIf</strong> 33268 * <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle> 33269 * <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div> 33270 * <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div> 33271 * </div> 33272 * </file> 33273 * <file name="styles.css"> 33274 * ul { 33275 * list-style: none; 33276 * padding-left: 0; 33277 * } 33278 * 33279 * li[ng-repeat] { 33280 * background: lightgreen; 33281 * padding: 8px; 33282 * margin: 8px; 33283 * } 33284 * 33285 * [ng-if] { 33286 * background: lightgrey; 33287 * padding: 8px; 33288 * } 33289 * 33290 * my-root { 33291 * background: lightgoldenrodyellow; 33292 * padding: 8px; 33293 * display: block; 33294 * } 33295 * 33296 * my-wrapper { 33297 * background: lightsalmon; 33298 * padding: 8px; 33299 * display: block; 33300 * } 33301 * 33302 * my-toggle { 33303 * background: lightblue; 33304 * padding: 8px; 33305 * display: block; 33306 * } 33307 * </file> 33308 * <file name="protractor.js" type="protractor"> 33309 * var OuterToggle = function() { 33310 * this.toggle = function() { 33311 * element(by.buttonText('Outer Toggle')).click(); 33312 * }; 33313 * this.isOpen = function() { 33314 * return element.all(by.binding('outerToggle.isOpen()')).first().getText(); 33315 * }; 33316 * }; 33317 * var NgRepeatToggle = function(i) { 33318 * var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1); 33319 * this.toggle = function() { 33320 * element(by.buttonText('ngRepeat Toggle ' + i)).click(); 33321 * }; 33322 * this.isOpen = function() { 33323 * return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText(); 33324 * }; 33325 * this.isOuterOpen = function() { 33326 * return parent.element(by.binding('outerToggle.isOpen() | json')).getText(); 33327 * }; 33328 * }; 33329 * var NgRepeatToggles = function() { 33330 * var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); }); 33331 * this.forEach = function(fn) { 33332 * toggles.forEach(fn); 33333 * }; 33334 * this.isOuterOpen = function(i) { 33335 * return toggles[i - 1].isOuterOpen(); 33336 * }; 33337 * }; 33338 * var NgIfToggle = function() { 33339 * var parent = element(by.css('[ng-if]')); 33340 * this.toggle = function() { 33341 * element(by.buttonText('ngIf Toggle')).click(); 33342 * }; 33343 * this.isOpen = function() { 33344 * return by.binding('ngIfToggle.isOpen() | json').getText(); 33345 * }; 33346 * this.isOuterOpen = function() { 33347 * return parent.element(by.binding('outerToggle.isOpen() | json')).getText(); 33348 * }; 33349 * }; 33350 * 33351 * it('should toggle the outer toggle', function() { 33352 * var outerToggle = new OuterToggle(); 33353 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false'); 33354 * outerToggle.toggle(); 33355 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true'); 33356 * }); 33357 * 33358 * it('should toggle all outer toggles', function() { 33359 * var outerToggle = new OuterToggle(); 33360 * var repeatToggles = new NgRepeatToggles(); 33361 * var ifToggle = new NgIfToggle(); 33362 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false'); 33363 * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false'); 33364 * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false'); 33365 * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false'); 33366 * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false'); 33367 * outerToggle.toggle(); 33368 * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true'); 33369 * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true'); 33370 * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true'); 33371 * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true'); 33372 * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true'); 33373 * }); 33374 * 33375 * it('should toggle each repeat iteration separately', function() { 33376 * var repeatToggles = new NgRepeatToggles(); 33377 * 33378 * repeatToggles.forEach(function(repeatToggle) { 33379 * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false'); 33380 * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false'); 33381 * repeatToggle.toggle(); 33382 * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true'); 33383 * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false'); 33384 * }); 33385 * }); 33386 * </file> 33387 * </example> 33388 * 33389 */ 33390 33391var ngRefMinErr = minErr('ngRef'); 33392 33393var ngRefDirective = ['$parse', function($parse) { 33394 return { 33395 priority: -1, // Needed for compatibility with element transclusion on the same element 33396 restrict: 'A', 33397 compile: function(tElement, tAttrs) { 33398 // Get the expected controller name, converts <data-some-thing> into "someThing" 33399 var controllerName = directiveNormalize(nodeName_(tElement)); 33400 33401 // Get the expression for value binding 33402 var getter = $parse(tAttrs.ngRef); 33403 var setter = getter.assign || function() { 33404 throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef); 33405 }; 33406 33407 return function(scope, element, attrs) { 33408 var refValue; 33409 33410 if (attrs.hasOwnProperty('ngRefRead')) { 33411 if (attrs.ngRefRead === '$element') { 33412 refValue = element; 33413 } else { 33414 refValue = element.data('$' + attrs.ngRefRead + 'Controller'); 33415 33416 if (!refValue) { 33417 throw ngRefMinErr( 33418 'noctrl', 33419 'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"', 33420 attrs.ngRefRead, 33421 tAttrs.ngRef 33422 ); 33423 } 33424 } 33425 } else { 33426 refValue = element.data('$' + controllerName + 'Controller'); 33427 } 33428 33429 refValue = refValue || element; 33430 33431 setter(scope, refValue); 33432 33433 // when the element is removed, remove it (nullify it) 33434 element.on('$destroy', function() { 33435 // only remove it if value has not changed, 33436 // because animations (and other procedures) may duplicate elements 33437 if (getter(scope) === refValue) { 33438 setter(scope, null); 33439 } 33440 }); 33441 }; 33442 } 33443 }; 33444}]; 33445 33446/* exported ngRepeatDirective */ 33447 33448/** 33449 * @ngdoc directive 33450 * @name ngRepeat 33451 * @multiElement 33452 * @restrict A 33453 * 33454 * @description 33455 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 33456 * instance gets its own scope, where the given loop variable is set to the current collection item, 33457 * and `$index` is set to the item index or key. 33458 * 33459 * Special properties are exposed on the local scope of each template instance, including: 33460 * 33461 * | Variable | Type | Details | 33462 * |-----------|-----------------|-----------------------------------------------------------------------------| 33463 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 33464 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 33465 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 33466 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 33467 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 33468 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 33469 * 33470 * <div class="alert alert-info"> 33471 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 33472 * This may be useful when, for instance, nesting ngRepeats. 33473 * </div> 33474 * 33475 * 33476 * ## Iterating over object properties 33477 * 33478 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 33479 * syntax: 33480 * 33481 * ```js 33482 * <div ng-repeat="(key, value) in myObj"> ... </div> 33483 * ``` 33484 * 33485 * However, there are a few limitations compared to array iteration: 33486 * 33487 * - The JavaScript specification does not define the order of keys 33488 * returned for an object, so AngularJS relies on the order returned by the browser 33489 * when running `for key in myObj`. Browsers generally follow the strategy of providing 33490 * keys in the order in which they were defined, although there are exceptions when keys are deleted 33491 * and reinstated. See the 33492 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 33493 * 33494 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because 33495 * it's a prefix used by AngularJS for public (`$`) and private (`$$`) properties. 33496 * 33497 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with 33498 * objects, and will throw an error if used with one. 33499 * 33500 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array 33501 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 33502 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 33503 * or implement a `$watch` on the object yourself. 33504 * 33505 * 33506 * ## Tracking and Duplicates 33507 * 33508 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 33509 * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM: 33510 * 33511 * * When an item is added, a new instance of the template is added to the DOM. 33512 * * When an item is removed, its template instance is removed from the DOM. 33513 * * When items are reordered, their respective templates are reordered in the DOM. 33514 * 33515 * To minimize creation of DOM elements, `ngRepeat` uses a function 33516 * to "keep track" of all items in the collection and their corresponding DOM elements. 33517 * For example, if an item is added to the collection, `ngRepeat` will know that all other items 33518 * already have DOM elements, and will not re-render them. 33519 * 33520 * All different types of tracking functions, their syntax, and their support for duplicate 33521 * items in collections can be found in the 33522 * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}. 33523 * 33524 * <div class="alert alert-success"> 33525 * **Best Practice:** If you are working with objects that have a unique identifier property, you 33526 * should track by this identifier instead of the object instance, 33527 * e.g. `item in items track by item.id`. 33528 * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items 33529 * it has already rendered, even if the JavaScript objects in the collection have been substituted 33530 * for new ones. For large collections, this significantly improves rendering performance. 33531 * </div> 33532 * 33533 * ### Effects of DOM Element re-use 33534 * 33535 * When DOM elements are re-used, ngRepeat updates the scope for the element, which will 33536 * automatically update any active bindings on the template. However, other 33537 * functionality will not be updated, because the element is not re-created: 33538 * 33539 * - Directives are not re-compiled 33540 * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not 33541 * updated if they have stabilized. 33542 * 33543 * The above affects all kinds of element re-use due to tracking, but may be especially visible 33544 * when tracking by `$index` due to the way ngRepeat re-uses elements. 33545 * 33546 * The following example shows the effects of different actions with tracking: 33547 33548 <example module="ngRepeat" name="ngRepeat-tracking" deps="angular-animate.js" animations="true"> 33549 <file name="script.js"> 33550 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 33551 var friends = [ 33552 {name:'John', age:25}, 33553 {name:'Mary', age:40}, 33554 {name:'Peter', age:85} 33555 ]; 33556 33557 $scope.removeFirst = function() { 33558 $scope.friends.shift(); 33559 }; 33560 33561 $scope.updateAge = function() { 33562 $scope.friends.forEach(function(el) { 33563 el.age = el.age + 5; 33564 }); 33565 }; 33566 33567 $scope.copy = function() { 33568 $scope.friends = angular.copy($scope.friends); 33569 }; 33570 33571 $scope.reset = function() { 33572 $scope.friends = angular.copy(friends); 33573 }; 33574 33575 $scope.reset(); 33576 }); 33577 </file> 33578 <file name="index.html"> 33579 <div ng-controller="repeatController"> 33580 <ol> 33581 <li>When you click "Update Age", only the first list updates the age, because all others have 33582 a one-time binding on the age property. If you then click "Copy", the current friend list 33583 is copied, and now the second list updates the age, because the identity of the collection items 33584 has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the 33585 items are already known according to their tracking functions. 33586 </li> 33587 <li>When you click "Remove First", the 4th list has the wrong age on both remaining items. This is 33588 due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first 33589 DOM element for the new first collection item, and so on. Since the age property is one-time 33590 bound, the value remains from the collection item which was previously at this index. 33591 </li> 33592 </ol> 33593 33594 <button ng-click="removeFirst()">Remove First</button> 33595 <button ng-click="updateAge()">Update Age</button> 33596 <button ng-click="copy()">Copy</button> 33597 <br><button ng-click="reset()">Reset List</button> 33598 <br> 33599 <code>track by $id(friend)</code> (default): 33600 <ul class="example-animate-container"> 33601 <li class="animate-repeat" ng-repeat="friend in friends"> 33602 {{friend.name}} is {{friend.age}} years old. 33603 </li> 33604 </ul> 33605 <code>track by $id(friend)</code> (default), with age one-time binding: 33606 <ul class="example-animate-container"> 33607 <li class="animate-repeat" ng-repeat="friend in friends"> 33608 {{friend.name}} is {{::friend.age}} years old. 33609 </li> 33610 </ul> 33611 <code>track by friend.name</code>, with age one-time binding: 33612 <ul class="example-animate-container"> 33613 <li class="animate-repeat" ng-repeat="friend in friends track by friend.name"> 33614 {{friend.name}} is {{::friend.age}} years old. 33615 </li> 33616 </ul> 33617 <code>track by $index</code>, with age one-time binding: 33618 <ul class="example-animate-container"> 33619 <li class="animate-repeat" ng-repeat="friend in friends track by $index"> 33620 {{friend.name}} is {{::friend.age}} years old. 33621 </li> 33622 </ul> 33623 </div> 33624 </file> 33625 <file name="animations.css"> 33626 .example-animate-container { 33627 background:white; 33628 border:1px solid black; 33629 list-style:none; 33630 margin:0; 33631 padding:0 10px; 33632 } 33633 33634 .animate-repeat { 33635 line-height:30px; 33636 list-style:none; 33637 box-sizing:border-box; 33638 } 33639 33640 .animate-repeat.ng-move, 33641 .animate-repeat.ng-enter, 33642 .animate-repeat.ng-leave { 33643 transition:all linear 0.5s; 33644 } 33645 33646 .animate-repeat.ng-leave.ng-leave-active, 33647 .animate-repeat.ng-move, 33648 .animate-repeat.ng-enter { 33649 opacity:0; 33650 max-height:0; 33651 } 33652 33653 .animate-repeat.ng-leave, 33654 .animate-repeat.ng-move.ng-move-active, 33655 .animate-repeat.ng-enter.ng-enter-active { 33656 opacity:1; 33657 max-height:30px; 33658 } 33659 </file> 33660 </example> 33661 33662 * 33663 * ## Special repeat start and end points 33664 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 33665 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 33666 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) 33667 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 33668 * 33669 * The example below makes use of this feature: 33670 * ```html 33671 * <header ng-repeat-start="item in items"> 33672 * Header {{ item }} 33673 * </header> 33674 * <div class="body"> 33675 * Body {{ item }} 33676 * </div> 33677 * <footer ng-repeat-end> 33678 * Footer {{ item }} 33679 * </footer> 33680 * ``` 33681 * 33682 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 33683 * ```html 33684 * <header> 33685 * Header A 33686 * </header> 33687 * <div class="body"> 33688 * Body A 33689 * </div> 33690 * <footer> 33691 * Footer A 33692 * </footer> 33693 * <header> 33694 * Header B 33695 * </header> 33696 * <div class="body"> 33697 * Body B 33698 * </div> 33699 * <footer> 33700 * Footer B 33701 * </footer> 33702 * ``` 33703 * 33704 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 33705 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 33706 * 33707 * @animations 33708 * | Animation | Occurs | 33709 * |----------------------------------|-------------------------------------| 33710 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | 33711 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | 33712 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | 33713 * 33714 * See the example below for defining CSS animations with ngRepeat. 33715 * 33716 * @element ANY 33717 * @scope 33718 * @priority 1000 33719 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 33720 * formats are currently supported: 33721 * 33722 * * `variable in expression` – where variable is the user defined loop variable and `expression` 33723 * is a scope expression giving the collection to enumerate. 33724 * 33725 * For example: `album in artist.albums`. 33726 * 33727 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, 33728 * and `expression` is the scope expression giving the collection to enumerate. 33729 * 33730 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 33731 * 33732 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression 33733 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 33734 * is specified, ng-repeat associates elements by identity. It is an error to have 33735 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 33736 * mapped to the same DOM element, which is not possible.) 33737 * 33738 * *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`. 33739 * This implies that the DOM elements will be associated by item identity in the collection. 33740 * 33741 * The built-in `$id()` function can be used to assign a unique 33742 * `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements 33743 * with the corresponding item in the collection by identity. Moving the same object would move 33744 * the DOM element in the same way in the DOM. 33745 * Note that the default id function does not support duplicate primitive values (`number`, `string`), 33746 * but supports duplictae non-primitive values (`object`) that are *equal* in shape. 33747 * 33748 * *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking 33749 * id, for example with a function, or using a property on the collection items. 33750 * `item in items track by item.id` is a typical pattern when the items have a unique identifier, 33751 * e.g. database id. In this case the object identity does not matter. Two objects are considered 33752 * equivalent as long as their `id` property is same. 33753 * Tracking by unique identifier is the most performant way and should be used whenever possible. 33754 * 33755 * *$index*: This special property tracks the collection items by their index, and 33756 * re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can 33757 * be used for a performance improvement if no unique identfier is available and the identity of 33758 * the collection items cannot be easily computed. It also allows duplicates. 33759 * 33760 * <div class="alert alert-warning"> 33761 * <strong>Note:</strong> Re-using DOM elements can have unforeseen effects. Read the 33762 * {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for 33763 * more info. 33764 * </div> 33765 * 33766 * <div class="alert alert-warning"> 33767 * <strong>Note:</strong> the `track by` expression must come last - after any filters, and the alias expression: 33768 * `item in items | filter:searchText as results track by item.id` 33769 * </div> 33770 * 33771 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the 33772 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 33773 * when a filter is active on the repeater, but the filtered result set is empty. 33774 * 33775 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 33776 * the items have been processed through the filter. 33777 * 33778 * Please note that `as [variable name]` is not an operator but rather a part of ngRepeat 33779 * micro-syntax so it can be used only after all filters (and not as operator, inside an expression). 33780 * 33781 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` . 33782 * 33783 * @example 33784 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed 33785 * results by name or by age. New (entering) and removed (leaving) items are animated. 33786 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true"> 33787 <file name="index.html"> 33788 <div ng-controller="repeatController"> 33789 I have {{friends.length}} friends. They are: 33790 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 33791 <ul class="example-animate-container"> 33792 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results track by friend.name"> 33793 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 33794 </li> 33795 <li class="animate-repeat" ng-if="results.length === 0"> 33796 <strong>No results found...</strong> 33797 </li> 33798 </ul> 33799 </div> 33800 </file> 33801 <file name="script.js"> 33802 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 33803 $scope.friends = [ 33804 {name:'John', age:25, gender:'boy'}, 33805 {name:'Jessie', age:30, gender:'girl'}, 33806 {name:'Johanna', age:28, gender:'girl'}, 33807 {name:'Joy', age:15, gender:'girl'}, 33808 {name:'Mary', age:28, gender:'girl'}, 33809 {name:'Peter', age:95, gender:'boy'}, 33810 {name:'Sebastian', age:50, gender:'boy'}, 33811 {name:'Erika', age:27, gender:'girl'}, 33812 {name:'Patrick', age:40, gender:'boy'}, 33813 {name:'Samantha', age:60, gender:'girl'} 33814 ]; 33815 }); 33816 </file> 33817 <file name="animations.css"> 33818 .example-animate-container { 33819 background:white; 33820 border:1px solid black; 33821 list-style:none; 33822 margin:0; 33823 padding:0 10px; 33824 } 33825 33826 .animate-repeat { 33827 line-height:30px; 33828 list-style:none; 33829 box-sizing:border-box; 33830 } 33831 33832 .animate-repeat.ng-move, 33833 .animate-repeat.ng-enter, 33834 .animate-repeat.ng-leave { 33835 transition:all linear 0.5s; 33836 } 33837 33838 .animate-repeat.ng-leave.ng-leave-active, 33839 .animate-repeat.ng-move, 33840 .animate-repeat.ng-enter { 33841 opacity:0; 33842 max-height:0; 33843 } 33844 33845 .animate-repeat.ng-leave, 33846 .animate-repeat.ng-move.ng-move-active, 33847 .animate-repeat.ng-enter.ng-enter-active { 33848 opacity:1; 33849 max-height:30px; 33850 } 33851 </file> 33852 <file name="protractor.js" type="protractor"> 33853 var friends = element.all(by.repeater('friend in friends')); 33854 33855 it('should render initial data set', function() { 33856 expect(friends.count()).toBe(10); 33857 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 33858 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 33859 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 33860 expect(element(by.binding('friends.length')).getText()) 33861 .toMatch("I have 10 friends. They are:"); 33862 }); 33863 33864 it('should update repeater when filter predicate changes', function() { 33865 expect(friends.count()).toBe(10); 33866 33867 element(by.model('q')).sendKeys('ma'); 33868 33869 expect(friends.count()).toBe(2); 33870 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 33871 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 33872 }); 33873 </file> 33874 </example> 33875 */ 33876var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { 33877 var NG_REMOVED = '$$NG_REMOVED'; 33878 var ngRepeatMinErr = minErr('ngRepeat'); 33879 33880 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 33881 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 33882 scope[valueIdentifier] = value; 33883 if (keyIdentifier) scope[keyIdentifier] = key; 33884 scope.$index = index; 33885 scope.$first = (index === 0); 33886 scope.$last = (index === (arrayLength - 1)); 33887 scope.$middle = !(scope.$first || scope.$last); 33888 // eslint-disable-next-line no-bitwise 33889 scope.$odd = !(scope.$even = (index & 1) === 0); 33890 }; 33891 33892 var getBlockStart = function(block) { 33893 return block.clone[0]; 33894 }; 33895 33896 var getBlockEnd = function(block) { 33897 return block.clone[block.clone.length - 1]; 33898 }; 33899 33900 var trackByIdArrayFn = function($scope, key, value) { 33901 return hashKey(value); 33902 }; 33903 33904 var trackByIdObjFn = function($scope, key) { 33905 return key; 33906 }; 33907 33908 return { 33909 restrict: 'A', 33910 multiElement: true, 33911 transclude: 'element', 33912 priority: 1000, 33913 terminal: true, 33914 $$tlb: true, 33915 compile: function ngRepeatCompile($element, $attr) { 33916 var expression = $attr.ngRepeat; 33917 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); 33918 33919 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 33920 33921 if (!match) { 33922 throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.', 33923 expression); 33924 } 33925 33926 var lhs = match[1]; 33927 var rhs = match[2]; 33928 var aliasAs = match[3]; 33929 var trackByExp = match[4]; 33930 33931 match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/); 33932 33933 if (!match) { 33934 throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.', 33935 lhs); 33936 } 33937 var valueIdentifier = match[3] || match[1]; 33938 var keyIdentifier = match[2]; 33939 33940 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 33941 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 33942 throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.', 33943 aliasAs); 33944 } 33945 33946 var trackByIdExpFn; 33947 33948 if (trackByExp) { 33949 var hashFnLocals = {$id: hashKey}; 33950 var trackByExpGetter = $parse(trackByExp); 33951 33952 trackByIdExpFn = function($scope, key, value, index) { 33953 // assign key, value, and $index to the locals so that they can be used in hash functions 33954 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 33955 hashFnLocals[valueIdentifier] = value; 33956 hashFnLocals.$index = index; 33957 return trackByExpGetter($scope, hashFnLocals); 33958 }; 33959 } 33960 33961 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 33962 33963 // Store a list of elements from previous run. This is a hash where key is the item from the 33964 // iterator, and the value is objects with following properties. 33965 // - scope: bound scope 33966 // - clone: previous element. 33967 // - index: position 33968 // 33969 // We are using no-proto object so that we don't need to guard against inherited props via 33970 // hasOwnProperty. 33971 var lastBlockMap = createMap(); 33972 33973 //watch props 33974 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 33975 var index, length, 33976 previousNode = $element[0], // node that cloned nodes should be inserted after 33977 // initialized to the comment node anchor 33978 nextNode, 33979 // Same as lastBlockMap but it has the current state. It will become the 33980 // lastBlockMap on the next iteration. 33981 nextBlockMap = createMap(), 33982 collectionLength, 33983 key, value, // key/value of iteration 33984 trackById, 33985 trackByIdFn, 33986 collectionKeys, 33987 block, // last object information {scope, element, id} 33988 nextBlockOrder, 33989 elementsToRemove; 33990 33991 if (aliasAs) { 33992 $scope[aliasAs] = collection; 33993 } 33994 33995 if (isArrayLike(collection)) { 33996 collectionKeys = collection; 33997 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 33998 } else { 33999 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 34000 // if object, extract keys, in enumeration order, unsorted 34001 collectionKeys = []; 34002 for (var itemKey in collection) { 34003 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 34004 collectionKeys.push(itemKey); 34005 } 34006 } 34007 } 34008 34009 collectionLength = collectionKeys.length; 34010 nextBlockOrder = new Array(collectionLength); 34011 34012 // locate existing items 34013 for (index = 0; index < collectionLength; index++) { 34014 key = (collection === collectionKeys) ? index : collectionKeys[index]; 34015 value = collection[key]; 34016 trackById = trackByIdFn($scope, key, value, index); 34017 if (lastBlockMap[trackById]) { 34018 // found previously seen block 34019 block = lastBlockMap[trackById]; 34020 delete lastBlockMap[trackById]; 34021 nextBlockMap[trackById] = block; 34022 nextBlockOrder[index] = block; 34023 } else if (nextBlockMap[trackById]) { 34024 // if collision detected. restore lastBlockMap and throw an error 34025 forEach(nextBlockOrder, function(block) { 34026 if (block && block.scope) lastBlockMap[block.id] = block; 34027 }); 34028 throw ngRepeatMinErr('dupes', 34029 'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}', 34030 expression, trackById, value); 34031 } else { 34032 // new never before seen block 34033 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 34034 nextBlockMap[trackById] = true; 34035 } 34036 } 34037 34038 // Clear the value property from the hashFnLocals object to prevent a reference to the last value 34039 // being leaked into the ngRepeatCompile function scope 34040 if (hashFnLocals) { 34041 hashFnLocals[valueIdentifier] = undefined; 34042 } 34043 34044 // remove leftover items 34045 for (var blockKey in lastBlockMap) { 34046 block = lastBlockMap[blockKey]; 34047 elementsToRemove = getBlockNodes(block.clone); 34048 $animate.leave(elementsToRemove); 34049 if (elementsToRemove[0].parentNode) { 34050 // if the element was not removed yet because of pending animation, mark it as deleted 34051 // so that we can ignore it later 34052 for (index = 0, length = elementsToRemove.length; index < length; index++) { 34053 elementsToRemove[index][NG_REMOVED] = true; 34054 } 34055 } 34056 block.scope.$destroy(); 34057 } 34058 34059 // we are not using forEach for perf reasons (trying to avoid #call) 34060 for (index = 0; index < collectionLength; index++) { 34061 key = (collection === collectionKeys) ? index : collectionKeys[index]; 34062 value = collection[key]; 34063 block = nextBlockOrder[index]; 34064 34065 if (block.scope) { 34066 // if we have already seen this object, then we need to reuse the 34067 // associated scope/element 34068 34069 nextNode = previousNode; 34070 34071 // skip nodes that are already pending removal via leave animation 34072 do { 34073 nextNode = nextNode.nextSibling; 34074 } while (nextNode && nextNode[NG_REMOVED]); 34075 34076 if (getBlockStart(block) !== nextNode) { 34077 // existing item which got moved 34078 $animate.move(getBlockNodes(block.clone), null, previousNode); 34079 } 34080 previousNode = getBlockEnd(block); 34081 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 34082 } else { 34083 // new item which we don't know about 34084 $transclude(function ngRepeatTransclude(clone, scope) { 34085 block.scope = scope; 34086 // http://jsperf.com/clone-vs-createcomment 34087 var endNode = ngRepeatEndComment.cloneNode(false); 34088 clone[clone.length++] = endNode; 34089 34090 $animate.enter(clone, null, previousNode); 34091 previousNode = endNode; 34092 // Note: We only need the first/last node of the cloned nodes. 34093 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 34094 // by a directive with templateUrl when its template arrives. 34095 block.clone = clone; 34096 nextBlockMap[block.id] = block; 34097 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 34098 }); 34099 } 34100 } 34101 lastBlockMap = nextBlockMap; 34102 }); 34103 }; 34104 } 34105 }; 34106}]; 34107 34108var NG_HIDE_CLASS = 'ng-hide'; 34109var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 34110/** 34111 * @ngdoc directive 34112 * @name ngShow 34113 * @multiElement 34114 * 34115 * @description 34116 * The `ngShow` directive shows or hides the given HTML element based on the expression provided to 34117 * the `ngShow` attribute. 34118 * 34119 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. 34120 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an 34121 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see 34122 * {@link ng.directive:ngCsp ngCsp}). 34123 * 34124 * ```html 34125 * <!-- when $scope.myValue is truthy (element is visible) --> 34126 * <div ng-show="myValue"></div> 34127 * 34128 * <!-- when $scope.myValue is falsy (element is hidden) --> 34129 * <div ng-show="myValue" class="ng-hide"></div> 34130 * ``` 34131 * 34132 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added 34133 * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide` 34134 * CSS class is removed from the element causing the element not to appear hidden. 34135 * 34136 * ## Why is `!important` used? 34137 * 34138 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the 34139 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as 34140 * simple as changing the display style on a HTML list item would make hidden elements appear 34141 * visible. This also becomes a bigger issue when dealing with CSS frameworks. 34142 * 34143 * By using `!important`, the show and hide behavior will work as expected despite any clash between 34144 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a 34145 * developer chooses to override the styling to change how to hide an element then it is just a 34146 * matter of using `!important` in their own CSS code. 34147 * 34148 * ### Overriding `.ng-hide` 34149 * 34150 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you 34151 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for 34152 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually 34153 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. 34154 * 34155 * ```css 34156 * .ng-hide:not(.ng-hide-animate) { 34157 * /* These are just alternative ways of hiding an element */ 34158 * display: block!important; 34159 * position: absolute; 34160 * top: -9999px; 34161 * left: -9999px; 34162 * } 34163 * ``` 34164 * 34165 * By default you don't need to override anything in CSS and the animations will work around the 34166 * display style. 34167 * 34168 * @animations 34169 * | Animation | Occurs | 34170 * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------| 34171 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. | 34172 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. | 34173 * 34174 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the 34175 * directive expression is true and false. This system works like the animation system present with 34176 * `ngClass` except that you must also include the `!important` flag to override the display 34177 * property so that the elements are not actually hidden during the animation. 34178 * 34179 * ```css 34180 * /* A working example can be found at the bottom of this page. */ 34181 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 34182 * transition: all 0.5s linear; 34183 * } 34184 * 34185 * .my-element.ng-hide-add { ... } 34186 * .my-element.ng-hide-add.ng-hide-add-active { ... } 34187 * .my-element.ng-hide-remove { ... } 34188 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 34189 * ``` 34190 * 34191 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property 34192 * to block during animation states - ngAnimate will automatically handle the style toggling for you. 34193 * 34194 * @element ANY 34195 * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the 34196 * element is shown/hidden respectively. 34197 * 34198 * @example 34199 * A simple example, animating the element's opacity: 34200 * 34201 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple"> 34202 <file name="index.html"> 34203 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br /> 34204 <div class="check-element animate-show-hide" ng-show="checked"> 34205 I show up when your checkbox is checked. 34206 </div> 34207 </file> 34208 <file name="animations.css"> 34209 .animate-show-hide.ng-hide { 34210 opacity: 0; 34211 } 34212 34213 .animate-show-hide.ng-hide-add, 34214 .animate-show-hide.ng-hide-remove { 34215 transition: all linear 0.5s; 34216 } 34217 34218 .check-element { 34219 border: 1px solid black; 34220 opacity: 1; 34221 padding: 10px; 34222 } 34223 </file> 34224 <file name="protractor.js" type="protractor"> 34225 it('should check ngShow', function() { 34226 var checkbox = element(by.model('checked')); 34227 var checkElem = element(by.css('.check-element')); 34228 34229 expect(checkElem.isDisplayed()).toBe(false); 34230 checkbox.click(); 34231 expect(checkElem.isDisplayed()).toBe(true); 34232 }); 34233 </file> 34234 </example> 34235 * 34236 * <hr /> 34237 * @example 34238 * A more complex example, featuring different show/hide animations: 34239 * 34240 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex"> 34241 <file name="index.html"> 34242 Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br /> 34243 <div class="check-element funky-show-hide" ng-show="checked"> 34244 I show up when your checkbox is checked. 34245 </div> 34246 </file> 34247 <file name="animations.css"> 34248 body { 34249 overflow: hidden; 34250 perspective: 1000px; 34251 } 34252 34253 .funky-show-hide.ng-hide-add { 34254 transform: rotateZ(0); 34255 transform-origin: right; 34256 transition: all 0.5s ease-in-out; 34257 } 34258 34259 .funky-show-hide.ng-hide-add.ng-hide-add-active { 34260 transform: rotateZ(-135deg); 34261 } 34262 34263 .funky-show-hide.ng-hide-remove { 34264 transform: rotateY(90deg); 34265 transform-origin: left; 34266 transition: all 0.5s ease; 34267 } 34268 34269 .funky-show-hide.ng-hide-remove.ng-hide-remove-active { 34270 transform: rotateY(0); 34271 } 34272 34273 .check-element { 34274 border: 1px solid black; 34275 opacity: 1; 34276 padding: 10px; 34277 } 34278 </file> 34279 <file name="protractor.js" type="protractor"> 34280 it('should check ngShow', function() { 34281 var checkbox = element(by.model('checked')); 34282 var checkElem = element(by.css('.check-element')); 34283 34284 expect(checkElem.isDisplayed()).toBe(false); 34285 checkbox.click(); 34286 expect(checkElem.isDisplayed()).toBe(true); 34287 }); 34288 </file> 34289 </example> 34290 * 34291 * @knownIssue 34292 * 34293 * ### Flickering when using ngShow to toggle between elements 34294 * 34295 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can 34296 * happen that both the element to show and the element to hide are visible for a very short time. 34297 * 34298 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations 34299 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than 34300 * other browsers. 34301 * 34302 * There are several way to mitigate this problem: 34303 * 34304 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}. 34305 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}. 34306 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements. 34307 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}. 34308 * - Define an animation on the affected elements. 34309 */ 34310var ngShowDirective = ['$animate', function($animate) { 34311 return { 34312 restrict: 'A', 34313 multiElement: true, 34314 link: function(scope, element, attr) { 34315 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 34316 // we're adding a temporary, animation-specific class for ng-hide since this way 34317 // we can control when the element is actually displayed on screen without having 34318 // to have a global/greedy CSS selector that breaks when other animations are run. 34319 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 34320 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 34321 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 34322 }); 34323 }); 34324 } 34325 }; 34326}]; 34327 34328 34329/** 34330 * @ngdoc directive 34331 * @name ngHide 34332 * @multiElement 34333 * 34334 * @description 34335 * The `ngHide` directive shows or hides the given HTML element based on the expression provided to 34336 * the `ngHide` attribute. 34337 * 34338 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. 34339 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an 34340 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see 34341 * {@link ng.directive:ngCsp ngCsp}). 34342 * 34343 * ```html 34344 * <!-- when $scope.myValue is truthy (element is hidden) --> 34345 * <div ng-hide="myValue" class="ng-hide"></div> 34346 * 34347 * <!-- when $scope.myValue is falsy (element is visible) --> 34348 * <div ng-hide="myValue"></div> 34349 * ``` 34350 * 34351 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added 34352 * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide` 34353 * CSS class is removed from the element causing the element not to appear hidden. 34354 * 34355 * ## Why is `!important` used? 34356 * 34357 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the 34358 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as 34359 * simple as changing the display style on a HTML list item would make hidden elements appear 34360 * visible. This also becomes a bigger issue when dealing with CSS frameworks. 34361 * 34362 * By using `!important`, the show and hide behavior will work as expected despite any clash between 34363 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a 34364 * developer chooses to override the styling to change how to hide an element then it is just a 34365 * matter of using `!important` in their own CSS code. 34366 * 34367 * ### Overriding `.ng-hide` 34368 * 34369 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you 34370 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for 34371 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually 34372 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. 34373 * 34374 * ```css 34375 * .ng-hide:not(.ng-hide-animate) { 34376 * /* These are just alternative ways of hiding an element */ 34377 * display: block!important; 34378 * position: absolute; 34379 * top: -9999px; 34380 * left: -9999px; 34381 * } 34382 * ``` 34383 * 34384 * By default you don't need to override in CSS anything and the animations will work around the 34385 * display style. 34386 * 34387 * @animations 34388 * | Animation | Occurs | 34389 * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------| 34390 * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. | 34391 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. | 34392 * 34393 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the 34394 * directive expression is true and false. This system works like the animation system present with 34395 * `ngClass` except that you must also include the `!important` flag to override the display 34396 * property so that the elements are not actually hidden during the animation. 34397 * 34398 * ```css 34399 * /* A working example can be found at the bottom of this page. */ 34400 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 34401 * transition: all 0.5s linear; 34402 * } 34403 * 34404 * .my-element.ng-hide-add { ... } 34405 * .my-element.ng-hide-add.ng-hide-add-active { ... } 34406 * .my-element.ng-hide-remove { ... } 34407 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 34408 * ``` 34409 * 34410 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property 34411 * to block during animation states - ngAnimate will automatically handle the style toggling for you. 34412 * 34413 * @element ANY 34414 * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the 34415 * element is hidden/shown respectively. 34416 * 34417 * @example 34418 * A simple example, animating the element's opacity: 34419 * 34420 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple"> 34421 <file name="index.html"> 34422 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br /> 34423 <div class="check-element animate-show-hide" ng-hide="checked"> 34424 I hide when your checkbox is checked. 34425 </div> 34426 </file> 34427 <file name="animations.css"> 34428 .animate-show-hide.ng-hide { 34429 opacity: 0; 34430 } 34431 34432 .animate-show-hide.ng-hide-add, 34433 .animate-show-hide.ng-hide-remove { 34434 transition: all linear 0.5s; 34435 } 34436 34437 .check-element { 34438 border: 1px solid black; 34439 opacity: 1; 34440 padding: 10px; 34441 } 34442 </file> 34443 <file name="protractor.js" type="protractor"> 34444 it('should check ngHide', function() { 34445 var checkbox = element(by.model('checked')); 34446 var checkElem = element(by.css('.check-element')); 34447 34448 expect(checkElem.isDisplayed()).toBe(true); 34449 checkbox.click(); 34450 expect(checkElem.isDisplayed()).toBe(false); 34451 }); 34452 </file> 34453 </example> 34454 * 34455 * <hr /> 34456 * @example 34457 * A more complex example, featuring different show/hide animations: 34458 * 34459 <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex"> 34460 <file name="index.html"> 34461 Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br /> 34462 <div class="check-element funky-show-hide" ng-hide="checked"> 34463 I hide when your checkbox is checked. 34464 </div> 34465 </file> 34466 <file name="animations.css"> 34467 body { 34468 overflow: hidden; 34469 perspective: 1000px; 34470 } 34471 34472 .funky-show-hide.ng-hide-add { 34473 transform: rotateZ(0); 34474 transform-origin: right; 34475 transition: all 0.5s ease-in-out; 34476 } 34477 34478 .funky-show-hide.ng-hide-add.ng-hide-add-active { 34479 transform: rotateZ(-135deg); 34480 } 34481 34482 .funky-show-hide.ng-hide-remove { 34483 transform: rotateY(90deg); 34484 transform-origin: left; 34485 transition: all 0.5s ease; 34486 } 34487 34488 .funky-show-hide.ng-hide-remove.ng-hide-remove-active { 34489 transform: rotateY(0); 34490 } 34491 34492 .check-element { 34493 border: 1px solid black; 34494 opacity: 1; 34495 padding: 10px; 34496 } 34497 </file> 34498 <file name="protractor.js" type="protractor"> 34499 it('should check ngHide', function() { 34500 var checkbox = element(by.model('checked')); 34501 var checkElem = element(by.css('.check-element')); 34502 34503 expect(checkElem.isDisplayed()).toBe(true); 34504 checkbox.click(); 34505 expect(checkElem.isDisplayed()).toBe(false); 34506 }); 34507 </file> 34508 </example> 34509 * 34510 * @knownIssue 34511 * 34512 * ### Flickering when using ngHide to toggle between elements 34513 * 34514 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can 34515 * happen that both the element to show and the element to hide are visible for a very short time. 34516 * 34517 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations 34518 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than 34519 * other browsers. 34520 * 34521 * There are several way to mitigate this problem: 34522 * 34523 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}. 34524 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}. 34525 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements. 34526 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}. 34527 * - Define an animation on the affected elements. 34528 */ 34529var ngHideDirective = ['$animate', function($animate) { 34530 return { 34531 restrict: 'A', 34532 multiElement: true, 34533 link: function(scope, element, attr) { 34534 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 34535 // The comment inside of the ngShowDirective explains why we add and 34536 // remove a temporary class for the show/hide animation 34537 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 34538 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 34539 }); 34540 }); 34541 } 34542 }; 34543}]; 34544 34545/** 34546 * @ngdoc directive 34547 * @name ngStyle 34548 * @restrict AC 34549 * 34550 * @description 34551 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 34552 * 34553 * @knownIssue 34554 * You should not use {@link guide/interpolation interpolation} in the value of the `style` 34555 * attribute, when using the `ngStyle` directive on the same element. 34556 * See {@link guide/interpolation#known-issues here} for more info. 34557 * 34558 * @element ANY 34559 * @param {expression} ngStyle 34560 * 34561 * {@link guide/expression Expression} which evals to an 34562 * object whose keys are CSS style names and values are corresponding values for those CSS 34563 * keys. 34564 * 34565 * Since some CSS style names are not valid keys for an object, they must be quoted. 34566 * See the 'background-color' style in the example below. 34567 * 34568 * @example 34569 <example name="ng-style"> 34570 <file name="index.html"> 34571 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 34572 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 34573 <input type="button" value="clear" ng-click="myStyle={}"> 34574 <br/> 34575 <span ng-style="myStyle">Sample Text</span> 34576 <pre>myStyle={{myStyle}}</pre> 34577 </file> 34578 <file name="style.css"> 34579 span { 34580 color: black; 34581 } 34582 </file> 34583 <file name="protractor.js" type="protractor"> 34584 var colorSpan = element(by.css('span')); 34585 34586 it('should check ng-style', function() { 34587 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/); 34588 element(by.css('input[value=\'set color\']')).click(); 34589 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(255, 0, 0, 1\)|rgb\(255, 0, 0\)/); 34590 element(by.css('input[value=clear]')).click(); 34591 expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/); 34592 }); 34593 </file> 34594 </example> 34595 */ 34596var ngStyleDirective = ngDirective(function(scope, element, attr) { 34597 scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 34598 if (oldStyles && (newStyles !== oldStyles)) { 34599 forEach(oldStyles, function(val, style) { element.css(style, ''); }); 34600 } 34601 if (newStyles) element.css(newStyles); 34602 }); 34603}); 34604 34605/** 34606 * @ngdoc directive 34607 * @name ngSwitch 34608 * @restrict EA 34609 * 34610 * @description 34611 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 34612 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 34613 * as specified in the template. 34614 * 34615 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 34616 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 34617 * matches the value obtained from the evaluated expression. In other words, you define a container element 34618 * (where you place the directive), place an expression on the **`on="..."` attribute** 34619 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 34620 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 34621 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 34622 * attribute is displayed. 34623 * 34624 * <div class="alert alert-info"> 34625 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 34626 * as literal string values to match against. 34627 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the 34628 * value of the expression `$scope.someVal`. 34629 * </div> 34630 34631 * @animations 34632 * | Animation | Occurs | 34633 * |----------------------------------|-------------------------------------| 34634 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | 34635 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | 34636 * 34637 * @usage 34638 * 34639 * ``` 34640 * <ANY ng-switch="expression"> 34641 * <ANY ng-switch-when="matchValue1">...</ANY> 34642 * <ANY ng-switch-when="matchValue2">...</ANY> 34643 * <ANY ng-switch-default>...</ANY> 34644 * </ANY> 34645 * ``` 34646 * 34647 * 34648 * @scope 34649 * @priority 1200 34650 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 34651 * On child elements add: 34652 * 34653 * * `ngSwitchWhen`: the case statement to match against. If match then this 34654 * case will be displayed. If the same match appears multiple times, all the 34655 * elements will be displayed. It is possible to associate multiple values to 34656 * the same `ngSwitchWhen` by defining the optional attribute 34657 * `ngSwitchWhenSeparator`. The separator will be used to split the value of 34658 * the `ngSwitchWhen` attribute into multiple tokens, and the element will show 34659 * if any of the `ngSwitch` evaluates to any of these tokens. 34660 * * `ngSwitchDefault`: the default case when no other case match. If there 34661 * are multiple default cases, all of them will be displayed when no other 34662 * case match. 34663 * 34664 * 34665 * @example 34666 <example module="switchExample" deps="angular-animate.js" animations="true" name="ng-switch"> 34667 <file name="index.html"> 34668 <div ng-controller="ExampleController"> 34669 <select ng-model="selection" ng-options="item for item in items"> 34670 </select> 34671 <code>selection={{selection}}</code> 34672 <hr/> 34673 <div class="animate-switch-container" 34674 ng-switch on="selection"> 34675 <div class="animate-switch" ng-switch-when="settings|options" ng-switch-when-separator="|">Settings Div</div> 34676 <div class="animate-switch" ng-switch-when="home">Home Span</div> 34677 <div class="animate-switch" ng-switch-default>default</div> 34678 </div> 34679 </div> 34680 </file> 34681 <file name="script.js"> 34682 angular.module('switchExample', ['ngAnimate']) 34683 .controller('ExampleController', ['$scope', function($scope) { 34684 $scope.items = ['settings', 'home', 'options', 'other']; 34685 $scope.selection = $scope.items[0]; 34686 }]); 34687 </file> 34688 <file name="animations.css"> 34689 .animate-switch-container { 34690 position:relative; 34691 background:white; 34692 border:1px solid black; 34693 height:40px; 34694 overflow:hidden; 34695 } 34696 34697 .animate-switch { 34698 padding:10px; 34699 } 34700 34701 .animate-switch.ng-animate { 34702 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 34703 34704 position:absolute; 34705 top:0; 34706 left:0; 34707 right:0; 34708 bottom:0; 34709 } 34710 34711 .animate-switch.ng-leave.ng-leave-active, 34712 .animate-switch.ng-enter { 34713 top:-50px; 34714 } 34715 .animate-switch.ng-leave, 34716 .animate-switch.ng-enter.ng-enter-active { 34717 top:0; 34718 } 34719 </file> 34720 <file name="protractor.js" type="protractor"> 34721 var switchElem = element(by.css('[ng-switch]')); 34722 var select = element(by.model('selection')); 34723 34724 it('should start in settings', function() { 34725 expect(switchElem.getText()).toMatch(/Settings Div/); 34726 }); 34727 it('should change to home', function() { 34728 select.all(by.css('option')).get(1).click(); 34729 expect(switchElem.getText()).toMatch(/Home Span/); 34730 }); 34731 it('should change to settings via "options"', function() { 34732 select.all(by.css('option')).get(2).click(); 34733 expect(switchElem.getText()).toMatch(/Settings Div/); 34734 }); 34735 it('should select default', function() { 34736 select.all(by.css('option')).get(3).click(); 34737 expect(switchElem.getText()).toMatch(/default/); 34738 }); 34739 </file> 34740 </example> 34741 */ 34742var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { 34743 return { 34744 require: 'ngSwitch', 34745 34746 // asks for $scope to fool the BC controller module 34747 controller: ['$scope', function NgSwitchController() { 34748 this.cases = {}; 34749 }], 34750 link: function(scope, element, attr, ngSwitchController) { 34751 var watchExpr = attr.ngSwitch || attr.on, 34752 selectedTranscludes = [], 34753 selectedElements = [], 34754 previousLeaveAnimations = [], 34755 selectedScopes = []; 34756 34757 var spliceFactory = function(array, index) { 34758 return function(response) { 34759 if (response !== false) array.splice(index, 1); 34760 }; 34761 }; 34762 34763 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 34764 var i, ii; 34765 34766 // Start with the last, in case the array is modified during the loop 34767 while (previousLeaveAnimations.length) { 34768 $animate.cancel(previousLeaveAnimations.pop()); 34769 } 34770 34771 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 34772 var selected = getBlockNodes(selectedElements[i].clone); 34773 selectedScopes[i].$destroy(); 34774 var runner = previousLeaveAnimations[i] = $animate.leave(selected); 34775 runner.done(spliceFactory(previousLeaveAnimations, i)); 34776 } 34777 34778 selectedElements.length = 0; 34779 selectedScopes.length = 0; 34780 34781 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 34782 forEach(selectedTranscludes, function(selectedTransclude) { 34783 selectedTransclude.transclude(function(caseElement, selectedScope) { 34784 selectedScopes.push(selectedScope); 34785 var anchor = selectedTransclude.element; 34786 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); 34787 var block = { clone: caseElement }; 34788 34789 selectedElements.push(block); 34790 $animate.enter(caseElement, anchor.parent(), anchor); 34791 }); 34792 }); 34793 } 34794 }); 34795 } 34796 }; 34797}]; 34798 34799var ngSwitchWhenDirective = ngDirective({ 34800 transclude: 'element', 34801 priority: 1200, 34802 require: '^ngSwitch', 34803 multiElement: true, 34804 link: function(scope, element, attrs, ctrl, $transclude) { 34805 34806 var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter( 34807 // Filter duplicate cases 34808 function(element, index, array) { return array[index - 1] !== element; } 34809 ); 34810 34811 forEach(cases, function(whenCase) { 34812 ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []); 34813 ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element }); 34814 }); 34815 } 34816}); 34817 34818var ngSwitchDefaultDirective = ngDirective({ 34819 transclude: 'element', 34820 priority: 1200, 34821 require: '^ngSwitch', 34822 multiElement: true, 34823 link: function(scope, element, attr, ctrl, $transclude) { 34824 ctrl.cases['?'] = (ctrl.cases['?'] || []); 34825 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 34826 } 34827}); 34828 34829/** 34830 * @ngdoc directive 34831 * @name ngTransclude 34832 * @restrict EAC 34833 * 34834 * @description 34835 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 34836 * 34837 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 34838 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 34839 * 34840 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 34841 * content of this element will be removed before the transcluded content is inserted. 34842 * If the transcluded content is empty (or only whitespace), the existing content is left intact. This lets you provide fallback 34843 * content in the case that no transcluded content is provided. 34844 * 34845 * @element ANY 34846 * 34847 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 34848 * or its value is the same as the name of the attribute then the default slot is used. 34849 * 34850 * @example 34851 * ### Basic transclusion 34852 * This example demonstrates basic transclusion of content into a component directive. 34853 * <example name="simpleTranscludeExample" module="transcludeExample"> 34854 * <file name="index.html"> 34855 * <script> 34856 * angular.module('transcludeExample', []) 34857 * .directive('pane', function(){ 34858 * return { 34859 * restrict: 'E', 34860 * transclude: true, 34861 * scope: { title:'@' }, 34862 * template: '<div style="border: 1px solid black;">' + 34863 * '<div style="background-color: gray">{{title}}</div>' + 34864 * '<ng-transclude></ng-transclude>' + 34865 * '</div>' 34866 * }; 34867 * }) 34868 * .controller('ExampleController', ['$scope', function($scope) { 34869 * $scope.title = 'Lorem Ipsum'; 34870 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 34871 * }]); 34872 * </script> 34873 * <div ng-controller="ExampleController"> 34874 * <input ng-model="title" aria-label="title"> <br/> 34875 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 34876 * <pane title="{{title}}"><span>{{text}}</span></pane> 34877 * </div> 34878 * </file> 34879 * <file name="protractor.js" type="protractor"> 34880 * it('should have transcluded', function() { 34881 * var titleElement = element(by.model('title')); 34882 * titleElement.clear(); 34883 * titleElement.sendKeys('TITLE'); 34884 * var textElement = element(by.model('text')); 34885 * textElement.clear(); 34886 * textElement.sendKeys('TEXT'); 34887 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 34888 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 34889 * }); 34890 * </file> 34891 * </example> 34892 * 34893 * @example 34894 * ### Transclude fallback content 34895 * This example shows how to use `NgTransclude` with fallback content, that 34896 * is displayed if no transcluded content is provided. 34897 * 34898 * <example module="transcludeFallbackContentExample" name="ng-transclude"> 34899 * <file name="index.html"> 34900 * <script> 34901 * angular.module('transcludeFallbackContentExample', []) 34902 * .directive('myButton', function(){ 34903 * return { 34904 * restrict: 'E', 34905 * transclude: true, 34906 * scope: true, 34907 * template: '<button style="cursor: pointer;">' + 34908 * '<ng-transclude>' + 34909 * '<b style="color: red;">Button1</b>' + 34910 * '</ng-transclude>' + 34911 * '</button>' 34912 * }; 34913 * }); 34914 * </script> 34915 * <!-- fallback button content --> 34916 * <my-button id="fallback"></my-button> 34917 * <!-- modified button content --> 34918 * <my-button id="modified"> 34919 * <i style="color: green;">Button2</i> 34920 * </my-button> 34921 * </file> 34922 * <file name="protractor.js" type="protractor"> 34923 * it('should have different transclude element content', function() { 34924 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 34925 * expect(element(by.id('modified')).getText()).toBe('Button2'); 34926 * }); 34927 * </file> 34928 * </example> 34929 * 34930 * @example 34931 * ### Multi-slot transclusion 34932 * This example demonstrates using multi-slot transclusion in a component directive. 34933 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 34934 * <file name="index.html"> 34935 * <style> 34936 * .title, .footer { 34937 * background-color: gray 34938 * } 34939 * </style> 34940 * <div ng-controller="ExampleController"> 34941 * <input ng-model="title" aria-label="title"> <br/> 34942 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 34943 * <pane> 34944 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 34945 * <pane-body><p>{{text}}</p></pane-body> 34946 * </pane> 34947 * </div> 34948 * </file> 34949 * <file name="app.js"> 34950 * angular.module('multiSlotTranscludeExample', []) 34951 * .directive('pane', function() { 34952 * return { 34953 * restrict: 'E', 34954 * transclude: { 34955 * 'title': '?paneTitle', 34956 * 'body': 'paneBody', 34957 * 'footer': '?paneFooter' 34958 * }, 34959 * template: '<div style="border: 1px solid black;">' + 34960 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 34961 * '<div ng-transclude="body"></div>' + 34962 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 34963 * '</div>' 34964 * }; 34965 * }) 34966 * .controller('ExampleController', ['$scope', function($scope) { 34967 * $scope.title = 'Lorem Ipsum'; 34968 * $scope.link = 'https://google.com'; 34969 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 34970 * }]); 34971 * </file> 34972 * <file name="protractor.js" type="protractor"> 34973 * it('should have transcluded the title and the body', function() { 34974 * var titleElement = element(by.model('title')); 34975 * titleElement.clear(); 34976 * titleElement.sendKeys('TITLE'); 34977 * var textElement = element(by.model('text')); 34978 * textElement.clear(); 34979 * textElement.sendKeys('TEXT'); 34980 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 34981 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 34982 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 34983 * }); 34984 * </file> 34985 * </example> 34986 */ 34987var ngTranscludeMinErr = minErr('ngTransclude'); 34988var ngTranscludeDirective = ['$compile', function($compile) { 34989 return { 34990 restrict: 'EAC', 34991 compile: function ngTranscludeCompile(tElement) { 34992 34993 // Remove and cache any original content to act as a fallback 34994 var fallbackLinkFn = $compile(tElement.contents()); 34995 tElement.empty(); 34996 34997 return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) { 34998 34999 if (!$transclude) { 35000 throw ngTranscludeMinErr('orphan', 35001 'Illegal use of ngTransclude directive in the template! ' + 35002 'No parent directive that requires a transclusion found. ' + 35003 'Element: {0}', 35004 startingTag($element)); 35005 } 35006 35007 35008 // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default 35009 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 35010 $attrs.ngTransclude = ''; 35011 } 35012 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 35013 35014 // If the slot is required and no transclusion content is provided then this call will throw an error 35015 $transclude(ngTranscludeCloneAttachFn, null, slotName); 35016 35017 // If the slot is optional and no transclusion content is provided then use the fallback content 35018 if (slotName && !$transclude.isSlotFilled(slotName)) { 35019 useFallbackContent(); 35020 } 35021 35022 function ngTranscludeCloneAttachFn(clone, transcludedScope) { 35023 if (clone.length && notWhitespace(clone)) { 35024 $element.append(clone); 35025 } else { 35026 useFallbackContent(); 35027 // There is nothing linked against the transcluded scope since no content was available, 35028 // so it should be safe to clean up the generated scope. 35029 transcludedScope.$destroy(); 35030 } 35031 } 35032 35033 function useFallbackContent() { 35034 // Since this is the fallback content rather than the transcluded content, 35035 // we link against the scope of this directive rather than the transcluded scope 35036 fallbackLinkFn($scope, function(clone) { 35037 $element.append(clone); 35038 }); 35039 } 35040 35041 function notWhitespace(nodes) { 35042 for (var i = 0, ii = nodes.length; i < ii; i++) { 35043 var node = nodes[i]; 35044 if (node.nodeType !== NODE_TYPE_TEXT || node.nodeValue.trim()) { 35045 return true; 35046 } 35047 } 35048 } 35049 }; 35050 } 35051 }; 35052}]; 35053 35054/** 35055 * @ngdoc directive 35056 * @name script 35057 * @restrict E 35058 * 35059 * @description 35060 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 35061 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 35062 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 35063 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 35064 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 35065 * 35066 * @param {string} type Must be set to `'text/ng-template'`. 35067 * @param {string} id Cache name of the template. 35068 * 35069 * @example 35070 <example name="script-tag"> 35071 <file name="index.html"> 35072 <script type="text/ng-template" id="/tpl.html"> 35073 Content of the template. 35074 </script> 35075 35076 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 35077 <div id="tpl-content" ng-include src="currentTpl"></div> 35078 </file> 35079 <file name="protractor.js" type="protractor"> 35080 it('should load template defined inside script tag', function() { 35081 element(by.css('#tpl-link')).click(); 35082 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 35083 }); 35084 </file> 35085 </example> 35086 */ 35087var scriptDirective = ['$templateCache', function($templateCache) { 35088 return { 35089 restrict: 'E', 35090 terminal: true, 35091 compile: function(element, attr) { 35092 if (attr.type === 'text/ng-template') { 35093 var templateUrl = attr.id, 35094 text = element[0].text; 35095 35096 $templateCache.put(templateUrl, text); 35097 } 35098 } 35099 }; 35100}]; 35101 35102/* exported selectDirective, optionDirective */ 35103 35104var noopNgModelController = { $setViewValue: noop, $render: noop }; 35105 35106function setOptionSelectedStatus(optionEl, value) { 35107 optionEl.prop('selected', value); 35108 /** 35109 * When unselecting an option, setting the property to null / false should be enough 35110 * However, screenreaders might react to the selected attribute instead, see 35111 * https://github.com/angular/angular.js/issues/14419 35112 * Note: "selected" is a boolean attr and will be removed when the "value" arg in attr() is false 35113 * or null 35114 */ 35115 optionEl.attr('selected', value); 35116} 35117 35118/** 35119 * @ngdoc type 35120 * @name select.SelectController 35121 * 35122 * @description 35123 * The controller for the {@link ng.select select} directive. The controller exposes 35124 * a few utility methods that can be used to augment the behavior of a regular or an 35125 * {@link ng.ngOptions ngOptions} select element. 35126 * 35127 * @example 35128 * ### Set a custom error when the unknown option is selected 35129 * 35130 * This example sets a custom error "unknownValue" on the ngModelController 35131 * when the select element's unknown option is selected, i.e. when the model is set to a value 35132 * that is not matched by any option. 35133 * 35134 * <example name="select-unknown-value-error" module="staticSelect"> 35135 * <file name="index.html"> 35136 * <div ng-controller="ExampleController"> 35137 * <form name="myForm"> 35138 * <label for="testSelect"> Single select: </label><br> 35139 * <select name="testSelect" ng-model="selected" unknown-value-error> 35140 * <option value="option-1">Option 1</option> 35141 * <option value="option-2">Option 2</option> 35142 * </select><br> 35143 * <span class="error" ng-if="myForm.testSelect.$error.unknownValue"> 35144 * Error: The current model doesn't match any option</span><br> 35145 * 35146 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 35147 * </form> 35148 * </div> 35149 * </file> 35150 * <file name="app.js"> 35151 * angular.module('staticSelect', []) 35152 * .controller('ExampleController', ['$scope', function($scope) { 35153 * $scope.selected = null; 35154 * 35155 * $scope.forceUnknownOption = function() { 35156 * $scope.selected = 'nonsense'; 35157 * }; 35158 * }]) 35159 * .directive('unknownValueError', function() { 35160 * return { 35161 * require: ['ngModel', 'select'], 35162 * link: function(scope, element, attrs, ctrls) { 35163 * var ngModelCtrl = ctrls[0]; 35164 * var selectCtrl = ctrls[1]; 35165 * 35166 * ngModelCtrl.$validators.unknownValue = function(modelValue, viewValue) { 35167 * if (selectCtrl.$isUnknownOptionSelected()) { 35168 * return false; 35169 * } 35170 * 35171 * return true; 35172 * }; 35173 * } 35174 * 35175 * }; 35176 * }); 35177 * </file> 35178 *</example> 35179 * 35180 * 35181 * @example 35182 * ### Set the "required" error when the unknown option is selected. 35183 * 35184 * By default, the "required" error on the ngModelController is only set on a required select 35185 * when the empty option is selected. This example adds a custom directive that also sets the 35186 * error when the unknown option is selected. 35187 * 35188 * <example name="select-unknown-value-required" module="staticSelect"> 35189 * <file name="index.html"> 35190 * <div ng-controller="ExampleController"> 35191 * <form name="myForm"> 35192 * <label for="testSelect"> Select: </label><br> 35193 * <select name="testSelect" ng-model="selected" required unknown-value-required> 35194 * <option value="option-1">Option 1</option> 35195 * <option value="option-2">Option 2</option> 35196 * </select><br> 35197 * <span class="error" ng-if="myForm.testSelect.$error.required">Error: Please select a value</span><br> 35198 * 35199 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 35200 * </form> 35201 * </div> 35202 * </file> 35203 * <file name="app.js"> 35204 * angular.module('staticSelect', []) 35205 * .controller('ExampleController', ['$scope', function($scope) { 35206 * $scope.selected = null; 35207 * 35208 * $scope.forceUnknownOption = function() { 35209 * $scope.selected = 'nonsense'; 35210 * }; 35211 * }]) 35212 * .directive('unknownValueRequired', function() { 35213 * return { 35214 * priority: 1, // This directive must run after the required directive has added its validator 35215 * require: ['ngModel', 'select'], 35216 * link: function(scope, element, attrs, ctrls) { 35217 * var ngModelCtrl = ctrls[0]; 35218 * var selectCtrl = ctrls[1]; 35219 * 35220 * var originalRequiredValidator = ngModelCtrl.$validators.required; 35221 * 35222 * ngModelCtrl.$validators.required = function() { 35223 * if (attrs.required && selectCtrl.$isUnknownOptionSelected()) { 35224 * return false; 35225 * } 35226 * 35227 * return originalRequiredValidator.apply(this, arguments); 35228 * }; 35229 * } 35230 * }; 35231 * }); 35232 * </file> 35233 * <file name="protractor.js" type="protractor"> 35234 * it('should show the error message when the unknown option is selected', function() { 35235 35236 var error = element(by.className('error')); 35237 35238 expect(error.getText()).toBe('Error: Please select a value'); 35239 35240 element(by.cssContainingText('option', 'Option 1')).click(); 35241 35242 expect(error.isPresent()).toBe(false); 35243 35244 element(by.tagName('button')).click(); 35245 35246 expect(error.getText()).toBe('Error: Please select a value'); 35247 }); 35248 * </file> 35249 *</example> 35250 * 35251 * 35252 */ 35253var SelectController = 35254 ['$element', '$scope', /** @this */ function($element, $scope) { 35255 35256 var self = this, 35257 optionsMap = new NgMap(); 35258 35259 self.selectValueMap = {}; // Keys are the hashed values, values the original values 35260 35261 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 35262 self.ngModelCtrl = noopNgModelController; 35263 self.multiple = false; 35264 35265 // The "unknown" option is one that is prepended to the list if the viewValue 35266 // does not match any of the options. When it is rendered the value of the unknown 35267 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 35268 // 35269 // Support: IE 9 only 35270 // We can't just jqLite('<option>') since jqLite is not smart enough 35271 // to create it in <select> and IE barfs otherwise. 35272 self.unknownOption = jqLite(window.document.createElement('option')); 35273 35274 // The empty option is an option with the value '' that the application developer can 35275 // provide inside the select. It is always selectable and indicates that a "null" selection has 35276 // been made by the user. 35277 // If the select has an empty option, and the model of the select is set to "undefined" or "null", 35278 // the empty option is selected. 35279 // If the model is set to a different unmatched value, the unknown option is rendered and 35280 // selected, i.e both are present, because a "null" selection and an unknown value are different. 35281 self.hasEmptyOption = false; 35282 self.emptyOption = undefined; 35283 35284 self.renderUnknownOption = function(val) { 35285 var unknownVal = self.generateUnknownOptionValue(val); 35286 self.unknownOption.val(unknownVal); 35287 $element.prepend(self.unknownOption); 35288 setOptionSelectedStatus(self.unknownOption, true); 35289 $element.val(unknownVal); 35290 }; 35291 35292 self.updateUnknownOption = function(val) { 35293 var unknownVal = self.generateUnknownOptionValue(val); 35294 self.unknownOption.val(unknownVal); 35295 setOptionSelectedStatus(self.unknownOption, true); 35296 $element.val(unknownVal); 35297 }; 35298 35299 self.generateUnknownOptionValue = function(val) { 35300 return '? ' + hashKey(val) + ' ?'; 35301 }; 35302 35303 self.removeUnknownOption = function() { 35304 if (self.unknownOption.parent()) self.unknownOption.remove(); 35305 }; 35306 35307 self.selectEmptyOption = function() { 35308 if (self.emptyOption) { 35309 $element.val(''); 35310 setOptionSelectedStatus(self.emptyOption, true); 35311 } 35312 }; 35313 35314 self.unselectEmptyOption = function() { 35315 if (self.hasEmptyOption) { 35316 setOptionSelectedStatus(self.emptyOption, false); 35317 } 35318 }; 35319 35320 $scope.$on('$destroy', function() { 35321 // disable unknown option so that we don't do work when the whole select is being destroyed 35322 self.renderUnknownOption = noop; 35323 }); 35324 35325 // Read the value of the select control, the implementation of this changes depending 35326 // upon whether the select can have multiple values and whether ngOptions is at work. 35327 self.readValue = function readSingleValue() { 35328 var val = $element.val(); 35329 // ngValue added option values are stored in the selectValueMap, normal interpolations are not 35330 var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val; 35331 35332 if (self.hasOption(realVal)) { 35333 return realVal; 35334 } 35335 35336 return null; 35337 }; 35338 35339 35340 // Write the value to the select control, the implementation of this changes depending 35341 // upon whether the select can have multiple values and whether ngOptions is at work. 35342 self.writeValue = function writeSingleValue(value) { 35343 // Make sure to remove the selected attribute from the previously selected option 35344 // Otherwise, screen readers might get confused 35345 var currentlySelectedOption = $element[0].options[$element[0].selectedIndex]; 35346 if (currentlySelectedOption) setOptionSelectedStatus(jqLite(currentlySelectedOption), false); 35347 35348 if (self.hasOption(value)) { 35349 self.removeUnknownOption(); 35350 35351 var hashedVal = hashKey(value); 35352 $element.val(hashedVal in self.selectValueMap ? hashedVal : value); 35353 35354 // Set selected attribute and property on selected option for screen readers 35355 var selectedOption = $element[0].options[$element[0].selectedIndex]; 35356 setOptionSelectedStatus(jqLite(selectedOption), true); 35357 } else { 35358 self.selectUnknownOrEmptyOption(value); 35359 } 35360 }; 35361 35362 35363 // Tell the select control that an option, with the given value, has been added 35364 self.addOption = function(value, element) { 35365 // Skip comment nodes, as they only pollute the `optionsMap` 35366 if (element[0].nodeType === NODE_TYPE_COMMENT) return; 35367 35368 assertNotHasOwnProperty(value, '"option value"'); 35369 if (value === '') { 35370 self.hasEmptyOption = true; 35371 self.emptyOption = element; 35372 } 35373 var count = optionsMap.get(value) || 0; 35374 optionsMap.set(value, count + 1); 35375 // Only render at the end of a digest. This improves render performance when many options 35376 // are added during a digest and ensures all relevant options are correctly marked as selected 35377 scheduleRender(); 35378 }; 35379 35380 // Tell the select control that an option, with the given value, has been removed 35381 self.removeOption = function(value) { 35382 var count = optionsMap.get(value); 35383 if (count) { 35384 if (count === 1) { 35385 optionsMap.delete(value); 35386 if (value === '') { 35387 self.hasEmptyOption = false; 35388 self.emptyOption = undefined; 35389 } 35390 } else { 35391 optionsMap.set(value, count - 1); 35392 } 35393 } 35394 }; 35395 35396 // Check whether the select control has an option matching the given value 35397 self.hasOption = function(value) { 35398 return !!optionsMap.get(value); 35399 }; 35400 35401 /** 35402 * @ngdoc method 35403 * @name select.SelectController#$hasEmptyOption 35404 * 35405 * @description 35406 * 35407 * Returns `true` if the select element currently has an empty option 35408 * element, i.e. an option that signifies that the select is empty / the selection is null. 35409 * 35410 */ 35411 self.$hasEmptyOption = function() { 35412 return self.hasEmptyOption; 35413 }; 35414 35415 /** 35416 * @ngdoc method 35417 * @name select.SelectController#$isUnknownOptionSelected 35418 * 35419 * @description 35420 * 35421 * Returns `true` if the select element's unknown option is selected. The unknown option is added 35422 * and automatically selected whenever the select model doesn't match any option. 35423 * 35424 */ 35425 self.$isUnknownOptionSelected = function() { 35426 // Presence of the unknown option means it is selected 35427 return $element[0].options[0] === self.unknownOption[0]; 35428 }; 35429 35430 /** 35431 * @ngdoc method 35432 * @name select.SelectController#$isEmptyOptionSelected 35433 * 35434 * @description 35435 * 35436 * Returns `true` if the select element has an empty option and this empty option is currently 35437 * selected. Returns `false` if the select element has no empty option or it is not selected. 35438 * 35439 */ 35440 self.$isEmptyOptionSelected = function() { 35441 return self.hasEmptyOption && $element[0].options[$element[0].selectedIndex] === self.emptyOption[0]; 35442 }; 35443 35444 self.selectUnknownOrEmptyOption = function(value) { 35445 if (value == null && self.emptyOption) { 35446 self.removeUnknownOption(); 35447 self.selectEmptyOption(); 35448 } else if (self.unknownOption.parent().length) { 35449 self.updateUnknownOption(value); 35450 } else { 35451 self.renderUnknownOption(value); 35452 } 35453 }; 35454 35455 var renderScheduled = false; 35456 function scheduleRender() { 35457 if (renderScheduled) return; 35458 renderScheduled = true; 35459 $scope.$$postDigest(function() { 35460 renderScheduled = false; 35461 self.ngModelCtrl.$render(); 35462 }); 35463 } 35464 35465 var updateScheduled = false; 35466 function scheduleViewValueUpdate(renderAfter) { 35467 if (updateScheduled) return; 35468 35469 updateScheduled = true; 35470 35471 $scope.$$postDigest(function() { 35472 if ($scope.$$destroyed) return; 35473 35474 updateScheduled = false; 35475 self.ngModelCtrl.$setViewValue(self.readValue()); 35476 if (renderAfter) self.ngModelCtrl.$render(); 35477 }); 35478 } 35479 35480 35481 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 35482 35483 if (optionAttrs.$attr.ngValue) { 35484 // The value attribute is set by ngValue 35485 var oldVal, hashedVal; 35486 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 35487 35488 var removal; 35489 var previouslySelected = optionElement.prop('selected'); 35490 35491 if (isDefined(hashedVal)) { 35492 self.removeOption(oldVal); 35493 delete self.selectValueMap[hashedVal]; 35494 removal = true; 35495 } 35496 35497 hashedVal = hashKey(newVal); 35498 oldVal = newVal; 35499 self.selectValueMap[hashedVal] = newVal; 35500 self.addOption(newVal, optionElement); 35501 // Set the attribute directly instead of using optionAttrs.$set - this stops the observer 35502 // from firing a second time. Other $observers on value will also get the result of the 35503 // ngValue expression, not the hashed value 35504 optionElement.attr('value', hashedVal); 35505 35506 if (removal && previouslySelected) { 35507 scheduleViewValueUpdate(); 35508 } 35509 35510 }); 35511 } else if (interpolateValueFn) { 35512 // The value attribute is interpolated 35513 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 35514 // This method is overwritten in ngOptions and has side-effects! 35515 self.readValue(); 35516 35517 var removal; 35518 var previouslySelected = optionElement.prop('selected'); 35519 35520 if (isDefined(oldVal)) { 35521 self.removeOption(oldVal); 35522 removal = true; 35523 } 35524 oldVal = newVal; 35525 self.addOption(newVal, optionElement); 35526 35527 if (removal && previouslySelected) { 35528 scheduleViewValueUpdate(); 35529 } 35530 }); 35531 } else if (interpolateTextFn) { 35532 // The text content is interpolated 35533 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 35534 optionAttrs.$set('value', newVal); 35535 var previouslySelected = optionElement.prop('selected'); 35536 if (oldVal !== newVal) { 35537 self.removeOption(oldVal); 35538 } 35539 self.addOption(newVal, optionElement); 35540 35541 if (oldVal && previouslySelected) { 35542 scheduleViewValueUpdate(); 35543 } 35544 }); 35545 } else { 35546 // The value attribute is static 35547 self.addOption(optionAttrs.value, optionElement); 35548 } 35549 35550 35551 optionAttrs.$observe('disabled', function(newVal) { 35552 35553 // Since model updates will also select disabled options (like ngOptions), 35554 // we only have to handle options becoming disabled, not enabled 35555 35556 if (newVal === 'true' || newVal && optionElement.prop('selected')) { 35557 if (self.multiple) { 35558 scheduleViewValueUpdate(true); 35559 } else { 35560 self.ngModelCtrl.$setViewValue(null); 35561 self.ngModelCtrl.$render(); 35562 } 35563 } 35564 }); 35565 35566 optionElement.on('$destroy', function() { 35567 var currentValue = self.readValue(); 35568 var removeValue = optionAttrs.value; 35569 35570 self.removeOption(removeValue); 35571 scheduleRender(); 35572 35573 if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 || 35574 currentValue === removeValue 35575 ) { 35576 // When multiple (selected) options are destroyed at the same time, we don't want 35577 // to run a model update for each of them. Instead, run a single update in the $$postDigest 35578 scheduleViewValueUpdate(true); 35579 } 35580 }); 35581 }; 35582}]; 35583 35584/** 35585 * @ngdoc directive 35586 * @name select 35587 * @restrict E 35588 * 35589 * @description 35590 * HTML `select` element with AngularJS data-binding. 35591 * 35592 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 35593 * between the scope and the `<select>` control (including setting default values). 35594 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 35595 * {@link ngOptions `ngOptions`} directives. 35596 * 35597 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 35598 * to the model identified by the `ngModel` directive. With static or repeated options, this is 35599 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 35600 * Value and textContent can be interpolated. 35601 * 35602 * The {@link select.SelectController select controller} exposes utility functions that can be used 35603 * to manipulate the select's behavior. 35604 * 35605 * ## Matching model and option values 35606 * 35607 * In general, the match between the model and an option is evaluated by strictly comparing the model 35608 * value against the value of the available options. 35609 * 35610 * If you are setting the option value with the option's `value` attribute, or textContent, the 35611 * value will always be a `string` which means that the model value must also be a string. 35612 * Otherwise the `select` directive cannot match them correctly. 35613 * 35614 * To bind the model to a non-string value, you can use one of the following strategies: 35615 * - the {@link ng.ngOptions `ngOptions`} directive 35616 * ({@link ng.select#using-select-with-ngoptions-and-setting-a-default-value}) 35617 * - the {@link ng.ngValue `ngValue`} directive, which allows arbitrary expressions to be 35618 * option values ({@link ng.select#using-ngvalue-to-bind-the-model-to-an-array-of-objects Example}) 35619 * - model $parsers / $formatters to convert the string value 35620 * ({@link ng.select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting Example}) 35621 * 35622 * If the viewValue of `ngModel` does not match any of the options, then the control 35623 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 35624 * 35625 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 35626 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 35627 * option. See example below for demonstration. 35628 * 35629 * ## Choosing between `ngRepeat` and `ngOptions` 35630 * 35631 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 35632 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits: 35633 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 35634 * comprehension expression 35635 * - reduced memory consumption by not creating a new scope for each repeated instance 35636 * - increased render speed by creating the options in a documentFragment instead of individually 35637 * 35638 * Specifically, select with repeated options slows down significantly starting at 2000 options in 35639 * Chrome and Internet Explorer / Edge. 35640 * 35641 * 35642 * @param {string} ngModel Assignable AngularJS expression to data-bind to. 35643 * @param {string=} name Property name of the form under which the control is published. 35644 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 35645 * bound to the model as an array. 35646 * @param {string=} required Sets `required` validation error key if the value is not entered. 35647 * @param {string=} ngRequired Adds required attribute and required validation constraint to 35648 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 35649 * when you want to data-bind to the required attribute. 35650 * @param {string=} ngChange AngularJS expression to be executed when selected option(s) changes due to user 35651 * interaction with the select element. 35652 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 35653 * set on the model on selection. See {@link ngOptions `ngOptions`}. 35654 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the 35655 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive. 35656 * 35657 * 35658 * @example 35659 * ### Simple `select` elements with static options 35660 * 35661 * <example name="static-select" module="staticSelect"> 35662 * <file name="index.html"> 35663 * <div ng-controller="ExampleController"> 35664 * <form name="myForm"> 35665 * <label for="singleSelect"> Single select: </label><br> 35666 * <select name="singleSelect" ng-model="data.singleSelect"> 35667 * <option value="option-1">Option 1</option> 35668 * <option value="option-2">Option 2</option> 35669 * </select><br> 35670 * 35671 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 35672 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 35673 * <option value="">---Please select---</option> <!-- not selected / blank option --> 35674 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 35675 * <option value="option-2">Option 2</option> 35676 * </select><br> 35677 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 35678 * <tt>singleSelect = {{data.singleSelect}}</tt> 35679 * 35680 * <hr> 35681 * <label for="multipleSelect"> Multiple select: </label><br> 35682 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 35683 * <option value="option-1">Option 1</option> 35684 * <option value="option-2">Option 2</option> 35685 * <option value="option-3">Option 3</option> 35686 * </select><br> 35687 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 35688 * </form> 35689 * </div> 35690 * </file> 35691 * <file name="app.js"> 35692 * angular.module('staticSelect', []) 35693 * .controller('ExampleController', ['$scope', function($scope) { 35694 * $scope.data = { 35695 * singleSelect: null, 35696 * multipleSelect: [], 35697 * option1: 'option-1' 35698 * }; 35699 * 35700 * $scope.forceUnknownOption = function() { 35701 * $scope.data.singleSelect = 'nonsense'; 35702 * }; 35703 * }]); 35704 * </file> 35705 *</example> 35706 * 35707 * @example 35708 * ### Using `ngRepeat` to generate `select` options 35709 * <example name="select-ngrepeat" module="ngrepeatSelect"> 35710 * <file name="index.html"> 35711 * <div ng-controller="ExampleController"> 35712 * <form name="myForm"> 35713 * <label for="repeatSelect"> Repeat select: </label> 35714 * <select name="repeatSelect" id="repeatSelect" ng-model="data.model"> 35715 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 35716 * </select> 35717 * </form> 35718 * <hr> 35719 * <tt>model = {{data.model}}</tt><br/> 35720 * </div> 35721 * </file> 35722 * <file name="app.js"> 35723 * angular.module('ngrepeatSelect', []) 35724 * .controller('ExampleController', ['$scope', function($scope) { 35725 * $scope.data = { 35726 * model: null, 35727 * availableOptions: [ 35728 * {id: '1', name: 'Option A'}, 35729 * {id: '2', name: 'Option B'}, 35730 * {id: '3', name: 'Option C'} 35731 * ] 35732 * }; 35733 * }]); 35734 * </file> 35735 *</example> 35736 * 35737 * @example 35738 * ### Using `ngValue` to bind the model to an array of objects 35739 * <example name="select-ngvalue" module="ngvalueSelect"> 35740 * <file name="index.html"> 35741 * <div ng-controller="ExampleController"> 35742 * <form name="myForm"> 35743 * <label for="ngvalueselect"> ngvalue select: </label> 35744 * <select size="6" name="ngvalueselect" ng-model="data.model" multiple> 35745 * <option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option> 35746 * </select> 35747 * </form> 35748 * <hr> 35749 * <pre>model = {{data.model | json}}</pre><br/> 35750 * </div> 35751 * </file> 35752 * <file name="app.js"> 35753 * angular.module('ngvalueSelect', []) 35754 * .controller('ExampleController', ['$scope', function($scope) { 35755 * $scope.data = { 35756 * model: null, 35757 * availableOptions: [ 35758 {value: 'myString', name: 'string'}, 35759 {value: 1, name: 'integer'}, 35760 {value: true, name: 'boolean'}, 35761 {value: null, name: 'null'}, 35762 {value: {prop: 'value'}, name: 'object'}, 35763 {value: ['a'], name: 'array'} 35764 * ] 35765 * }; 35766 * }]); 35767 * </file> 35768 *</example> 35769 * 35770 * @example 35771 * ### Using `select` with `ngOptions` and setting a default value 35772 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 35773 * 35774 * <example name="select-with-default-values" module="defaultValueSelect"> 35775 * <file name="index.html"> 35776 * <div ng-controller="ExampleController"> 35777 * <form name="myForm"> 35778 * <label for="mySelect">Make a choice:</label> 35779 * <select name="mySelect" id="mySelect" 35780 * ng-options="option.name for option in data.availableOptions track by option.id" 35781 * ng-model="data.selectedOption"></select> 35782 * </form> 35783 * <hr> 35784 * <tt>option = {{data.selectedOption}}</tt><br/> 35785 * </div> 35786 * </file> 35787 * <file name="app.js"> 35788 * angular.module('defaultValueSelect', []) 35789 * .controller('ExampleController', ['$scope', function($scope) { 35790 * $scope.data = { 35791 * availableOptions: [ 35792 * {id: '1', name: 'Option A'}, 35793 * {id: '2', name: 'Option B'}, 35794 * {id: '3', name: 'Option C'} 35795 * ], 35796 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 35797 * }; 35798 * }]); 35799 * </file> 35800 *</example> 35801 * 35802 * @example 35803 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 35804 * 35805 * <example name="select-with-non-string-options" module="nonStringSelect"> 35806 * <file name="index.html"> 35807 * <select ng-model="model.id" convert-to-number> 35808 * <option value="0">Zero</option> 35809 * <option value="1">One</option> 35810 * <option value="2">Two</option> 35811 * </select> 35812 * {{ model }} 35813 * </file> 35814 * <file name="app.js"> 35815 * angular.module('nonStringSelect', []) 35816 * .run(function($rootScope) { 35817 * $rootScope.model = { id: 2 }; 35818 * }) 35819 * .directive('convertToNumber', function() { 35820 * return { 35821 * require: 'ngModel', 35822 * link: function(scope, element, attrs, ngModel) { 35823 * ngModel.$parsers.push(function(val) { 35824 * return parseInt(val, 10); 35825 * }); 35826 * ngModel.$formatters.push(function(val) { 35827 * return '' + val; 35828 * }); 35829 * } 35830 * }; 35831 * }); 35832 * </file> 35833 * <file name="protractor.js" type="protractor"> 35834 * it('should initialize to model', function() { 35835 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 35836 * }); 35837 * </file> 35838 * </example> 35839 * 35840 */ 35841var selectDirective = function() { 35842 35843 return { 35844 restrict: 'E', 35845 require: ['select', '?ngModel'], 35846 controller: SelectController, 35847 priority: 1, 35848 link: { 35849 pre: selectPreLink, 35850 post: selectPostLink 35851 } 35852 }; 35853 35854 function selectPreLink(scope, element, attr, ctrls) { 35855 35856 var selectCtrl = ctrls[0]; 35857 var ngModelCtrl = ctrls[1]; 35858 35859 // if ngModel is not defined, we don't need to do anything but set the registerOption 35860 // function to noop, so options don't get added internally 35861 if (!ngModelCtrl) { 35862 selectCtrl.registerOption = noop; 35863 return; 35864 } 35865 35866 35867 selectCtrl.ngModelCtrl = ngModelCtrl; 35868 35869 // When the selected item(s) changes we delegate getting the value of the select control 35870 // to the `readValue` method, which can be changed if the select can have multiple 35871 // selected values or if the options are being generated by `ngOptions` 35872 element.on('change', function() { 35873 selectCtrl.removeUnknownOption(); 35874 scope.$apply(function() { 35875 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 35876 }); 35877 }); 35878 35879 // If the select allows multiple values then we need to modify how we read and write 35880 // values from and to the control; also what it means for the value to be empty and 35881 // we have to add an extra watch since ngModel doesn't work well with arrays - it 35882 // doesn't trigger rendering if only an item in the array changes. 35883 if (attr.multiple) { 35884 selectCtrl.multiple = true; 35885 35886 // Read value now needs to check each option to see if it is selected 35887 selectCtrl.readValue = function readMultipleValue() { 35888 var array = []; 35889 forEach(element.find('option'), function(option) { 35890 if (option.selected && !option.disabled) { 35891 var val = option.value; 35892 array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val); 35893 } 35894 }); 35895 return array; 35896 }; 35897 35898 // Write value now needs to set the selected property of each matching option 35899 selectCtrl.writeValue = function writeMultipleValue(value) { 35900 forEach(element.find('option'), function(option) { 35901 var shouldBeSelected = !!value && (includes(value, option.value) || 35902 includes(value, selectCtrl.selectValueMap[option.value])); 35903 var currentlySelected = option.selected; 35904 35905 // Support: IE 9-11 only, Edge 12-15+ 35906 // In IE and Edge adding options to the selection via shift+click/UP/DOWN 35907 // will de-select already selected options if "selected" on those options was set 35908 // more than once (i.e. when the options were already selected) 35909 // So we only modify the selected property if necessary. 35910 // Note: this behavior cannot be replicated via unit tests because it only shows in the 35911 // actual user interface. 35912 if (shouldBeSelected !== currentlySelected) { 35913 setOptionSelectedStatus(jqLite(option), shouldBeSelected); 35914 } 35915 35916 }); 35917 }; 35918 35919 // we have to do it on each watch since ngModel watches reference, but 35920 // we need to work of an array, so we need to see if anything was inserted/removed 35921 var lastView, lastViewRef = NaN; 35922 scope.$watch(function selectMultipleWatch() { 35923 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 35924 lastView = shallowCopy(ngModelCtrl.$viewValue); 35925 ngModelCtrl.$render(); 35926 } 35927 lastViewRef = ngModelCtrl.$viewValue; 35928 }); 35929 35930 // If we are a multiple select then value is now a collection 35931 // so the meaning of $isEmpty changes 35932 ngModelCtrl.$isEmpty = function(value) { 35933 return !value || value.length === 0; 35934 }; 35935 35936 } 35937 } 35938 35939 function selectPostLink(scope, element, attrs, ctrls) { 35940 // if ngModel is not defined, we don't need to do anything 35941 var ngModelCtrl = ctrls[1]; 35942 if (!ngModelCtrl) return; 35943 35944 var selectCtrl = ctrls[0]; 35945 35946 // We delegate rendering to the `writeValue` method, which can be changed 35947 // if the select can have multiple selected values or if the options are being 35948 // generated by `ngOptions`. 35949 // This must be done in the postLink fn to prevent $render to be called before 35950 // all nodes have been linked correctly. 35951 ngModelCtrl.$render = function() { 35952 selectCtrl.writeValue(ngModelCtrl.$viewValue); 35953 }; 35954 } 35955}; 35956 35957 35958// The option directive is purely designed to communicate the existence (or lack of) 35959// of dynamically created (and destroyed) option elements to their containing select 35960// directive via its controller. 35961var optionDirective = ['$interpolate', function($interpolate) { 35962 return { 35963 restrict: 'E', 35964 priority: 100, 35965 compile: function(element, attr) { 35966 var interpolateValueFn, interpolateTextFn; 35967 35968 if (isDefined(attr.ngValue)) { 35969 // Will be handled by registerOption 35970 } else if (isDefined(attr.value)) { 35971 // If the value attribute is defined, check if it contains an interpolation 35972 interpolateValueFn = $interpolate(attr.value, true); 35973 } else { 35974 // If the value attribute is not defined then we fall back to the 35975 // text content of the option element, which may be interpolated 35976 interpolateTextFn = $interpolate(element.text(), true); 35977 if (!interpolateTextFn) { 35978 attr.$set('value', element.text()); 35979 } 35980 } 35981 35982 return function(scope, element, attr) { 35983 // This is an optimization over using ^^ since we don't want to have to search 35984 // all the way to the root of the DOM for every single option element 35985 var selectCtrlName = '$selectController', 35986 parent = element.parent(), 35987 selectCtrl = parent.data(selectCtrlName) || 35988 parent.parent().data(selectCtrlName); // in case we are in optgroup 35989 35990 if (selectCtrl) { 35991 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 35992 } 35993 }; 35994 } 35995 }; 35996}]; 35997 35998/** 35999 * @ngdoc directive 36000 * @name ngRequired 36001 * @restrict A 36002 * 36003 * @param {expression} ngRequired AngularJS expression. If it evaluates to `true`, it sets the 36004 * `required` attribute to the element and adds the `required` 36005 * {@link ngModel.NgModelController#$validators `validator`}. 36006 * 36007 * @description 36008 * 36009 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36010 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 36011 * applied to custom controls. 36012 * 36013 * The directive sets the `required` attribute on the element if the AngularJS expression inside 36014 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 36015 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 36016 * for more info. 36017 * 36018 * The validator will set the `required` error key to true if the `required` attribute is set and 36019 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the 36020 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 36021 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 36022 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 36023 * 36024 * @example 36025 * <example name="ngRequiredDirective" module="ngRequiredExample"> 36026 * <file name="index.html"> 36027 * <script> 36028 * angular.module('ngRequiredExample', []) 36029 * .controller('ExampleController', ['$scope', function($scope) { 36030 * $scope.required = true; 36031 * }]); 36032 * </script> 36033 * <div ng-controller="ExampleController"> 36034 * <form name="form"> 36035 * <label for="required">Toggle required: </label> 36036 * <input type="checkbox" ng-model="required" id="required" /> 36037 * <br> 36038 * <label for="input">This input must be filled if `required` is true: </label> 36039 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 36040 * <hr> 36041 * required error set? = <code>{{form.input.$error.required}}</code><br> 36042 * model = <code>{{model}}</code> 36043 * </form> 36044 * </div> 36045 * </file> 36046 * <file name="protractor.js" type="protractor"> 36047 var required = element(by.binding('form.input.$error.required')); 36048 var model = element(by.binding('model')); 36049 var input = element(by.id('input')); 36050 36051 it('should set the required error', function() { 36052 expect(required.getText()).toContain('true'); 36053 36054 input.sendKeys('123'); 36055 expect(required.getText()).not.toContain('true'); 36056 expect(model.getText()).toContain('123'); 36057 }); 36058 * </file> 36059 * </example> 36060 */ 36061var requiredDirective = ['$parse', function($parse) { 36062 return { 36063 restrict: 'A', 36064 require: '?ngModel', 36065 link: function(scope, elm, attr, ctrl) { 36066 if (!ctrl) return; 36067 // For boolean attributes like required, presence means true 36068 var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope); 36069 36070 if (!attr.ngRequired) { 36071 // force truthy in case we are on non input element 36072 // (input elements do this automatically for boolean attributes like required) 36073 attr.required = true; 36074 } 36075 36076 ctrl.$validators.required = function(modelValue, viewValue) { 36077 return !value || !ctrl.$isEmpty(viewValue); 36078 }; 36079 36080 attr.$observe('required', function(newVal) { 36081 36082 if (value !== newVal) { 36083 value = newVal; 36084 ctrl.$validate(); 36085 } 36086 }); 36087 } 36088 }; 36089}]; 36090 36091/** 36092 * @ngdoc directive 36093 * @name ngPattern 36094 * @restrict A 36095 * 36096 * @param {expression|RegExp} ngPattern AngularJS expression that must evaluate to a `RegExp` or a `String` 36097 * parsable into a `RegExp`, or a `RegExp` literal. See above for 36098 * more details. 36099 * 36100 * @description 36101 * 36102 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36103 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 36104 * 36105 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 36106 * does not match a RegExp which is obtained from the `ngPattern` attribute value: 36107 * - the value is an AngularJS expression: 36108 * - If the expression evaluates to a RegExp object, then this is used directly. 36109 * - If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 36110 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 36111 * - If the value is a RegExp literal, e.g. `ngPattern="/^\d+$/"`, it is used directly. 36112 * 36113 * <div class="alert alert-info"> 36114 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 36115 * start at the index of the last search's match, thus not taking the whole input value into 36116 * account. 36117 * </div> 36118 * 36119 * <div class="alert alert-info"> 36120 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 36121 * differences: 36122 * <ol> 36123 * <li> 36124 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 36125 * not available. 36126 * </li> 36127 * <li> 36128 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 36129 * interpolated. 36130 * </li> 36131 * </ol> 36132 * </div> 36133 * 36134 * @example 36135 * <example name="ngPatternDirective" module="ngPatternExample"> 36136 * <file name="index.html"> 36137 * <script> 36138 * angular.module('ngPatternExample', []) 36139 * .controller('ExampleController', ['$scope', function($scope) { 36140 * $scope.regex = '\\d+'; 36141 * }]); 36142 * </script> 36143 * <div ng-controller="ExampleController"> 36144 * <form name="form"> 36145 * <label for="regex">Set a pattern (regex string): </label> 36146 * <input type="text" ng-model="regex" id="regex" /> 36147 * <br> 36148 * <label for="input">This input is restricted by the current pattern: </label> 36149 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 36150 * <hr> 36151 * input valid? = <code>{{form.input.$valid}}</code><br> 36152 * model = <code>{{model}}</code> 36153 * </form> 36154 * </div> 36155 * </file> 36156 * <file name="protractor.js" type="protractor"> 36157 var model = element(by.binding('model')); 36158 var input = element(by.id('input')); 36159 36160 it('should validate the input with the default pattern', function() { 36161 input.sendKeys('aaa'); 36162 expect(model.getText()).not.toContain('aaa'); 36163 36164 input.clear().then(function() { 36165 input.sendKeys('123'); 36166 expect(model.getText()).toContain('123'); 36167 }); 36168 }); 36169 * </file> 36170 * </example> 36171 */ 36172var patternDirective = ['$parse', function($parse) { 36173 return { 36174 restrict: 'A', 36175 require: '?ngModel', 36176 compile: function(tElm, tAttr) { 36177 var patternExp; 36178 var parseFn; 36179 36180 if (tAttr.ngPattern) { 36181 patternExp = tAttr.ngPattern; 36182 36183 // ngPattern might be a scope expression, or an inlined regex, which is not parsable. 36184 // We get value of the attribute here, so we can compare the old and the new value 36185 // in the observer to avoid unnecessary validations 36186 if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) { 36187 parseFn = function() { return tAttr.ngPattern; }; 36188 } else { 36189 parseFn = $parse(tAttr.ngPattern); 36190 } 36191 } 36192 36193 return function(scope, elm, attr, ctrl) { 36194 if (!ctrl) return; 36195 36196 var attrVal = attr.pattern; 36197 36198 if (attr.ngPattern) { 36199 attrVal = parseFn(scope); 36200 } else { 36201 patternExp = attr.pattern; 36202 } 36203 36204 var regexp = parsePatternAttr(attrVal, patternExp, elm); 36205 36206 attr.$observe('pattern', function(newVal) { 36207 var oldRegexp = regexp; 36208 36209 regexp = parsePatternAttr(newVal, patternExp, elm); 36210 36211 if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) { 36212 ctrl.$validate(); 36213 } 36214 }); 36215 36216 ctrl.$validators.pattern = function(modelValue, viewValue) { 36217 // HTML5 pattern constraint validates the input value, so we validate the viewValue 36218 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 36219 }; 36220 }; 36221 } 36222 36223 }; 36224}]; 36225 36226/** 36227 * @ngdoc directive 36228 * @name ngMaxlength 36229 * @restrict A 36230 * 36231 * @param {expression} ngMaxlength AngularJS expression that must evaluate to a `Number` or `String` 36232 * parsable into a `Number`. Used as value for the `maxlength` 36233 * {@link ngModel.NgModelController#$validators validator}. 36234 * 36235 * @description 36236 * 36237 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36238 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 36239 * 36240 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 36241 * is longer than the integer obtained by evaluating the AngularJS expression given in the 36242 * `ngMaxlength` attribute value. 36243 * 36244 * <div class="alert alert-info"> 36245 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 36246 * differences: 36247 * <ol> 36248 * <li> 36249 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 36250 * validation is not available. 36251 * </li> 36252 * <li> 36253 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 36254 * interpolated. 36255 * </li> 36256 * </ol> 36257 * </div> 36258 * 36259 * @example 36260 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 36261 * <file name="index.html"> 36262 * <script> 36263 * angular.module('ngMaxlengthExample', []) 36264 * .controller('ExampleController', ['$scope', function($scope) { 36265 * $scope.maxlength = 5; 36266 * }]); 36267 * </script> 36268 * <div ng-controller="ExampleController"> 36269 * <form name="form"> 36270 * <label for="maxlength">Set a maxlength: </label> 36271 * <input type="number" ng-model="maxlength" id="maxlength" /> 36272 * <br> 36273 * <label for="input">This input is restricted by the current maxlength: </label> 36274 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 36275 * <hr> 36276 * input valid? = <code>{{form.input.$valid}}</code><br> 36277 * model = <code>{{model}}</code> 36278 * </form> 36279 * </div> 36280 * </file> 36281 * <file name="protractor.js" type="protractor"> 36282 var model = element(by.binding('model')); 36283 var input = element(by.id('input')); 36284 36285 it('should validate the input with the default maxlength', function() { 36286 input.sendKeys('abcdef'); 36287 expect(model.getText()).not.toContain('abcdef'); 36288 36289 input.clear().then(function() { 36290 input.sendKeys('abcde'); 36291 expect(model.getText()).toContain('abcde'); 36292 }); 36293 }); 36294 * </file> 36295 * </example> 36296 */ 36297var maxlengthDirective = ['$parse', function($parse) { 36298 return { 36299 restrict: 'A', 36300 require: '?ngModel', 36301 link: function(scope, elm, attr, ctrl) { 36302 if (!ctrl) return; 36303 36304 var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope); 36305 var maxlengthParsed = parseLength(maxlength); 36306 36307 attr.$observe('maxlength', function(value) { 36308 if (maxlength !== value) { 36309 maxlengthParsed = parseLength(value); 36310 maxlength = value; 36311 ctrl.$validate(); 36312 } 36313 }); 36314 ctrl.$validators.maxlength = function(modelValue, viewValue) { 36315 return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed); 36316 }; 36317 } 36318 }; 36319}]; 36320 36321/** 36322 * @ngdoc directive 36323 * @name ngMinlength 36324 * @restrict A 36325 * 36326 * @param {expression} ngMinlength AngularJS expression that must evaluate to a `Number` or `String` 36327 * parsable into a `Number`. Used as value for the `minlength` 36328 * {@link ngModel.NgModelController#$validators validator}. 36329 * 36330 * @description 36331 * 36332 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 36333 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 36334 * 36335 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 36336 * is shorter than the integer obtained by evaluating the AngularJS expression given in the 36337 * `ngMinlength` attribute value. 36338 * 36339 * <div class="alert alert-info"> 36340 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 36341 * differences: 36342 * <ol> 36343 * <li> 36344 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 36345 * validation is not available. 36346 * </li> 36347 * <li> 36348 * The `ngMinlength` value must be an expression, while the `minlength` value must be 36349 * interpolated. 36350 * </li> 36351 * </ol> 36352 * </div> 36353 * 36354 * @example 36355 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 36356 * <file name="index.html"> 36357 * <script> 36358 * angular.module('ngMinlengthExample', []) 36359 * .controller('ExampleController', ['$scope', function($scope) { 36360 * $scope.minlength = 3; 36361 * }]); 36362 * </script> 36363 * <div ng-controller="ExampleController"> 36364 * <form name="form"> 36365 * <label for="minlength">Set a minlength: </label> 36366 * <input type="number" ng-model="minlength" id="minlength" /> 36367 * <br> 36368 * <label for="input">This input is restricted by the current minlength: </label> 36369 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 36370 * <hr> 36371 * input valid? = <code>{{form.input.$valid}}</code><br> 36372 * model = <code>{{model}}</code> 36373 * </form> 36374 * </div> 36375 * </file> 36376 * <file name="protractor.js" type="protractor"> 36377 var model = element(by.binding('model')); 36378 var input = element(by.id('input')); 36379 36380 it('should validate the input with the default minlength', function() { 36381 input.sendKeys('ab'); 36382 expect(model.getText()).not.toContain('ab'); 36383 36384 input.sendKeys('abc'); 36385 expect(model.getText()).toContain('abc'); 36386 }); 36387 * </file> 36388 * </example> 36389 */ 36390var minlengthDirective = ['$parse', function($parse) { 36391 return { 36392 restrict: 'A', 36393 require: '?ngModel', 36394 link: function(scope, elm, attr, ctrl) { 36395 if (!ctrl) return; 36396 36397 var minlength = attr.minlength || $parse(attr.ngMinlength)(scope); 36398 var minlengthParsed = parseLength(minlength) || -1; 36399 36400 attr.$observe('minlength', function(value) { 36401 if (minlength !== value) { 36402 minlengthParsed = parseLength(value) || -1; 36403 minlength = value; 36404 ctrl.$validate(); 36405 } 36406 36407 }); 36408 ctrl.$validators.minlength = function(modelValue, viewValue) { 36409 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed; 36410 }; 36411 } 36412 }; 36413}]; 36414 36415 36416function parsePatternAttr(regex, patternExp, elm) { 36417 if (!regex) return undefined; 36418 36419 if (isString(regex)) { 36420 regex = new RegExp('^' + regex + '$'); 36421 } 36422 36423 if (!regex.test) { 36424 throw minErr('ngPattern')('noregexp', 36425 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 36426 regex, startingTag(elm)); 36427 } 36428 36429 return regex; 36430} 36431 36432function parseLength(val) { 36433 var intVal = toInt(val); 36434 return isNumberNaN(intVal) ? -1 : intVal; 36435} 36436 36437if (window.angular.bootstrap) { 36438 // AngularJS is already loaded, so we can return here... 36439 if (window.console) { 36440 console.log('WARNING: Tried to load AngularJS more than once.'); 36441 } 36442 return; 36443} 36444 36445// try to bind to jquery now so that one can write jqLite(fn) 36446// but we will rebind on bootstrap again. 36447bindJQuery(); 36448 36449publishExternalAPI(angular); 36450 36451angular.module("ngLocale", [], ["$provide", function($provide) { 36452var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 36453function getDecimals(n) { 36454 n = n + ''; 36455 var i = n.indexOf('.'); 36456 return (i == -1) ? 0 : n.length - i - 1; 36457} 36458 36459function getVF(n, opt_precision) { 36460 var v = opt_precision; 36461 36462 if (undefined === v) { 36463 v = Math.min(getDecimals(n), 3); 36464 } 36465 36466 var base = Math.pow(10, v); 36467 var f = ((n * base) | 0) % base; 36468 return {v: v, f: f}; 36469} 36470 36471$provide.value("$locale", { 36472 "DATETIME_FORMATS": { 36473 "AMPMS": [ 36474 "AM", 36475 "PM" 36476 ], 36477 "DAY": [ 36478 "Sunday", 36479 "Monday", 36480 "Tuesday", 36481 "Wednesday", 36482 "Thursday", 36483 "Friday", 36484 "Saturday" 36485 ], 36486 "ERANAMES": [ 36487 "Before Christ", 36488 "Anno Domini" 36489 ], 36490 "ERAS": [ 36491 "BC", 36492 "AD" 36493 ], 36494 "FIRSTDAYOFWEEK": 6, 36495 "MONTH": [ 36496 "January", 36497 "February", 36498 "March", 36499 "April", 36500 "May", 36501 "June", 36502 "July", 36503 "August", 36504 "September", 36505 "October", 36506 "November", 36507 "December" 36508 ], 36509 "SHORTDAY": [ 36510 "Sun", 36511 "Mon", 36512 "Tue", 36513 "Wed", 36514 "Thu", 36515 "Fri", 36516 "Sat" 36517 ], 36518 "SHORTMONTH": [ 36519 "Jan", 36520 "Feb", 36521 "Mar", 36522 "Apr", 36523 "May", 36524 "Jun", 36525 "Jul", 36526 "Aug", 36527 "Sep", 36528 "Oct", 36529 "Nov", 36530 "Dec" 36531 ], 36532 "STANDALONEMONTH": [ 36533 "January", 36534 "February", 36535 "March", 36536 "April", 36537 "May", 36538 "June", 36539 "July", 36540 "August", 36541 "September", 36542 "October", 36543 "November", 36544 "December" 36545 ], 36546 "WEEKENDRANGE": [ 36547 5, 36548 6 36549 ], 36550 "fullDate": "EEEE, MMMM d, y", 36551 "longDate": "MMMM d, y", 36552 "medium": "MMM d, y h:mm:ss a", 36553 "mediumDate": "MMM d, y", 36554 "mediumTime": "h:mm:ss a", 36555 "short": "M/d/yy h:mm a", 36556 "shortDate": "M/d/yy", 36557 "shortTime": "h:mm a" 36558 }, 36559 "NUMBER_FORMATS": { 36560 "CURRENCY_SYM": "$", 36561 "DECIMAL_SEP": ".", 36562 "GROUP_SEP": ",", 36563 "PATTERNS": [ 36564 { 36565 "gSize": 3, 36566 "lgSize": 3, 36567 "maxFrac": 3, 36568 "minFrac": 0, 36569 "minInt": 1, 36570 "negPre": "-", 36571 "negSuf": "", 36572 "posPre": "", 36573 "posSuf": "" 36574 }, 36575 { 36576 "gSize": 3, 36577 "lgSize": 3, 36578 "maxFrac": 2, 36579 "minFrac": 2, 36580 "minInt": 1, 36581 "negPre": "-\u00a4", 36582 "negSuf": "", 36583 "posPre": "\u00a4", 36584 "posSuf": "" 36585 } 36586 ] 36587 }, 36588 "id": "en-us", 36589 "localeID": "en_US", 36590 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} 36591}); 36592}]); 36593 36594 jqLite(function() { 36595 angularInit(window.document, bootstrap); 36596 }); 36597 36598})(window); 36599 36600!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend(window.angular.element('<style>').text('@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}'));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.