1/** 2 * @license AngularJS v1.5.5 3 * (c) 2010-2016 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function(window) {'use strict'; 7 8/** 9 * @description 10 * 11 * This object provides a utility for producing rich Error messages within 12 * Angular. It can be called as follows: 13 * 14 * var exampleMinErr = minErr('example'); 15 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 16 * 17 * The above creates an instance of minErr in the example namespace. The 18 * resulting error will have a namespaced error code of example.one. The 19 * resulting error will replace {0} with the value of foo, and {1} with the 20 * value of bar. The object is not restricted in the number of arguments it can 21 * take. 22 * 23 * If fewer arguments are specified than necessary for interpolation, the extra 24 * interpolation markers will be preserved in the final string. 25 * 26 * Since data will be parsed statically during a build step, some restrictions 27 * are applied with respect to how minErr instances are created and called. 28 * Instances should have names of the form namespaceMinErr for a minErr created 29 * using minErr('namespace') . Error codes, namespaces and template strings 30 * should all be static strings, not variables or general expressions. 31 * 32 * @param {string} module The namespace to use for the new minErr instance. 33 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 34 * error from returned function, for cases when a particular type of error is useful. 35 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 36 */ 37 38function minErr(module, ErrorConstructor) { 39 ErrorConstructor = ErrorConstructor || Error; 40 return function() { 41 var SKIP_INDEXES = 2; 42 43 var templateArgs = arguments, 44 code = templateArgs[0], 45 message = '[' + (module ? module + ':' : '') + code + '] ', 46 template = templateArgs[1], 47 paramPrefix, i; 48 49 message += template.replace(/\{\d+\}/g, function(match) { 50 var index = +match.slice(1, -1), 51 shiftedIndex = index + SKIP_INDEXES; 52 53 if (shiftedIndex < templateArgs.length) { 54 return toDebugString(templateArgs[shiftedIndex]); 55 } 56 57 return match; 58 }); 59 60 message += '\nhttp://errors.angularjs.org/1.5.5/' + 61 (module ? module + '/' : '') + code; 62 63 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { 64 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + 65 encodeURIComponent(toDebugString(templateArgs[i])); 66 } 67 68 return new ErrorConstructor(message); 69 }; 70} 71 72/* We need to tell jshint what variables are being exported */ 73/* global angular: true, 74 msie: true, 75 jqLite: true, 76 jQuery: true, 77 slice: true, 78 splice: true, 79 push: true, 80 toString: true, 81 ngMinErr: true, 82 angularModule: true, 83 uid: true, 84 REGEX_STRING_REGEXP: true, 85 VALIDITY_STATE_PROPERTY: true, 86 87 lowercase: true, 88 uppercase: true, 89 manualLowercase: true, 90 manualUppercase: true, 91 nodeName_: true, 92 isArrayLike: true, 93 forEach: true, 94 forEachSorted: true, 95 reverseParams: true, 96 nextUid: true, 97 setHashKey: true, 98 extend: true, 99 toInt: true, 100 inherit: true, 101 merge: true, 102 noop: true, 103 identity: true, 104 valueFn: true, 105 isUndefined: true, 106 isDefined: true, 107 isObject: true, 108 isBlankObject: true, 109 isString: true, 110 isNumber: true, 111 isDate: true, 112 isArray: true, 113 isFunction: true, 114 isRegExp: true, 115 isWindow: true, 116 isScope: true, 117 isFile: true, 118 isFormData: true, 119 isBlob: true, 120 isBoolean: true, 121 isPromiseLike: true, 122 trim: true, 123 escapeForRegexp: true, 124 isElement: true, 125 makeMap: true, 126 includes: true, 127 arrayRemove: true, 128 copy: true, 129 shallowCopy: true, 130 equals: true, 131 csp: true, 132 jq: true, 133 concat: true, 134 sliceArgs: true, 135 bind: true, 136 toJsonReplacer: true, 137 toJson: true, 138 fromJson: true, 139 convertTimezoneToLocal: true, 140 timezoneToOffset: true, 141 startingTag: true, 142 tryDecodeURIComponent: true, 143 parseKeyValue: true, 144 toKeyValue: true, 145 encodeUriSegment: true, 146 encodeUriQuery: true, 147 angularInit: true, 148 bootstrap: true, 149 getTestability: true, 150 snake_case: true, 151 bindJQuery: true, 152 assertArg: true, 153 assertArgFn: true, 154 assertNotHasOwnProperty: true, 155 getter: true, 156 getBlockNodes: true, 157 hasOwnProperty: true, 158 createMap: true, 159 160 NODE_TYPE_ELEMENT: true, 161 NODE_TYPE_ATTRIBUTE: true, 162 NODE_TYPE_TEXT: true, 163 NODE_TYPE_COMMENT: true, 164 NODE_TYPE_DOCUMENT: true, 165 NODE_TYPE_DOCUMENT_FRAGMENT: true, 166*/ 167
168//////////////////////////////////// 169 170/** 171 * @ngdoc module 172 * @name ng 173 * @module ng 174 * @installation 175 * @description 176 * 177 * # ng (core module) 178 * The ng module is loaded by default when an AngularJS application is started. The module itself 179 * contains the essential components for an AngularJS application to function. The table below 180 * lists a high level breakdown of each of the services/factories, filters, directives and testing 181 * components available within this core module. 182 * 183 * <div doc-module-components="ng"></div> 184 */ 185 186var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 187 188// The name of a form control's ValidityState property. 189// This is used so that it's possible for internal tests to create mock ValidityStates. 190var VALIDITY_STATE_PROPERTY = 'validity'; 191 192var hasOwnProperty = Object.prototype.hasOwnProperty; 193 194var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 195var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 196 197 198var manualLowercase = function(s) { 199 /* jshint bitwise: false */ 200 return isString(s) 201 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 202 : s; 203}; 204var manualUppercase = function(s) { 205 /* jshint bitwise: false */ 206 return isString(s) 207 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 208 : s; 209}; 210 211 212// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 213// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 214// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387 215if ('i' !== 'I'.toLowerCase()) { 216 lowercase = manualLowercase; 217 uppercase = manualUppercase; 218} 219 220 221var 222 msie, // holds major version number for IE, or NaN if UA is not IE. 223 jqLite, // delay binding since jQuery could be loaded after us. 224 jQuery, // delay binding 225 slice = [].slice, 226 splice = [].splice, 227 push = [].push, 228 toString = Object.prototype.toString, 229 getPrototypeOf = Object.getPrototypeOf, 230 ngMinErr = minErr('ng'), 231 232 /** @name angular */ 233 angular = window.angular || (window.angular = {}), 234 angularModule, 235 uid = 0; 236 237/** 238 * documentMode is an IE-only property 239 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 240 */ 241msie = window.document.documentMode; 242 243 244/** 245 * @private 246 * @param {*} obj 247 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 248 * String ...) 249 */ 250function isArrayLike(obj) { 251 252 // `null`, `undefined` and `window` are not array-like 253 if (obj == null || isWindow(obj)) return false; 254 255 // arrays, strings and jQuery/jqLite objects are array like 256 // * jqLite is either the jQuery or jqLite constructor function 257 // * we have to check the existence of jqLite first as this method is called 258 // via the forEach method when constructing the jqLite object in the first place 259 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 260 261 // Support: iOS 8.2 (not reproducible in simulator) 262 // "length" in obj used to prevent JIT error (gh-11508) 263 var length = "length" in Object(obj) && obj.length; 264 265 // NodeList objects (with `item` method) and 266 // other objects with suitable length characteristics are array-like 267 return isNumber(length) && 268 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function'); 269 270} 271 272/** 273 * @ngdoc function 274 * @name angular.forEach 275 * @module ng 276 * @kind function 277 * 278 * @description 279 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 280 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 281 * is the value of an object property or an array element, `key` is the object property key or 282 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 283 * 284 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 285 * using the `hasOwnProperty` method. 286 * 287 * Unlike ES262's 288 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 289 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 290 * return the value provided. 291 * 292 ```js 293 var values = {name: 'misko', gender: 'male'}; 294 var log = []; 295 angular.forEach(values, function(value, key) { 296 this.push(key + ': ' + value); 297 }, log); 298 expect(log).toEqual(['name: misko', 'gender: male']); 299 ``` 300 * 301 * @param {Object|Array} obj Object to iterate over. 302 * @param {Function} iterator Iterator function. 303 * @param {Object=} context Object to become context (`this`) for the iterator function. 304 * @returns {Object|Array} Reference to `obj`. 305 */ 306 307function forEach(obj, iterator, context) { 308 var key, length; 309 if (obj) { 310 if (isFunction(obj)) { 311 for (key in obj) { 312 // Need to check if hasOwnProperty exists, 313 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 314 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 315 iterator.call(context, obj[key], key, obj); 316 } 317 } 318 } else if (isArray(obj) || isArrayLike(obj)) { 319 var isPrimitive = typeof obj !== 'object'; 320 for (key = 0, length = obj.length; key < length; key++) { 321 if (isPrimitive || key in obj) { 322 iterator.call(context, obj[key], key, obj); 323 } 324 } 325 }
vendor: 11,620 bytes, lines 325-809
325 else if (obj.forEach && obj.forEach !== forEach) { 326 obj.forEach(iterator, context, obj); 327 } else if (isBlankObject(obj)) { 328 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 329 for (key in obj) { 330 iterator.call(context, obj[key], key, obj); 331 } 332 } else if (typeof obj.hasOwnProperty === 'function') { 333 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 334 for (key in obj) { 335 if (obj.hasOwnProperty(key)) { 336 iterator.call(context, obj[key], key, obj); 337 } 338 } 339 } else { 340 // Slow path for objects which do not have a method `hasOwnProperty` 341 for (key in obj) { 342 if (hasOwnProperty.call(obj, key)) { 343 iterator.call(context, obj[key], key, obj); 344 } 345 } 346 } 347 } 348 return obj; 349} 350 351function forEachSorted(obj, iterator, context) { 352 var keys = Object.keys(obj).sort(); 353 for (var i = 0; i < keys.length; i++) { 354 iterator.call(context, obj[keys[i]], keys[i]); 355 } 356 return keys; 357} 358 359 360/** 361 * when using forEach the params are value, key, but it is often useful to have key, value. 362 * @param {function(string, *)} iteratorFn 363 * @returns {function(*, string)} 364 */ 365function reverseParams(iteratorFn) { 366 return function(value, key) {iteratorFn(key, value);}; 367} 368 369/** 370 * A consistent way of creating unique IDs in angular. 371 * 372 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 373 * we hit number precision issues in JavaScript. 374 * 375 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 376 * 377 * @returns {number} an unique alpha-numeric string 378 */ 379function nextUid() { 380 return ++uid; 381} 382 383 384/** 385 * Set or clear the hashkey for an object. 386 * @param obj object 387 * @param h the hashkey (!truthy to delete the hashkey) 388 */ 389function setHashKey(obj, h) { 390 if (h) { 391 obj.$$hashKey = h; 392 } else { 393 delete obj.$$hashKey; 394 } 395} 396 397 398function baseExtend(dst, objs, deep) { 399 var h = dst.$$hashKey; 400 401 for (var i = 0, ii = objs.length; i < ii; ++i) { 402 var obj = objs[i]; 403 if (!isObject(obj) && !isFunction(obj)) continue; 404 var keys = Object.keys(obj); 405 for (var j = 0, jj = keys.length; j < jj; j++) { 406 var key = keys[j]; 407 var src = obj[key]; 408 409 if (deep && isObject(src)) { 410 if (isDate(src)) { 411 dst[key] = new Date(src.valueOf()); 412 } else if (isRegExp(src)) { 413 dst[key] = new RegExp(src); 414 } else if (src.nodeName) { 415 dst[key] = src.cloneNode(true); 416 } else if (isElement(src)) { 417 dst[key] = src.clone(); 418 } else { 419 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 420 baseExtend(dst[key], [src], true); 421 } 422 } else { 423 dst[key] = src; 424 } 425 } 426 } 427 428 setHashKey(dst, h); 429 return dst; 430} 431 432/** 433 * @ngdoc function 434 * @name angular.extend 435 * @module ng 436 * @kind function 437 * 438 * @description 439 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 440 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 441 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 442 * 443 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 444 * {@link angular.merge} for this. 445 * 446 * @param {Object} dst Destination object. 447 * @param {...Object} src Source object(s). 448 * @returns {Object} Reference to `dst`. 449 */ 450function extend(dst) { 451 return baseExtend(dst, slice.call(arguments, 1), false); 452} 453 454 455/** 456* @ngdoc function 457* @name angular.merge 458* @module ng 459* @kind function 460* 461* @description 462* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 463* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 464* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 465* 466* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 467* objects, performing a deep copy. 468* 469* @param {Object} dst Destination object. 470* @param {...Object} src Source object(s). 471* @returns {Object} Reference to `dst`. 472*/ 473function merge(dst) { 474 return baseExtend(dst, slice.call(arguments, 1), true); 475} 476 477 478 479function toInt(str) { 480 return parseInt(str, 10); 481} 482 483 484function inherit(parent, extra) { 485 return extend(Object.create(parent), extra); 486} 487 488/** 489 * @ngdoc function 490 * @name angular.noop 491 * @module ng 492 * @kind function 493 * 494 * @description 495 * A function that performs no operations. This function can be useful when writing code in the 496 * functional style. 497 ```js 498 function foo(callback) { 499 var result = calculateResult(); 500 (callback || angular.noop)(result); 501 } 502 ``` 503 */ 504function noop() {} 505noop.$inject = []; 506 507 508/** 509 * @ngdoc function 510 * @name angular.identity 511 * @module ng 512 * @kind function 513 * 514 * @description 515 * A function that returns its first argument. This function is useful when writing code in the 516 * functional style. 517 * 518 ```js 519 function transformer(transformationFn, value) { 520 return (transformationFn || angular.identity)(value); 521 }; 522 ``` 523 * @param {*} value to be returned. 524 * @returns {*} the value passed in. 525 */ 526function identity($) {return $;} 527identity.$inject = []; 528 529 530function valueFn(value) {return function valueRef() {return value;};} 531 532function hasCustomToString(obj) { 533 return isFunction(obj.toString) && obj.toString !== toString; 534} 535 536 537/** 538 * @ngdoc function 539 * @name angular.isUndefined 540 * @module ng 541 * @kind function 542 * 543 * @description 544 * Determines if a reference is undefined. 545 * 546 * @param {*} value Reference to check. 547 * @returns {boolean} True if `value` is undefined. 548 */ 549function isUndefined(value) {return typeof value === 'undefined';} 550 551 552/** 553 * @ngdoc function 554 * @name angular.isDefined 555 * @module ng 556 * @kind function 557 * 558 * @description 559 * Determines if a reference is defined. 560 * 561 * @param {*} value Reference to check. 562 * @returns {boolean} True if `value` is defined. 563 */ 564function isDefined(value) {return typeof value !== 'undefined';} 565 566 567/** 568 * @ngdoc function 569 * @name angular.isObject 570 * @module ng 571 * @kind function 572 * 573 * @description 574 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 575 * considered to be objects. Note that JavaScript arrays are objects. 576 * 577 * @param {*} value Reference to check. 578 * @returns {boolean} True if `value` is an `Object` but not `null`. 579 */ 580function isObject(value) { 581 // http://jsperf.com/isobject4 582 return value !== null && typeof value === 'object'; 583} 584 585 586/** 587 * Determine if a value is an object with a null prototype 588 * 589 * @returns {boolean} True if `value` is an `Object` with a null prototype 590 */ 591function isBlankObject(value) { 592 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 593} 594 595 596/** 597 * @ngdoc function 598 * @name angular.isString 599 * @module ng 600 * @kind function 601 * 602 * @description 603 * Determines if a reference is a `String`. 604 * 605 * @param {*} value Reference to check. 606 * @returns {boolean} True if `value` is a `String`. 607 */ 608function isString(value) {return typeof value === 'string';} 609 610 611/** 612 * @ngdoc function 613 * @name angular.isNumber 614 * @module ng 615 * @kind function 616 * 617 * @description 618 * Determines if a reference is a `Number`. 619 * 620 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 621 * 622 * If you wish to exclude these then you can use the native 623 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 624 * method. 625 * 626 * @param {*} value Reference to check. 627 * @returns {boolean} True if `value` is a `Number`. 628 */ 629function isNumber(value) {return typeof value === 'number';} 630 631 632/** 633 * @ngdoc function 634 * @name angular.isDate 635 * @module ng 636 * @kind function 637 * 638 * @description 639 * Determines if a value is a date. 640 * 641 * @param {*} value Reference to check. 642 * @returns {boolean} True if `value` is a `Date`. 643 */ 644function isDate(value) { 645 return toString.call(value) === '[object Date]'; 646} 647 648 649/** 650 * @ngdoc function 651 * @name angular.isArray 652 * @module ng 653 * @kind function 654 * 655 * @description 656 * Determines if a reference is an `Array`. 657 * 658 * @param {*} value Reference to check. 659 * @returns {boolean} True if `value` is an `Array`. 660 */ 661var isArray = Array.isArray; 662 663/** 664 * @ngdoc function 665 * @name angular.isFunction 666 * @module ng 667 * @kind function 668 * 669 * @description 670 * Determines if a reference is a `Function`. 671 * 672 * @param {*} value Reference to check. 673 * @returns {boolean} True if `value` is a `Function`. 674 */ 675function isFunction(value) {return typeof value === 'function';} 676 677 678/** 679 * Determines if a value is a regular expression object. 680 * 681 * @private 682 * @param {*} value Reference to check. 683 * @returns {boolean} True if `value` is a `RegExp`. 684 */ 685function isRegExp(value) { 686 return toString.call(value) === '[object RegExp]'; 687} 688 689 690/** 691 * Checks if `obj` is a window object. 692 * 693 * @private 694 * @param {*} obj Object to check 695 * @returns {boolean} True if `obj` is a window obj. 696 */ 697function isWindow(obj) { 698 return obj && obj.window === obj; 699} 700 701 702function isScope(obj) { 703 return obj && obj.$evalAsync && obj.$watch; 704} 705 706 707function isFile(obj) { 708 return toString.call(obj) === '[object File]'; 709} 710 711 712function isFormData(obj) { 713 return toString.call(obj) === '[object FormData]'; 714} 715 716 717function isBlob(obj) { 718 return toString.call(obj) === '[object Blob]'; 719} 720 721 722function isBoolean(value) { 723 return typeof value === 'boolean'; 724} 725 726 727function isPromiseLike(obj) { 728 return obj && isFunction(obj.then); 729} 730 731 732var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/; 733function isTypedArray(value) { 734 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 735} 736 737function isArrayBuffer(obj) { 738 return toString.call(obj) === '[object ArrayBuffer]'; 739} 740 741 742var trim = function(value) { 743 return isString(value) ? value.trim() : value; 744}; 745 746// Copied from: 747// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 748// Prereq: s is a string. 749var escapeForRegexp = function(s) { 750 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 751 replace(/\x08/g, '\\x08'); 752}; 753 754 755/** 756 * @ngdoc function 757 * @name angular.isElement 758 * @module ng 759 * @kind function 760 * 761 * @description 762 * Determines if a reference is a DOM element (or wrapped jQuery element). 763 * 764 * @param {*} value Reference to check. 765 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 766 */ 767function isElement(node) { 768 return !!(node && 769 (node.nodeName // we are a direct element 770 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 771} 772 773/** 774 * @param str 'key1,key2,...' 775 * @returns {object} in the form of {key1:true, key2:true, ...} 776 */ 777function makeMap(str) { 778 var obj = {}, items = str.split(','), i; 779 for (i = 0; i < items.length; i++) { 780 obj[items[i]] = true; 781 } 782 return obj; 783} 784 785 786function nodeName_(element) { 787 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 788} 789 790function includes(array, obj) { 791 return Array.prototype.indexOf.call(array, obj) != -1; 792} 793 794function arrayRemove(array, value) { 795 var index = array.indexOf(value); 796 if (index >= 0) { 797 array.splice(index, 1); 798 } 799 return index; 800} 801 802/** 803 * @ngdoc function 804 * @name angular.copy 805 * @module ng 806 * @kind function 807 * 808 * @description 809 * Creates a deep copy of `source`, which should be an object or an array.
810 * 811 * * If no destination is supplied, a copy of the object or array is created. 812 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 813 * are deleted and then all elements/properties from the source are copied to it. 814 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 815 * * If `source` is identical to 'destination' an exception will be thrown. 816 * 817 * @param {*} source The source that will be used to make a copy. 818 * Can be any type, including primitives, `null`, and `undefined`. 819 * @param {(Object|Array)=} destination Destination into which the source is copied. If 820 * provided, must be of the same type as `source`. 821 * @returns {*} The copy or updated `destination`, if `destination` was specified. 822 * 823 * @example 824 <example module="copyExample"> 825 <file name="index.html"> 826 <div ng-controller="ExampleController"> 827 <form novalidate class="simple-form"> 828 Name: <input type="text" ng-model="user.name" /><br /> 829 E-mail: <input type="email" ng-model="user.email" /><br /> 830 Gender: <input type="radio" ng-model="user.gender" value="male" />male 831 <input type="radio" ng-model="user.gender" value="female" />female<br /> 832 <button ng-click="reset()">RESET</button> 833 <button ng-click="update(user)">SAVE</button> 834 </form> 835 <pre>form = {{user | json}}</pre> 836 <pre>master = {{master | json}}</pre> 837 </div> 838 839 <script> 840 angular.module('copyExample', []) 841 .controller('ExampleController', ['$scope', function($scope) { 842 $scope.master= {}; 843 844 $scope.update = function(user) { 845 // Example with 1 argument 846 $scope.master= angular.copy(user); 847 }; 848 849 $scope.reset = function() { 850 // Example with 2 arguments 851 angular.copy($scope.master, $scope.user); 852 }; 853 854 $scope.reset(); 855 }]); 856 </script> 857 </file> 858 </example> 859 */ 860function copy(source, destination) { 861 var stackSource = []; 862 var stackDest = []; 863 864 if (destination) { 865 if (isTypedArray(destination) || isArrayBuffer(destination)) { 866 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated."); 867 } 868 if (source === destination) { 869 throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); 870 } 871 872 // Empty the destination object 873 if (isArray(destination)) { 874 destination.length = 0; 875 } else { 876 forEach(destination, function(value, key) { 877 if (key !== '$$hashKey') { 878 delete destination[key]; 879 } 880 }); 881 } 882 883 stackSource.push(source); 884 stackDest.push(destination); 885 return copyRecurse(source, destination); 886 } 887 888 return copyElement(source); 889 890 function copyRecurse(source, destination) { 891 var h = destination.$$hashKey; 892 var key; 893 if (isArray(source)) { 894 for (var i = 0, ii = source.length; i < ii; i++) { 895 destination.push(copyElement(source[i])); 896 } 897 } else if (isBlankObject(source)) { 898 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 899 for (key in source) { 900 destination[key] = copyElement(source[key]); 901 } 902 } else if (source && typeof source.hasOwnProperty === 'function') { 903 // Slow path, which must rely on hasOwnProperty 904 for (key in source) { 905 if (source.hasOwnProperty(key)) { 906 destination[key] = copyElement(source[key]); 907 } 908 } 909 } else { 910 // Slowest path --- hasOwnProperty can't be called as a method 911 for (key in source) { 912 if (hasOwnProperty.call(source, key)) { 913 destination[key] = copyElement(source[key]); 914 } 915 } 916 } 917 setHashKey(destination, h); 918 return destination; 919 } 920 921 function copyElement(source) { 922 // Simple values 923 if (!isObject(source)) { 924 return source; 925 } 926 927 // Already copied values 928 var index = stackSource.indexOf(source); 929 if (index !== -1) { 930 return stackDest[index]; 931 } 932 933 if (isWindow(source) || isScope(source)) { 934 throw ngMinErr('cpws', 935 "Can't copy! Making copies of Window or Scope instances is not supported."); 936 } 937 938 var needsRecurse = false;
vendor: 3,388 bytes, lines 939-1054
939 var destination = copyType(source); 940 941 if (destination === undefined) { 942 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 943 needsRecurse = true; 944 } 945 946 stackSource.push(source); 947 stackDest.push(destination); 948 949 return needsRecurse 950 ? copyRecurse(source, destination) 951 : destination; 952 } 953 954 function copyType(source) { 955 switch (toString.call(source)) { 956 case '[object Int8Array]': 957 case '[object Int16Array]': 958 case '[object Int32Array]': 959 case '[object Float32Array]': 960 case '[object Float64Array]': 961 case '[object Uint8Array]': 962 case '[object Uint8ClampedArray]': 963 case '[object Uint16Array]': 964 case '[object Uint32Array]': 965 return new source.constructor(copyElement(source.buffer)); 966 967 case '[object ArrayBuffer]': 968 //Support: IE10 969 if (!source.slice) { 970 var copied = new ArrayBuffer(source.byteLength); 971 new Uint8Array(copied).set(new Uint8Array(source)); 972 return copied; 973 } 974 return source.slice(0); 975 976 case '[object Boolean]': 977 case '[object Number]': 978 case '[object String]': 979 case '[object Date]': 980 return new source.constructor(source.valueOf()); 981 982 case '[object RegExp]': 983 var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 984 re.lastIndex = source.lastIndex; 985 return re; 986 987 case '[object Blob]': 988 return new source.constructor([source], {type: source.type}); 989 } 990 991 if (isFunction(source.cloneNode)) { 992 return source.cloneNode(true); 993 } 994 } 995} 996 997/** 998 * Creates a shallow copy of an object, an array or a primitive. 999 * 1000 * Assumes that there are no proto properties for objects. 1001 */ 1002function shallowCopy(src, dst) { 1003 if (isArray(src)) { 1004 dst = dst || []; 1005 1006 for (var i = 0, ii = src.length; i < ii; i++) { 1007 dst[i] = src[i]; 1008 } 1009 } else if (isObject(src)) { 1010 dst = dst || {}; 1011 1012 for (var key in src) { 1013 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 1014 dst[key] = src[key]; 1015 } 1016 } 1017 } 1018 1019 return dst || src; 1020} 1021 1022 1023/** 1024 * @ngdoc function 1025 * @name angular.equals 1026 * @module ng 1027 * @kind function 1028 * 1029 * @description 1030 * Determines if two objects or two values are equivalent. Supports value types, regular 1031 * expressions, arrays and objects. 1032 * 1033 * Two objects or values are considered equivalent if at least one of the following is true: 1034 * 1035 * * Both objects or values pass `===` comparison. 1036 * * Both objects or values are of the same type and all of their properties are equal by 1037 * comparing them with `angular.equals`. 1038 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1039 * * Both values represent the same regular expression (In JavaScript, 1040 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1041 * representation matches). 1042 * 1043 * During a property comparison, properties of `function` type and properties with names 1044 * that begin with `$` are ignored. 1045 * 1046 * Scope and DOMWindow objects are being compared only by identify (`===`). 1047 * 1048 * @param {*} o1 Object or value to compare. 1049 * @param {*} o2 Object or value to compare. 1050 * @returns {boolean} True if arguments are equal. 1051 * 1052 * @example 1053 <example module="equalsExample" name="equalsExample"> 1054 <file name="index.html">
1055 <div ng-controller="ExampleController"> 1056 <form novalidate> 1057 <h3>User 1</h3> 1058 Name: <input type="text" ng-model="user1.name"> 1059 Age: <input type="number" ng-model="user1.age"> 1060 1061 <h3>User 2</h3> 1062 Name: <input type="text" ng-model="user2.name"> 1063 Age: <input type="number" ng-model="user2.age"> 1064 1065 <div> 1066 <br/> 1067 <input type="button" value="Compare" ng-click="compare()"> 1068 </div> 1069 User 1: <pre>{{user1 | json}}</pre> 1070 User 2: <pre>{{user2 | json}}</pre> 1071 Equal: <pre>{{result}}</pre> 1072 </form> 1073 </div> 1074 </file> 1075 <file name="script.js"> 1076 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { 1077 $scope.user1 = {}; 1078 $scope.user2 = {}; 1079 $scope.result; 1080 $scope.compare = function() { 1081 $scope.result = angular.equals($scope.user1, $scope.user2); 1082 }; 1083 }]); 1084 </file> 1085 </example> 1086 */ 1087function equals(o1, o2) { 1088 if (o1 === o2) return true; 1089 if (o1 === null || o2 === null) return false; 1090 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1091 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1092 if (t1 == t2 && t1 == 'object') { 1093 if (isArray(o1)) { 1094 if (!isArray(o2)) return false; 1095 if ((length = o1.length) == o2.length) { 1096 for (key = 0; key < length; key++) { 1097 if (!equals(o1[key], o2[key])) return false; 1098 } 1099 return true; 1100 } 1101 } else if (isDate(o1)) { 1102 if (!isDate(o2)) return false; 1103 return equals(o1.getTime(), o2.getTime()); 1104 } else if (isRegExp(o1)) { 1105 if (!isRegExp(o2)) return false; 1106 return o1.toString() == o2.toString(); 1107 } else { 1108 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1109 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1110 keySet = createMap(); 1111 for (key in o1) { 1112 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1113 if (!equals(o1[key], o2[key])) return false; 1114 keySet[key] = true; 1115 } 1116 for (key in o2) { 1117 if (!(key in keySet) && 1118 key.charAt(0) !== '$' && 1119 isDefined(o2[key]) && 1120 !isFunction(o2[key])) return false; 1121 } 1122 return true; 1123 } 1124 } 1125 return false; 1126} 1127 1128var csp = function() { 1129 if (!isDefined(csp.rules)) { 1130 1131 1132 var ngCspElement = (window.document.querySelector('[ng-csp]') || 1133 window.document.querySelector('[data-ng-csp]')); 1134 1135 if (ngCspElement) { 1136 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1137 ngCspElement.getAttribute('data-ng-csp'); 1138 csp.rules = { 1139 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1140 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1141 }; 1142 } else { 1143 csp.rules = { 1144 noUnsafeEval: noUnsafeEval(), 1145 noInlineStyle: false 1146 }; 1147 } 1148 } 1149 1150 return csp.rules; 1151 1152 function noUnsafeEval() { 1153 try { 1154 /* jshint -W031, -W054 */ 1155 new Function(''); 1156 /* jshint +W031, +W054 */ 1157 return false; 1158 } catch (e) { 1159 return true; 1160 } 1161 } 1162}; 1163 1164/** 1165 * @ngdoc directive 1166 * @module ng 1167 * @name ngJq 1168 * 1169 * @element ANY 1170 * @param {string=} ngJq the name of the library available under `window` 1171 * to be used for angular.element 1172 * @description 1173 * Use this directive to force the angular.element library. This should be 1174 * used to force either jqLite by leaving ng-jq blank or setting the name of 1175 * the jquery variable under window (eg. jQuery). 1176 * 1177 * Since angular looks for this directive when it is loaded (doesn't wait for the 1178 * DOMContentLoaded event), it must be placed on an element that comes before the script 1179 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1180 * others ignored. 1181 * 1182 * @example 1183 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1184 ```html 1185 <!doctype html> 1186 <html ng-app ng-jq> 1187 ... 1188 ... 1189 </html> 1190 ``` 1191 * @example 1192 * This example shows how to use a jQuery based library of a different name. 1193 * The library name must be available at the top most 'window'. 1194 ```html 1195 <!doctype html> 1196 <html ng-app ng-jq="jQueryLib"> 1197 ... 1198 ... 1199 </html> 1200 ``` 1201 */ 1202var jq = function() { 1203 if (isDefined(jq.name_)) return jq.name_; 1204 var el; 1205 var i, ii = ngAttrPrefixes.length, prefix, name; 1206 for (i = 0; i < ii; ++i) { 1207 prefix = ngAttrPrefixes[i]; 1208 if (el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1209 name = el.getAttribute(prefix + 'jq'); 1210 break; 1211 } 1212 } 1213 1214 return (jq.name_ = name); 1215}; 1216 1217function concat(array1, array2, index) { 1218 return array1.concat(slice.call(array2, index)); 1219} 1220 1221function sliceArgs(args, startIndex) { 1222 return slice.call(args, startIndex || 0); 1223} 1224 1225
vendor: 9,222 bytes, lines 1226-1514
1226/* jshint -W101 */ 1227/** 1228 * @ngdoc function 1229 * @name angular.bind 1230 * @module ng 1231 * @kind function 1232 * 1233 * @description 1234 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1235 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1236 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1237 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1238 * 1239 * @param {Object} self Context which `fn` should be evaluated in. 1240 * @param {function()} fn Function to be bound. 1241 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1242 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1243 */ 1244/* jshint +W101 */ 1245function bind(self, fn) { 1246 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1247 if (isFunction(fn) && !(fn instanceof RegExp)) { 1248 return curryArgs.length 1249 ? function() { 1250 return arguments.length 1251 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1252 : fn.apply(self, curryArgs); 1253 } 1254 : function() { 1255 return arguments.length 1256 ? fn.apply(self, arguments) 1257 : fn.call(self); 1258 }; 1259 } else { 1260 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1261 return fn; 1262 } 1263} 1264 1265 1266function toJsonReplacer(key, value) { 1267 var val = value; 1268 1269 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1270 val = undefined; 1271 } else if (isWindow(value)) { 1272 val = '$WINDOW'; 1273 } else if (value && window.document === value) { 1274 val = '$DOCUMENT'; 1275 } else if (isScope(value)) { 1276 val = '$SCOPE'; 1277 } 1278 1279 return val; 1280} 1281 1282 1283/** 1284 * @ngdoc function 1285 * @name angular.toJson 1286 * @module ng 1287 * @kind function 1288 * 1289 * @description 1290 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1291 * stripped since angular uses this notation internally. 1292 * 1293 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1294 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1295 * If set to an integer, the JSON output will contain that many spaces per indentation. 1296 * @returns {string|undefined} JSON-ified string representing `obj`. 1297 */ 1298function toJson(obj, pretty) { 1299 if (isUndefined(obj)) return undefined; 1300 if (!isNumber(pretty)) { 1301 pretty = pretty ? 2 : null; 1302 } 1303 return JSON.stringify(obj, toJsonReplacer, pretty); 1304} 1305 1306 1307/** 1308 * @ngdoc function 1309 * @name angular.fromJson 1310 * @module ng 1311 * @kind function 1312 * 1313 * @description 1314 * Deserializes a JSON string. 1315 * 1316 * @param {string} json JSON string to deserialize. 1317 * @returns {Object|Array|string|number} Deserialized JSON string. 1318 */ 1319function fromJson(json) { 1320 return isString(json) 1321 ? JSON.parse(json) 1322 : json; 1323} 1324 1325 1326var ALL_COLONS = /:/g; 1327function timezoneToOffset(timezone, fallback) { 1328 // IE/Edge do not "understand" colon (`:`) in timezone 1329 timezone = timezone.replace(ALL_COLONS, ''); 1330 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1331 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1332} 1333 1334 1335function addDateMinutes(date, minutes) { 1336 date = new Date(date.getTime()); 1337 date.setMinutes(date.getMinutes() + minutes); 1338 return date; 1339} 1340 1341 1342function convertTimezoneToLocal(date, timezone, reverse) { 1343 reverse = reverse ? -1 : 1; 1344 var dateTimezoneOffset = date.getTimezoneOffset(); 1345 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 1346 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 1347} 1348 1349 1350/** 1351 * @returns {string} Returns the string representation of the element. 1352 */ 1353function startingTag(element) { 1354 element = jqLite(element).clone(); 1355 try { 1356 // turns out IE does not let you set .html() on elements which 1357 // are not allowed to have children. So we just ignore it. 1358 element.empty(); 1359 } catch (e) {} 1360 var elemHtml = jqLite('<div>').append(element).html(); 1361 try { 1362 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1363 elemHtml. 1364 match(/^(<[^>]+>)/)[1]. 1365 replace(/^<([\w\-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); 1366 } catch (e) { 1367 return lowercase(elemHtml); 1368 } 1369 1370} 1371 1372 1373///////////////////////////////////////////////// 1374 1375/** 1376 * Tries to decode the URI component without throwing an exception. 1377 * 1378 * @private 1379 * @param str value potential URI component to check. 1380 * @returns {boolean} True if `value` can be decoded 1381 * with the decodeURIComponent function. 1382 */ 1383function tryDecodeURIComponent(value) { 1384 try { 1385 return decodeURIComponent(value); 1386 } catch (e) { 1387 // Ignore any invalid uri component 1388 } 1389} 1390 1391 1392/** 1393 * Parses an escaped url query string into key-value pairs. 1394 * @returns {Object.<string,boolean|Array>} 1395 */ 1396function parseKeyValue(/**string*/keyValue) { 1397 var obj = {}; 1398 forEach((keyValue || "").split('&'), function(keyValue) { 1399 var splitPoint, key, val; 1400 if (keyValue) { 1401 key = keyValue = keyValue.replace(/\+/g,'%20'); 1402 splitPoint = keyValue.indexOf('='); 1403 if (splitPoint !== -1) { 1404 key = keyValue.substring(0, splitPoint); 1405 val = keyValue.substring(splitPoint + 1); 1406 } 1407 key = tryDecodeURIComponent(key); 1408 if (isDefined(key)) { 1409 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1410 if (!hasOwnProperty.call(obj, key)) { 1411 obj[key] = val; 1412 } else if (isArray(obj[key])) { 1413 obj[key].push(val); 1414 } else { 1415 obj[key] = [obj[key],val]; 1416 } 1417 } 1418 } 1419 }); 1420 return obj; 1421} 1422 1423function toKeyValue(obj) { 1424 var parts = []; 1425 forEach(obj, function(value, key) { 1426 if (isArray(value)) { 1427 forEach(value, function(arrayValue) { 1428 parts.push(encodeUriQuery(key, true) + 1429 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1430 }); 1431 } else { 1432 parts.push(encodeUriQuery(key, true) + 1433 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1434 } 1435 }); 1436 return parts.length ? parts.join('&') : ''; 1437} 1438 1439 1440/** 1441 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1442 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1443 * segments: 1444 * segment = *pchar 1445 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1446 * pct-encoded = "%" HEXDIG HEXDIG 1447 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1448 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1449 * / "*" / "+" / "," / ";" / "=" 1450 */ 1451function encodeUriSegment(val) { 1452 return encodeUriQuery(val, true). 1453 replace(/%26/gi, '&'). 1454 replace(/%3D/gi, '='). 1455 replace(/%2B/gi, '+'); 1456} 1457 1458 1459/** 1460 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1461 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1462 * encoded per http://tools.ietf.org/html/rfc3986: 1463 * query = *( pchar / "/" / "?" ) 1464 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1465 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1466 * pct-encoded = "%" HEXDIG HEXDIG 1467 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1468 * / "*" / "+" / "," / ";" / "=" 1469 */ 1470function encodeUriQuery(val, pctEncodeSpaces) { 1471 return encodeURIComponent(val). 1472 replace(/%40/gi, '@'). 1473 replace(/%3A/gi, ':'). 1474 replace(/%24/g, '$'). 1475 replace(/%2C/gi, ','). 1476 replace(/%3B/gi, ';'). 1477 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1478} 1479 1480var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1481 1482function getNgAttribute(element, ngAttr) { 1483 var attr, i, ii = ngAttrPrefixes.length; 1484 for (i = 0; i < ii; ++i) { 1485 attr = ngAttrPrefixes[i] + ngAttr; 1486 if (isString(attr = element.getAttribute(attr))) { 1487 return attr; 1488 } 1489 } 1490 return null; 1491} 1492 1493/** 1494 * @ngdoc directive 1495 * @name ngApp 1496 * @module ng 1497 * 1498 * @element ANY 1499 * @param {angular.Module} ngApp an optional application 1500 * {@link angular.module module} name to load. 1501 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1502 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1503 * do not use explicit function annotation (and are thus unsuitable for minification), as described 1504 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1505 * tracking down the root of these bugs. 1506 * 1507 * @description 1508 * 1509 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1510 * designates the **root element** of the application and is typically placed near the root element 1511 * of the page - e.g. on the `<body>` or `<html>` tags. 1512 * 1513 * There are a few things to keep in mind when using `ngApp`: 1514 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1514p` 1515 * found in the document will be used to define the root element to auto-bootstrap as an 1516 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1517 * {@link angular.bootstrap} instead. 1518 * - AngularJS applications cannot be nested within each other. 1519 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. 1520 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and 1521 * {@link ngRoute.ngView `ngView`}. 1522 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 1523 * causing animations to stop working and making the injector inaccessible from outside the app. 1524 * 1525 * You can specify an **AngularJS module** to be used as the root module for the application. This 1526 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1527 * should contain the application code needed or have dependencies on other modules that will 1528 * contain the code. See {@link angular.module} for more information. 1529 * 1530 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1531 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1532 * would not be resolved to `3`. 1533 * 1534 * `ngApp` is the easiest, and most common way to bootstrap an application. 1535 * 1536 <example module="ngAppDemo"> 1537 <file name="index.html"> 1538 <div ng-controller="ngAppDemoController"> 1539 I can add: {{a}} + {{b}} = {{ a+b }} 1540 </div> 1541 </file> 1542 <file name="script.js"> 1543 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1544 $scope.a = 1; 1545 $scope.b = 2; 1546 }); 1547 </file> 1548 </example> 1549 * 1550 * Using `ngStrictDi`, you would see something like this: 1551 * 1552 <example ng-app-included="true"> 1553 <file name="index.html"> 1554 <div ng-app="ngAppStrictDemo" ng-strict-di> 1555 <div ng-controller="GoodController1"> 1556 I can add: {{a}} + {{b}} = {{ a+b }} 1557 1558 <p>This renders because the controller does not fail to 1559 instantiate, by using explicit annotation style (see 1560 script.js for details) 1561 </p> 1562 </div> 1563 1564 <div ng-controller="GoodController2"> 1565 Name: <input ng-model="name"><br /> 1566 Hello, {{name}}! 1567 1568 <p>This renders because the controller does not fail to 1569 instantiate, by using explicit annotation style 1570 (see script.js for details) 1571 </p> 1572 </div> 1573 1574 <div ng-controller="BadController"> 1575 I can add: {{a}} + {{b}} = {{ a+b }} 1576 1577 <p>The controller could not be instantiated, due to relying 1578 on automatic function annotations (which are disabled in 1579 strict mode). As such, the content of this section is not 1580 interpolated, and there should be an error in your web console. 1581 </p> 1582 </div> 1583 </div> 1584 </file> 1585 <file name="script.js"> 1586 angular.module('ngAppStrictDemo', []) 1587 // BadController will fail to instantiate, due to relying on automatic function annotation, 1588 // rather than an explicit annotation 1589 .controller('BadController', function($scope) { 1590 $scope.a = 1; 1591 $scope.b = 2; 1592 }) 1593 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1594 // due to using explicit annotations using the array style and $inject property, respectively. 1595 .controller('GoodController1', ['$scope', function($scope) { 1596 $scope.a = 1; 1597 $scope.b = 2; 1598 }]) 1599 .controller('GoodController2', GoodController2); 1600 function GoodController2($scope) { 1601 $scope.name = "World"; 1602 } 1603 GoodController2.$inject = ['$scope']; 1604 </file> 1605 <file name="style.css"> 1606 div[ng-controller] { 1607 margin-bottom: 1em; 1608 -webkit-border-radius: 4px; 1609 border-radius: 4px; 1610 border: 1px solid; 1611 padding: .5em; 1612 } 1613 div[ng-controller^=Good] { 1614 border-color: #d6e9c6; 1615 background-color: #dff0d8; 1616 color: #3c763d; 1617 } 1618 div[ng-controller^=Bad] { 1619 border-color: #ebccd1; 1620 background-color: #f2dede; 1621 color: #a94442; 1622 margin-bottom: 0; 1623 } 1624 </file> 1625 </example> 1626 */ 1627function angularInit(element, bootstrap) { 1628 var appElement, 1629 module, 1630 config = {}; 1631 1632 // The element `element` has priority over any other element 1633 forEach(ngAttrPrefixes, function(prefix) { 1634 var name = prefix + 'app'; 1635 1636 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1637 appElement = element; 1638 module = element.getAttribute(name); 1639 } 1640 }); 1641 forEach(ngAttrPrefixes, function(prefix) { 1642 var name = prefix + 'app'; 1643 var candidate; 1644 1645 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1646 appElement = candidate; 1647 module = candidate.getAttribute(name); 1648 } 1649 }); 1650 if (appElement) { 1651 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1652 bootstrap(appElement, module ? [module] : [], config); 1653 } 1654} 1655 1656/** 1657 * @ngdoc function 1658 * @name angular.bootstrap 1659 * @module ng 1660 * @description 1661 * Use this function to manually start up angular application. 1662 * 1663 * For more information, see the {@link guide/bootstrap Bootstrap guide}. 1664 * 1665 * Angular will detect if it has been loaded into the browser more than once and only allow the 1666 * first loaded script to be bootstrapped and will report a warning to the browser console for 1667 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1668 * multiple instances of Angular try to work on the DOM. 1669 * 1670 * <div class="alert alert-warning">
1671 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually. 1672 * They must use {@link ng.directive:ngApp ngApp}. 1673 * </div> 1674 * 1675 * <div class="alert alert-warning"> 1676 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, 1677 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. 1678 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 1679 * causing animations to stop working and making the injector inaccessible from outside the app. 1680 * </div> 1681 * 1682 * ```html 1683 * <!doctype html> 1684 * <html> 1685 * <body> 1686 * <div ng-controller="WelcomeController"> 1687 * {{greeting}} 1688 * </div> 1689 * 1690 * <script src="angular.js"></script> 1691 * <script> 1692 * var app = angular.module('demo', []) 1693 * .controller('WelcomeController', function($scope) { 1694 * $scope.greeting = 'Welcome!'; 1695 * }); 1696 * angular.bootstrap(document, ['demo']); 1697 * </script> 1698 * </body> 1699 * </html> 1700 * ``` 1701 * 1702 * @param {DOMElement} element DOM element which is the root of angular application. 1703 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1704 * Each item in the array should be the name of a predefined module or a (DI annotated) 1705 * function that will be invoked by the injector as a `config` block. 1706 * See: {@link angular.module modules} 1707 * @param {Object=} config an object for defining configuration options for the application. The 1708 * following keys are supported: 1709 * 1710 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1711 * assist in finding bugs which break minified code. Defaults to `false`. 1712 * 1713 * @returns {auto.$injector} Returns the newly created injector for this app. 1714 */ 1715function bootstrap(element, modules, config) { 1716 if (!isObject(config)) config = {}; 1717 var defaultConfig = { 1718 strictDi: false 1719 }; 1720 config = extend(defaultConfig, config); 1721 var doBootstrap = function() { 1722 element = jqLite(element); 1723 1724 if (element.injector()) { 1725 var tag = (element[0] === window.document) ? 'document' : startingTag(element); 1726 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1727 throw ngMinErr( 1728 'btstrpd', 1729 "App already bootstrapped with this element '{0}'", 1730 tag.replace(/</,'<').replace(/>/,'>')); 1731 } 1732 1733 modules = modules || []; 1734 modules.unshift(['$provide', function($provide) { 1735 $provide.value('$rootElement', element); 1736 }]); 1737 1738 if (config.debugInfoEnabled) { 1739 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1740 modules.push(['$compileProvider', function($compileProvider) { 1741 $compileProvider.debugInfoEnabled(true); 1742 }]); 1743 } 1744 1745 modules.unshift('ng'); 1746 var injector = createInjector(modules, config.strictDi); 1747 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1748 function bootstrapApply(scope, element, compile, injector) { 1749 scope.$apply(function() { 1750 element.data('$injector', injector); 1751 compile(element)(scope); 1752 }); 1753 }] 1754 ); 1755 return injector; 1756 }; 1757 1758 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1759 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1760 1761 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1762 config.debugInfoEnabled = true; 1763 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1764 } 1765 1766 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1767 return doBootstrap(); 1768 } 1769 1770 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1771 angular.resumeBootstrap = function(extraModules) { 1772 forEach(extraModules, function(module) { 1773 modules.push(module); 1774 }); 1775 return doBootstrap(); 1776 }; 1777 1778 if (isFunction(angular.resumeDeferredBootstrap)) { 1779 angular.resumeDeferredBootstrap(); 1780 } 1781} 1782 1783/** 1784 * @ngdoc function 1785 * @name angular.reloadWithDebugInfo 1786 * @module ng 1787 * @description 1788 * Use this function to reload the current application with debug information turned on. 1789 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1790 * 1791 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1792 */ 1793function reloadWithDebugInfo() { 1794 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1795 window.location.reload(); 1796} 1797 1798/** 1799 * @name angular.getTestability 1800 * @module ng 1801 * @description 1802 * Get the testability service for the instance of Angular on the given 1803 * element. 1804 * @param {DOMElement} element DOM element which is the root of angular application. 1805 */ 1806function getTestability(rootElement) { 1807 var injector = angular.element(rootElement).injector(); 1808 if (!injector) { 1809 throw ngMinErr('test', 1810 'no injector found for element argument to getTestability'); 1811 } 1812 return injector.get('$$testability'); 1813} 1814 1815var SNAKE_CASE_REGEXP = /[A-Z]/g; 1816function snake_case(name, separator) { 1817 separator = separator || '_'; 1818 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1819 return (pos ? separator : '') + letter.toLowerCase(); 1820 }); 1821} 1822 1823var bindJQueryFired = false;
vendor: 8,937 bytes, lines 1824-2108
1824function bindJQuery() { 1825 var originalCleanData; 1826 1827 if (bindJQueryFired) { 1828 return; 1829 } 1830 1831 // bind to jQuery if present; 1832 var jqName = jq(); 1833 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 1834 !jqName ? undefined : // use jqLite 1835 window[jqName]; // use jQuery specified by `ngJq` 1836 1837 // Use jQuery if it exists with proper functionality, otherwise default to us. 1838 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1839 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1840 // versions. It will not work for sure with jQuery <1.7, though. 1841 if (jQuery && jQuery.fn.on) { 1842 jqLite = jQuery; 1843 extend(jQuery.fn, { 1844 scope: JQLitePrototype.scope, 1845 isolateScope: JQLitePrototype.isolateScope, 1846 controller: JQLitePrototype.controller, 1847 injector: JQLitePrototype.injector, 1848 inheritedData: JQLitePrototype.inheritedData 1849 }); 1850 1851 // All nodes removed from the DOM via various jQuery APIs like .remove() 1852 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1853 // the $destroy event on all removed nodes. 1854 originalCleanData = jQuery.cleanData; 1855 jQuery.cleanData = function(elems) { 1856 var events; 1857 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1858 events = jQuery._data(elem, "events"); 1859 if (events && events.$destroy) { 1860 jQuery(elem).triggerHandler('$destroy'); 1861 } 1862 } 1863 originalCleanData(elems); 1864 }; 1865 } else { 1866 jqLite = JQLite; 1867 } 1868 1869 angular.element = jqLite; 1870 1871 // Prevent double-proxying. 1872 bindJQueryFired = true; 1873} 1874 1875/** 1876 * throw error if the argument is falsy. 1877 */ 1878function assertArg(arg, name, reason) { 1879 if (!arg) { 1880 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1881 } 1882 return arg; 1883} 1884 1885function assertArgFn(arg, name, acceptArrayAnnotation) { 1886 if (acceptArrayAnnotation && isArray(arg)) { 1887 arg = arg[arg.length - 1]; 1888 } 1889 1890 assertArg(isFunction(arg), name, 'not a function, got ' + 1891 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1892 return arg; 1893} 1894 1895/** 1896 * throw error if the name given is hasOwnProperty 1897 * @param {String} name the name to test 1898 * @param {String} context the context in which the name is used, such as module or directive 1899 */ 1900function assertNotHasOwnProperty(name, context) { 1901 if (name === 'hasOwnProperty') { 1902 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1903 } 1904} 1905 1906/** 1907 * Return the value accessible from the object by path. Any undefined traversals are ignored 1908 * @param {Object} obj starting object 1909 * @param {String} path path to traverse 1910 * @param {boolean} [bindFnToScope=true] 1911 * @returns {Object} value as accessible by path 1912 */ 1913//TODO(misko): this function needs to be removed 1914function getter(obj, path, bindFnToScope) { 1915 if (!path) return obj; 1916 var keys = path.split('.'); 1917 var key; 1918 var lastInstance = obj; 1919 var len = keys.length; 1920 1921 for (var i = 0; i < len; i++) { 1922 key = keys[i]; 1923 if (obj) { 1924 obj = (lastInstance = obj)[key]; 1925 } 1926 } 1927 if (!bindFnToScope && isFunction(obj)) { 1928 return bind(lastInstance, obj); 1929 } 1930 return obj; 1931} 1932 1933/** 1934 * Return the DOM siblings between the first and last node in the given array. 1935 * @param {Array} array like object 1936 * @returns {Array} the inputted object or a jqLite collection containing the nodes 1937 */ 1938function getBlockNodes(nodes) { 1939 // TODO(perf): update `nodes` instead of creating a new object? 1940 var node = nodes[0]; 1941 var endNode = nodes[nodes.length - 1]; 1942 var blockNodes; 1943 1944 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 1945 if (blockNodes || nodes[i] !== node) { 1946 if (!blockNodes) { 1947 blockNodes = jqLite(slice.call(nodes, 0, i)); 1948 } 1949 blockNodes.push(node); 1950 } 1951 } 1952 1953 return blockNodes || nodes; 1954} 1955 1956 1957/** 1958 * Creates a new object without a prototype. This object is useful for lookup without having to 1959 * guard against prototypically inherited properties via hasOwnProperty. 1960 * 1961 * Related micro-benchmarks: 1962 * - http://jsperf.com/object-create2 1963 * - http://jsperf.com/proto-map-lookup/2 1964 * - http://jsperf.com/for-in-vs-object-keys2 1965 * 1966 * @returns {Object} 1967 */ 1968function createMap() { 1969 return Object.create(null); 1970} 1971 1972var NODE_TYPE_ELEMENT = 1; 1973var NODE_TYPE_ATTRIBUTE = 2; 1974var NODE_TYPE_TEXT = 3; 1975var NODE_TYPE_COMMENT = 8; 1976var NODE_TYPE_DOCUMENT = 9; 1977var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1978 1979/** 1980 * @ngdoc type 1981 * @name angular.Module 1982 * @module ng 1983 * @description 1984 * 1985 * Interface for configuring angular {@link angular.module modules}. 1986 */ 1987 1988function setupModuleLoader(window) { 1989 1990 var $injectorMinErr = minErr('$injector'); 1991 var ngMinErr = minErr('ng'); 1992 1993 function ensure(obj, name, factory) { 1994 return obj[name] || (obj[name] = factory()); 1995 } 1996 1997 var angular = ensure(window, 'angular', Object); 1998 1999 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 2000 angular.$$minErr = angular.$$minErr || minErr; 2001 2002 return ensure(angular, 'module', function() { 2003 /** @type {Object.<string, angular.Module>} */ 2004 var modules = {}; 2005 2006 /** 2007 * @ngdoc function 2008 * @name angular.module 2009 * @module ng 2010 * @description 2011 * 2012 * The `angular.module` is a global place for creating, registering and retrieving Angular 2013 * modules. 2014 * All modules (angular core or 3rd party) that should be available to an application must be 2015 * registered using this mechanism. 2016 * 2017 * Passing one argument retrieves an existing {@link angular.Module}, 2018 * whereas passing more than one argument creates a new {@link angular.Module} 2019 * 2020 * 2021 * # Module 2022 * 2023 * A module is a collection of services, directives, controllers, filters, and configuration information. 2024 * `angular.module` is used to configure the {@link auto.$injector $injector}. 2025 * 2026 * ```js 2027 * // Create a new module 2028 * var myModule = angular.module('myModule', []); 2029 * 2030 * // register a new service 2031 * myModule.value('appName', 'MyCoolApp'); 2032 * 2033 * // configure existing services inside initialization blocks. 2034 * myModule.config(['$locationProvider', function($locationProvider) { 2035 * // Configure existing providers 2036 * $locationProvider.hashPrefix('!'); 2037 * }]); 2038 * ``` 2039 * 2040 * Then you can create an injector and load your modules like this: 2041 * 2042 * ```js 2043 * var injector = angular.injector(['ng', 'myModule']) 2044 * ``` 2045 * 2046 * However it's more likely that you'll just use 2047 * {@link ng.directive:ngApp ngApp} or 2048 * {@link angular.bootstrap} to simplify this process for you. 2049 * 2050 * @param {!string} name The name of the module to create or retrieve. 2051 * @param {!Array.<string>=} requires If specified then new module is being created. If 2052 * unspecified then the module is being retrieved for further configuration. 2053 * @param {Function=} configFn Optional configuration function for the module. Same as 2054 * {@link angular.Module#config Module#config()}. 2055 * @returns {angular.Module} new module with the {@link angular.Module} api. 2056 */ 2057 return function module(name, requires, configFn) { 2058 var assertNotHasOwnProperty = function(name, context) { 2059 if (name === 'hasOwnProperty') { 2060 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2061 } 2062 }; 2063 2064 assertNotHasOwnProperty(name, 'module'); 2065 if (requires && modules.hasOwnProperty(name)) { 2066 modules[name] = null; 2067 } 2068 return ensure(modules, name, function() { 2069 if (!requires) { 2070 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 2071 "the module name or forgot to load it. If registering a module ensure that you " + 2072 "specify the dependencies as the second argument.", name); 2073 } 2074 2075 /** @type {!Array.<Array.<*>>} */ 2076 var invokeQueue = []; 2077 2078 /** @type {!Array.<Function>} */ 2079 var configBlocks = []; 2080 2081 /** @type {!Array.<Function>} */ 2082 var runBlocks = []; 2083 2084 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2085 2086 /** @type {angular.Module} */ 2087 var moduleInstance = { 2088 // Private state 2089 _invokeQueue: invokeQueue, 2090 _configBlocks: configBlocks, 2091 _runBlocks: runBlocks, 2092 2093 /** 2094 * @ngdoc property 2095 * @name angular.Module#requires 2096 * @module ng 2097 * 2098 * @description 2099 * Holds the list of modules which the injector will load before the current module is 2100 * loaded. 2101 */ 2102 requires: requires, 2103 2104 /** 2105 * @ngdoc property 2106 * @name angular.Module#name 2107 * @module ng 2108 *
2109 * @description 2110 * Name of the module. 2111 */ 2112 name: name, 2113 2114 2115 /** 2116 * @ngdoc method 2117 * @name angular.Module#provider 2118 * @module ng 2119 * @param {string} name service name 2120 * @param {Function} providerType Construction function for creating new instance of the 2121 * service. 2122 * @description 2123 * See {@link auto.$provide#provider $provide.provider()}. 2124 */ 2125 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2126 2127 /** 2128 * @ngdoc method 2129 * @name angular.Module#factory 2130 * @module ng 2131 * @param {string} name service name 2132 * @param {Function} providerFunction Function for creating new instance of the service. 2133 * @description 2134 * See {@link auto.$provide#factory $provide.factory()}. 2135 */ 2136 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2137 2138 /** 2139 * @ngdoc method 2140 * @name angular.Module#service 2141 * @module ng 2142 * @param {string} name service name 2143 * @param {Function} constructor A constructor function that will be instantiated. 2144 * @description 2145 * See {@link auto.$provide#service $provide.service()}. 2146 */ 2147 service: invokeLaterAndSetModuleName('$provide', 'service'), 2148 2149 /** 2150 * @ngdoc method 2151 * @name angular.Module#value 2152 * @module ng 2153 * @param {string} name service name 2154 * @param {*} object Service instance object. 2155 * @description 2156 * See {@link auto.$provide#value $provide.value()}. 2157 */ 2158 value: invokeLater('$provide', 'value'), 2159 2160 /** 2161 * @ngdoc method 2162 * @name angular.Module#constant 2163 * @module ng 2164 * @param {string} name constant name 2165 * @param {*} object Constant value. 2166 * @description 2167 * Because the constants are fixed, they get applied before other provide methods. 2168 * See {@link auto.$provide#constant $provide.constant()}. 2169 */ 2170 constant: invokeLater('$provide', 'constant', 'unshift'), 2171 2172 /** 2173 * @ngdoc method 2174 * @name angular.Module#decorator 2175 * @module ng 2176 * @param {string} name The name of the service to decorate. 2177 * @param {Function} decorFn This function will be invoked when the service needs to be 2178 * instantiated and should return the decorated service instance. 2179 * @description 2180 * See {@link auto.$provide#decorator $provide.decorator()}. 2181 */ 2182 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 2183 2184 /** 2185 * @ngdoc method 2186 * @name angular.Module#animation 2187 * @module ng 2188 * @param {string} name animation name 2189 * @param {Function} animationFactory Factory function for creating new instance of an 2190 * animation. 2191 * @description 2192 * 2193 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2194 * 2195 * 2196 * Defines an animation hook that can be later used with 2197 * {@link $animate $animate} service and directives that use this service. 2198 * 2199 * ```js 2200 * module.animation('.animation-name', function($inject1, $inject2) { 2201 * return { 2202 * eventName : function(element, done) { 2203 * //code to run the animation 2204 * //once complete, then run done() 2205 * return function cancellationFunction(element) { 2206 * //code to cancel the animation 2207 * } 2208 * } 2209 * } 2210 * }) 2211 * ``` 2212 * 2213 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2214 * {@link ngAnimate ngAnimate module} for more information. 2215 */ 2216 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2217 2218 /** 2219 * @ngdoc method 2220 * @name angular.Module#filter 2221 * @module ng 2222 * @param {string} name Filter name - this must be a valid angular expression identifier 2223 * @param {Function} filterFactory Factory function for creating new instance of filter.
2224 * @description 2225 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2226 * 2227 * <div class="alert alert-warning"> 2228 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2229 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2230 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2231 * (`myapp_subsection_filterx`). 2232 * </div> 2233 */ 2234 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2235 2236 /** 2237 * @ngdoc method 2238 * @name angular.Module#controller 2239 * @module ng 2240 * @param {string|Object} name Controller name, or an object map of controllers where the 2241 * keys are the names and the values are the constructors. 2242 * @param {Function} constructor Controller constructor function. 2243 * @description 2244 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2245 */ 2246 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2247 2248 /** 2249 * @ngdoc method 2250 * @name angular.Module#directive 2251 * @module ng 2252 * @param {string|Object} name Directive name, or an object map of directives where the 2253 * keys are the names and the values are the factories. 2254 * @param {Function} directiveFactory Factory function for creating new instance of 2255 * directives. 2256 * @description 2257 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2258 */ 2259 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2260 2261 /** 2262 * @ngdoc method 2263 * @name angular.Module#component 2264 * @module ng 2265 * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) 2266 * @param {Object} options Component definition object (a simplified 2267 * {@link ng.$compile#directive-definition-object directive definition object}) 2268 * 2269 * @description 2270 * See {@link ng.$compileProvider#component $compileProvider.component()}. 2271 */ 2272 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 2273 2274 /** 2275 * @ngdoc method 2276 * @name angular.Module#config 2277 * @module ng 2278 * @param {Function} configFn Execute this function on module load. Useful for service 2279 * configuration. 2280 * @description 2281 * Use this method to register work which needs to be performed on module loading. 2282 * For more about how to configure services, see 2283 * {@link providers#provider-recipe Provider Recipe}. 2284 */ 2285 config: config, 2286 2287 /** 2288 * @ngdoc method 2289 * @name angular.Module#run 2290 * @module ng 2291 * @param {Function} initializationFn Execute this function after injector creation. 2292 * Useful for application initialization. 2293 * @description 2294 * Use this method to register work which should be performed when the injector is done 2295 * loading all modules. 2296 */ 2297 run: function(block) { 2298 runBlocks.push(block); 2299 return this; 2300 } 2301 }; 2302 2303 if (configFn) { 2304 config(configFn); 2305 } 2306 2307 return moduleInstance; 2308 2309 /** 2310 * @param {string} provider 2311 * @param {string} method 2312 * @param {String=} insertMethod 2313 * @returns {angular.Module} 2314 */ 2315 function invokeLater(provider, method, insertMethod, queue) { 2316 if (!queue) queue = invokeQueue; 2317 return function() { 2318 queue[insertMethod || 'push']([provider, method, arguments]); 2319 return moduleInstance; 2320 }; 2321 } 2322 2323 /** 2324 * @param {string} provider 2325 * @param {string} method 2326 * @returns {angular.Module} 2327 */
vendor: 4,714 bytes, lines 2328-2527
2328 function invokeLaterAndSetModuleName(provider, method) { 2329 return function(recipeName, factoryFunction) { 2330 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2331 invokeQueue.push([provider, method, arguments]); 2332 return moduleInstance; 2333 }; 2334 } 2335 }); 2336 }; 2337 }); 2338 2339} 2340 2341/* global: toDebugString: true */ 2342 2343function serializeObject(obj) { 2344 var seen = []; 2345 2346 return JSON.stringify(obj, function(key, val) { 2347 val = toJsonReplacer(key, val); 2348 if (isObject(val)) { 2349 2350 if (seen.indexOf(val) >= 0) return '...'; 2351 2352 seen.push(val); 2353 } 2354 return val; 2355 }); 2356} 2357 2358function toDebugString(obj) { 2359 if (typeof obj === 'function') { 2360 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2361 } else if (isUndefined(obj)) { 2362 return 'undefined'; 2363 } else if (typeof obj !== 'string') { 2364 return serializeObject(obj); 2365 } 2366 return obj; 2367} 2368 2369/* global angularModule: true, 2370 version: true, 2371 2372 $CompileProvider, 2373 2374 htmlAnchorDirective, 2375 inputDirective, 2376 inputDirective, 2377 formDirective, 2378 scriptDirective, 2379 selectDirective, 2380 styleDirective, 2381 optionDirective, 2382 ngBindDirective, 2383 ngBindHtmlDirective, 2384 ngBindTemplateDirective, 2385 ngClassDirective, 2386 ngClassEvenDirective, 2387 ngClassOddDirective, 2388 ngCloakDirective, 2389 ngControllerDirective, 2390 ngFormDirective, 2391 ngHideDirective, 2392 ngIfDirective, 2393 ngIncludeDirective, 2394 ngIncludeFillContentDirective, 2395 ngInitDirective, 2396 ngNonBindableDirective, 2397 ngPluralizeDirective, 2398 ngRepeatDirective, 2399 ngShowDirective, 2400 ngStyleDirective, 2401 ngSwitchDirective, 2402 ngSwitchWhenDirective, 2403 ngSwitchDefaultDirective, 2404 ngOptionsDirective, 2405 ngTranscludeDirective, 2406 ngModelDirective, 2407 ngListDirective, 2408 ngChangeDirective, 2409 patternDirective, 2410 patternDirective, 2411 requiredDirective, 2412 requiredDirective, 2413 minlengthDirective, 2414 minlengthDirective, 2415 maxlengthDirective, 2416 maxlengthDirective, 2417 ngValueDirective, 2418 ngModelOptionsDirective, 2419 ngAttributeAliasDirectives, 2420 ngEventDirectives, 2421 2422 $AnchorScrollProvider, 2423 $AnimateProvider, 2424 $CoreAnimateCssProvider, 2425 $$CoreAnimateJsProvider, 2426 $$CoreAnimateQueueProvider, 2427 $$AnimateRunnerFactoryProvider, 2428 $$AnimateAsyncRunFactoryProvider, 2429 $BrowserProvider, 2430 $CacheFactoryProvider, 2431 $ControllerProvider, 2432 $DateProvider, 2433 $DocumentProvider, 2434 $ExceptionHandlerProvider, 2435 $FilterProvider, 2436 $$ForceReflowProvider, 2437 $InterpolateProvider, 2438 $IntervalProvider, 2439 $$HashMapProvider, 2440 $HttpProvider, 2441 $HttpParamSerializerProvider, 2442 $HttpParamSerializerJQLikeProvider, 2443 $HttpBackendProvider, 2444 $xhrFactoryProvider, 2445 $LocationProvider, 2446 $LogProvider, 2447 $ParseProvider, 2448 $RootScopeProvider, 2449 $QProvider, 2450 $$QProvider, 2451 $$SanitizeUriProvider, 2452 $SceProvider, 2453 $SceDelegateProvider, 2454 $SnifferProvider, 2455 $TemplateCacheProvider, 2456 $TemplateRequestProvider, 2457 $$TestabilityProvider, 2458 $TimeoutProvider, 2459 $$RAFProvider, 2460 $WindowProvider, 2461 $$jqLiteProvider, 2462 $$CookieReaderProvider 2463*/ 2464 2465 2466/** 2467 * @ngdoc object 2468 * @name angular.version 2469 * @module ng 2470 * @description 2471 * An object that contains information about the current AngularJS version. 2472 * 2473 * This object has the following properties: 2474 * 2475 * - `full` – `{string}` – Full version string, such as "0.9.18". 2476 * - `major` – `{number}` – Major version number, such as "0". 2477 * - `minor` – `{number}` – Minor version number, such as "9". 2478 * - `dot` – `{number}` – Dot version number, such as "18". 2479 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". 2480 */ 2481var version = { 2482 full: '1.5.5', // all of these placeholder strings will be replaced by grunt's 2483 major: 1, // package task 2484 minor: 5, 2485 dot: 5, 2486 codeName: 'material-conspiration' 2487}; 2488 2489 2490function publishExternalAPI(angular) { 2491 extend(angular, { 2492 'bootstrap': bootstrap, 2493 'copy': copy, 2494 'extend': extend, 2495 'merge': merge, 2496 'equals': equals, 2497 'element': jqLite, 2498 'forEach': forEach, 2499 'injector': createInjector, 2500 'noop': noop, 2501 'bind': bind, 2502 'toJson': toJson, 2503 'fromJson': fromJson, 2504 'identity': identity, 2505 'isUndefined': isUndefined, 2506 'isDefined': isDefined, 2507 'isString': isString, 2508 'isFunction': isFunction, 2509 'isObject': isObject, 2510 'isNumber': isNumber, 2511 'isElement': isElement, 2512 'isArray': isArray, 2513 'version': version, 2514 'isDate': isDate, 2515 'lowercase': lowercase, 2516 'uppercase': uppercase, 2517 'callbacks': {counter: 0}, 2518 'getTestability': getTestability, 2519 '$$minErr': minErr, 2520 '$$csp': csp, 2521 'reloadWithDebugInfo': reloadWithDebugInfo 2522 }); 2523 2524 angularModule = setupModuleLoader(window); 2525 2526 angularModule('ng', ['ngLocale'], ['$provide', 2527 function ngModule($provide) {
2528 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2529 $provide.provider({ 2530 $$sanitizeUri: $$SanitizeUriProvider 2531 }); 2532 $provide.provider('$compile', $CompileProvider). 2533 directive({ 2534 a: htmlAnchorDirective, 2535 input: inputDirective, 2536 textarea: inputDirective, 2537 form: formDirective, 2538 script: scriptDirective, 2539 select: selectDirective, 2540 style: styleDirective, 2541 option: optionDirective, 2542 ngBind: ngBindDirective, 2543 ngBindHtml: ngBindHtmlDirective, 2544 ngBindTemplate: ngBindTemplateDirective, 2545 ngClass: ngClassDirective, 2546 ngClassEven: ngClassEvenDirective, 2547 ngClassOdd: ngClassOddDirective, 2548 ngCloak: ngCloakDirective, 2549 ngController: ngControllerDirective, 2550 ngForm: ngFormDirective, 2551 ngHide: ngHideDirective, 2552 ngIf: ngIfDirective, 2553 ngInclude: ngIncludeDirective, 2554 ngInit: ngInitDirective, 2555 ngNonBindable: ngNonBindableDirective, 2556 ngPluralize: ngPluralizeDirective, 2557 ngRepeat: ngRepeatDirective, 2558 ngShow: ngShowDirective, 2559 ngStyle: ngStyleDirective, 2560 ngSwitch: ngSwitchDirective, 2561 ngSwitchWhen: ngSwitchWhenDirective, 2562 ngSwitchDefault: ngSwitchDefaultDirective, 2563 ngOptions: ngOptionsDirective, 2564 ngTransclude: ngTranscludeDirective, 2565 ngModel: ngModelDirective, 2566 ngList: ngListDirective, 2567 ngChange: ngChangeDirective, 2568 pattern: patternDirective, 2569 ngPattern: patternDirective, 2570 required: requiredDirective, 2571 ngRequired: requiredDirective, 2572 minlength: minlengthDirective, 2573 ngMinlength: minlengthDirective, 2574 maxlength: maxlengthDirective, 2575 ngMaxlength: maxlengthDirective, 2576 ngValue: ngValueDirective, 2577 ngModelOptions: ngModelOptionsDirective 2578 }). 2579 directive({ 2580 ngInclude: ngIncludeFillContentDirective 2581 }). 2582 directive(ngAttributeAliasDirectives). 2583 directive(ngEventDirectives); 2584 $provide.provider({ 2585 $anchorScroll: $AnchorScrollProvider, 2586 $animate: $AnimateProvider, 2587 $animateCss: $CoreAnimateCssProvider, 2588 $$animateJs: $$CoreAnimateJsProvider, 2589 $$animateQueue: $$CoreAnimateQueueProvider, 2590 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 2591 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 2592 $browser: $BrowserProvider, 2593 $cacheFactory: $CacheFactoryProvider, 2594 $controller: $ControllerProvider, 2595 $document: $DocumentProvider, 2596 $exceptionHandler: $ExceptionHandlerProvider, 2597 $filter: $FilterProvider, 2598 $$forceReflow: $$ForceReflowProvider, 2599 $interpolate: $InterpolateProvider, 2600 $interval: $IntervalProvider, 2601 $http: $HttpProvider, 2602 $httpParamSerializer: $HttpParamSerializerProvider, 2603 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2604 $httpBackend: $HttpBackendProvider, 2605 $xhrFactory: $xhrFactoryProvider, 2606 $location: $LocationProvider, 2607 $log: $LogProvider, 2608 $parse: $ParseProvider, 2609 $rootScope: $RootScopeProvider, 2610 $q: $QProvider, 2611 $$q: $$QProvider, 2612 $sce: $SceProvider, 2613 $sceDelegate: $SceDelegateProvider, 2614 $sniffer: $SnifferProvider, 2615 $templateCache: $TemplateCacheProvider, 2616 $templateRequest: $TemplateRequestProvider, 2617 $$testability: $$TestabilityProvider, 2618 $timeout: $TimeoutProvider, 2619 $window: $WindowProvider, 2620 $$rAF: $$RAFProvider, 2621 $$jqLite: $$jqLiteProvider, 2622 $$HashMap: $$HashMapProvider, 2623 $$cookieReader: $$CookieReaderProvider 2624 }); 2625 } 2626 ]); 2627} 2628 2629/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2630 * Any commits to this file should be reviewed with security in mind. * 2631 * Changes to this file can potentially create security vulnerabilities. * 2632 * An approval from 2 Core members with history of modifying * 2633 * this file is required. * 2634 * * 2635 * Does the change somehow allow for arbitrary javascript to be executed? * 2636 * Or allows for someone to change the prototype of built-in objects? * 2637 * Or gives undesired access to variables likes document or window? * 2638 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2639
2640/* global JQLitePrototype: true, 2641 addEventListenerFn: true, 2642 removeEventListenerFn: true, 2643 BOOLEAN_ATTR: true, 2644 ALIASED_ATTR: true, 2645*/ 2646 2647////////////////////////////////// 2648//JQLite 2649////////////////////////////////// 2650 2651/** 2652 * @ngdoc function 2653 * @name angular.element 2654 * @module ng 2655 * @kind function 2656 * 2657 * @description 2658 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2659 * 2660 * If jQuery is available, `angular.element` is an alias for the 2661 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2662 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 2663 * 2664 * jqLite is a tiny, API-compatible subset of jQuery that allows 2665 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 2666 * commonly needed functionality with the goal of having a very small footprint. 2667 * 2668 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 2669 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 2670 * specific version of jQuery if multiple versions exist on the page. 2671 * 2672 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or 2673 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 2674 * 2675 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 2676 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 2677 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 2678 * 2679 * ## Angular's jqLite 2680 * jqLite provides only the following jQuery methods: 2681 * 2682 * - [`addClass()`](http://api.jquery.com/addClass/) 2683 * - [`after()`](http://api.jquery.com/after/) 2684 * - [`append()`](http://api.jquery.com/append/) 2685 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2686 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2687 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2688 * - [`clone()`](http://api.jquery.com/clone/) 2689 * - [`contents()`](http://api.jquery.com/contents/) 2690 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 2691 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 2692 * - [`data()`](http://api.jquery.com/data/) 2693 * - [`detach()`](http://api.jquery.com/detach/) 2694 * - [`empty()`](http://api.jquery.com/empty/) 2695 * - [`eq()`](http://api.jquery.com/eq/) 2696 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2697 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2698 * - [`html()`](http://api.jquery.com/html/) 2699 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2700 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2701 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 2702 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2703 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2704 * - [`prepend()`](http://api.jquery.com/prepend/) 2705 * - [`prop()`](http://api.jquery.com/prop/) 2706 * - [`ready()`](http://api.jquery.com/ready/) 2707 * - [`remove()`](http://api.jquery.com/remove/) 2708 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2709 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2710 * - [`removeData()`](http://api.jquery.com/removeData/) 2711 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2712 * - [`text()`](http://api.jquery.com/text/) 2713 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2714 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2715 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 2716 * - [`val()`](http://api.jquery.com/val/) 2717 * - [`wrap()`](http://api.jquery.com/wrap/) 2718 * 2719 * ## jQuery/jqLite Extras 2720 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2721 * 2722 * ### Events 2723 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2723pis and fires this event 2724 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2725 * element before it is removed. 2726 * 2727 * ### Methods 2728 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2729 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2730 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2731 * `'ngModel'`). 2732 * - `injector()` - retrieves the injector of the current element or its parent. 2733 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2734 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2735 * be enabled. 2736 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2737 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2738 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2739 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2740 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2741 * parent element is reached. 2742 * 2743 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See 2744 * https://github.com/angular/angular.js/issues/14251 for more information. 2745 * 2746 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2747 * @returns {Object} jQuery object. 2748 */ 2749 2750JQLite.expando = 'ng339'; 2751 2752var jqCache = JQLite.cache = {}, 2753 jqId = 1, 2754 addEventListenerFn = function(element, type, fn) { 2755 element.addEventListener(type, fn, false); 2756 }, 2757 removeEventListenerFn = function(element, type, fn) { 2758 element.removeEventListener(type, fn, false); 2759 }; 2760 2761/* 2762 * !!! This is an undocumented "private" function !!! 2763 */ 2764JQLite._data = function(node) { 2765 //jQuery always returns an object on cache miss 2766 return this.cache[node[this.expando]] || {}; 2767}; 2768 2769function jqNextId() { return ++jqId; } 2770 2771 2772var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2773var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2774var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2775var jqLiteMinErr = minErr('jqLite'); 2776 2777/** 2778 * Converts snake_case to camelCase. 2779 * Also there is special case for Moz prefix starting with upper case letter. 2780 * @param name Name to normalize 2781 */ 2782function camelCase(name) { 2783 return name. 2784 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2785 return offset ? letter.toUpperCase() : letter; 2786 }). 2787 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2788} 2789 2790var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 2791var HTML_REGEXP = /<|&#?\w+;/; 2792var TAG_NAME_REGEXP = /<([\w:-]+)/; 2793var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 2794 2795var wrapMap = { 2796 'option': [1, '<select multiple="multiple">', '</select>'], 2797 2798 'thead': [1, '<table>', '</table>'], 2799 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2800 'tr': [2, '<table><tbody>', '</tbody></table>'], 2801 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2802 '_default': [0, "", ""] 2803}; 2804 2805wrapMap.optgroup = wrapMap.option; 2806wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2807wrapMap.th = wrapMap.td; 2808 2809 2810function jqLiteIsTextNode(html) { 2811 return !HTML_REGEXP.test(html); 2812} 2813 2814function jqLiteAcceptsData(node) { 2815 // The window object can accept data but has no nodeType 2816 // Otherwise we are only interested in elements (1) and documents (9) 2817 var nodeType = node.nodeType; 2818 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2819} 2820 2821function jqLiteHasData(node) { 2822 for (var key in jqCache[node.ng339]) { 2823 return true; 2824 } 2825 return false; 2826} 2827 2828function jqLiteCleanData(nodes) { 2829 for (var i = 0, ii = nodes.length; i < ii; i++) { 2830 jqLiteRemoveData(nodes[i]); 2831 } 2832} 2833 2834function jqLiteBuildFragment(html, context) { 2835 var tmp, tag, wrap, 2836 fragment = context.createDocumentFragment(), 2837 nodes = [], i; 2838 2839 if (jqLiteIsTextNode(html)) { 2840 // Convert non-html into a text node 2841 nodes.push(context.createTextNode(html)); 2842 } else { 2843 // Convert html into DOM nodes 2844 tmp = tmp || fragment.appendChild(context.createElement("div")); 2845 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2846 wrap = wrapMap[tag] || wrapMap._default; 2847 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2848 2849 // Descend through wrappers to the right content 2850 i = wrap[0]; 2851 while (i--) { 2852 tmp = tmp.lastChild; 2853 } 2854 2855 nodes = concat(nodes, tmp.childNodes); 2856 2857 tmp = fragment.firstChild; 2858 tmp.textContent = ""; 2859 } 2860 2861 // Remove wrapper from fragment 2862 fragment.textContent = ""; 2863 fragment.innerHTML = ""; // Clear inner HTML 2864 forEach(nodes, function(node) { 2865 fragment.appendChild(node); 2866 }); 2867 2868 return fragment; 2869} 2870 2871function jqLiteParseHTML(html, context) { 2872 context = context || window.document; 2873 var parsed; 2874 2875 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2876 return [context.createElement(parsed[1])]; 2877 } 2878 2879 if ((parsed = jqLiteBuildFragment(html, context))) { 2880 return parsed.childNodes; 2881 } 2882 2883 return []; 2884} 2885 2886function jqLiteWrapNode(node, wrapper) { 2887 var parent = node.parentNode; 2888 2889 if (parent) { 2890 parent.replaceChild(wrapper, node); 2891 } 2892 2893 wrapper.appendChild(node); 2894} 2895 2896 2897// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 2898var jqLiteContains = window.Node.prototype.contains || function(arg) { 2899 // jshint bitwise: false 2900 return !!(this.compareDocumentPosition(arg) & 16); 2901 // jshint bitwise: true 2902}; 2903 2904///////////////////////////////////////////// 2905function JQLite(element) { 2906 if (element instanceof JQLite) { 2907 return element; 2908 } 2909 2910 var argIsString; 2911 2912 if (isString(element)) { 2913 element = trim(element); 2914 argIsString = true; 2915 } 2916 if (!(this instanceof JQLite)) { 2917 if (argIsString && element.charAt(0) != '<') { 2918 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2919 } 2920 return new JQLite(element); 2921 } 2922 2923 if (argIsString) { 2924 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2925 } else { 2926 jqLiteAddNodes(this, element); 2927 } 2928} 2929 2930function jqLiteClone(element) { 2931 return element.cloneNode(true); 2932} 2933 2934function jqLiteDealoc(element, onlyDescendants) { 2935 if (!onlyDescendants) jqLiteRemoveData(element); 2936 2937 if (element.querySelectorAll) { 2938 var descendants = element.querySelectorAll('*');
vendor: 21,520 bytes, lines 2939-3670
2939 for (var i = 0, l = descendants.length; i < l; i++) { 2940 jqLiteRemoveData(descendants[i]); 2941 } 2942 } 2943} 2944 2945function jqLiteOff(element, type, fn, unsupported) { 2946 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2947 2948 var expandoStore = jqLiteExpandoStore(element); 2949 var events = expandoStore && expandoStore.events; 2950 var handle = expandoStore && expandoStore.handle; 2951 2952 if (!handle) return; //no listeners registered 2953 2954 if (!type) { 2955 for (type in events) { 2956 if (type !== '$destroy') { 2957 removeEventListenerFn(element, type, handle); 2958 } 2959 delete events[type]; 2960 } 2961 } else { 2962 2963 var removeHandler = function(type) { 2964 var listenerFns = events[type]; 2965 if (isDefined(fn)) { 2966 arrayRemove(listenerFns || [], fn); 2967 } 2968 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 2969 removeEventListenerFn(element, type, handle); 2970 delete events[type]; 2971 } 2972 }; 2973 2974 forEach(type.split(' '), function(type) { 2975 removeHandler(type); 2976 if (MOUSE_EVENT_MAP[type]) { 2977 removeHandler(MOUSE_EVENT_MAP[type]); 2978 } 2979 }); 2980 } 2981} 2982 2983function jqLiteRemoveData(element, name) { 2984 var expandoId = element.ng339; 2985 var expandoStore = expandoId && jqCache[expandoId]; 2986 2987 if (expandoStore) { 2988 if (name) { 2989 delete expandoStore.data[name]; 2990 return; 2991 } 2992 2993 if (expandoStore.handle) { 2994 if (expandoStore.events.$destroy) { 2995 expandoStore.handle({}, '$destroy'); 2996 } 2997 jqLiteOff(element); 2998 } 2999 delete jqCache[expandoId]; 3000 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 3001 } 3002} 3003 3004 3005function jqLiteExpandoStore(element, createIfNecessary) { 3006 var expandoId = element.ng339, 3007 expandoStore = expandoId && jqCache[expandoId]; 3008 3009 if (createIfNecessary && !expandoStore) { 3010 element.ng339 = expandoId = jqNextId(); 3011 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 3012 } 3013 3014 return expandoStore; 3015} 3016 3017 3018function jqLiteData(element, key, value) { 3019 if (jqLiteAcceptsData(element)) { 3020 3021 var isSimpleSetter = isDefined(value); 3022 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 3023 var massGetter = !key; 3024 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 3025 var data = expandoStore && expandoStore.data; 3026 3027 if (isSimpleSetter) { // data('key', value) 3028 data[key] = value; 3029 } else { 3030 if (massGetter) { // data() 3031 return data; 3032 } else { 3033 if (isSimpleGetter) { // data('key') 3034 // don't force creation of expandoStore if it doesn't exist yet 3035 return data && data[key]; 3036 } else { // mass-setter: data({key1: val1, key2: val2}) 3037 extend(data, key); 3038 } 3039 } 3040 } 3041 } 3042} 3043 3044function jqLiteHasClass(element, selector) { 3045 if (!element.getAttribute) return false; 3046 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 3047 indexOf(" " + selector + " ") > -1); 3048} 3049 3050function jqLiteRemoveClass(element, cssClasses) { 3051 if (cssClasses && element.setAttribute) { 3052 forEach(cssClasses.split(' '), function(cssClass) { 3053 element.setAttribute('class', trim( 3054 (" " + (element.getAttribute('class') || '') + " ") 3055 .replace(/[\n\t]/g, " ") 3056 .replace(" " + trim(cssClass) + " ", " ")) 3057 ); 3058 }); 3059 } 3060} 3061 3062function jqLiteAddClass(element, cssClasses) { 3063 if (cssClasses && element.setAttribute) { 3064 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3065 .replace(/[\n\t]/g, " "); 3066 3067 forEach(cssClasses.split(' '), function(cssClass) { 3068 cssClass = trim(cssClass); 3069 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 3070 existingClasses += cssClass + ' '; 3071 } 3072 }); 3073 3074 element.setAttribute('class', trim(existingClasses)); 3075 } 3076} 3077 3078 3079function jqLiteAddNodes(root, elements) { 3080 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 3081 3082 if (elements) { 3083 3084 // if a Node (the most common case) 3085 if (elements.nodeType) { 3086 root[root.length++] = elements; 3087 } else { 3088 var length = elements.length; 3089 3090 // if an Array or NodeList and not a Window 3091 if (typeof length === 'number' && elements.window !== elements) { 3092 if (length) { 3093 for (var i = 0; i < length; i++) { 3094 root[root.length++] = elements[i]; 3095 } 3096 } 3097 } else { 3098 root[root.length++] = elements; 3099 } 3100 } 3101 } 3102} 3103 3104 3105function jqLiteController(element, name) { 3106 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 3107} 3108 3109function jqLiteInheritedData(element, name, value) { 3110 // if element is the document object work with the html element instead 3111 // this makes $(document).scope() possible 3112 if (element.nodeType == NODE_TYPE_DOCUMENT) { 3113 element = element.documentElement; 3114 } 3115 var names = isArray(name) ? name : [name]; 3116 3117 while (element) { 3118 for (var i = 0, ii = names.length; i < ii; i++) { 3119 if (isDefined(value = jqLite.data(element, names[i]))) return value; 3120 } 3121 3122 // If dealing with a document fragment node with a host element, and no parent, use the host 3123 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 3124 // to lookup parent controllers. 3125 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 3126 } 3127} 3128 3129function jqLiteEmpty(element) { 3130 jqLiteDealoc(element, true); 3131 while (element.firstChild) { 3132 element.removeChild(element.firstChild); 3133 } 3134} 3135 3136function jqLiteRemove(element, keepData) { 3137 if (!keepData) jqLiteDealoc(element); 3138 var parent = element.parentNode; 3139 if (parent) parent.removeChild(element); 3140} 3141 3142 3143function jqLiteDocumentLoaded(action, win) { 3144 win = win || window; 3145 if (win.document.readyState === 'complete') { 3146 // Force the action to be run async for consistent behavior 3147 // from the action's point of view 3148 // i.e. it will definitely not be in a $apply 3149 win.setTimeout(action); 3150 } else { 3151 // No need to unbind this handler as load is only ever called once 3152 jqLite(win).on('load', action); 3153 } 3154} 3155 3156////////////////////////////////////////// 3157// Functions which are declared directly. 3158////////////////////////////////////////// 3159var JQLitePrototype = JQLite.prototype = { 3160 ready: function(fn) { 3161 var fired = false; 3162 3163 function trigger() { 3164 if (fired) return; 3165 fired = true; 3166 fn(); 3167 } 3168 3169 // check if document is already loaded 3170 if (window.document.readyState === 'complete') { 3171 window.setTimeout(trigger); 3172 } else { 3173 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 3174 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 3175 // jshint -W064 3176 JQLite(window).on('load', trigger); // fallback to window.onload for others 3177 // jshint +W064 3178 } 3179 }, 3180 toString: function() { 3181 var value = []; 3182 forEach(this, function(e) { value.push('' + e);}); 3183 return '[' + value.join(', ') + ']'; 3184 }, 3185 3186 eq: function(index) { 3187 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3188 }, 3189 3190 length: 0, 3191 push: push, 3192 sort: [].sort, 3193 splice: [].splice 3194}; 3195 3196////////////////////////////////////////// 3197// Functions iterating getter/setters. 3198// these functions return self on setter and 3199// value on get. 3200////////////////////////////////////////// 3201var BOOLEAN_ATTR = {}; 3202forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3203 BOOLEAN_ATTR[lowercase(value)] = value; 3204}); 3205var BOOLEAN_ELEMENTS = {}; 3206forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3207 BOOLEAN_ELEMENTS[value] = true; 3208}); 3209var ALIASED_ATTR = { 3210 'ngMinlength': 'minlength', 3211 'ngMaxlength': 'maxlength', 3212 'ngMin': 'min', 3213 'ngMax': 'max', 3214 'ngPattern': 'pattern' 3215}; 3216 3217function getBooleanAttrName(element, name) { 3218 // check dom last since we will most likely fail on name 3219 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3220 3221 // booleanAttr is here twice to minimize DOM access 3222 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3223} 3224 3225function getAliasedAttrName(name) { 3226 return ALIASED_ATTR[name]; 3227} 3228 3229forEach({ 3230 data: jqLiteData, 3231 removeData: jqLiteRemoveData, 3232 hasData: jqLiteHasData, 3233 cleanData: jqLiteCleanData 3234}, function(fn, name) { 3235 JQLite[name] = fn; 3236}); 3237 3238forEach({ 3239 data: jqLiteData, 3240 inheritedData: jqLiteInheritedData, 3241 3242 scope: function(element) { 3243 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3244 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3245 }, 3246 3247 isolateScope: function(element) { 3248 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3249 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3250 }, 3251 3252 controller: jqLiteController, 3253 3254 injector: function(element) { 3255 return jqLiteInheritedData(element, '$injector'); 3256 }, 3257 3258 removeAttr: function(element, name) { 3259 element.removeAttribute(name); 3260 }, 3261 3262 hasClass: jqLiteHasClass, 3263 3264 css: function(element, name, value) { 3265 name = camelCase(name); 3266 3267 if (isDefined(value)) { 3268 element.style[name] = value; 3269 } else { 3270 return element.style[name]; 3271 } 3272 }, 3273 3274 attr: function(element, name, value) { 3275 var nodeType = element.nodeType; 3276 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3277 return; 3278 } 3279 var lowercasedName = lowercase(name); 3280 if (BOOLEAN_ATTR[lowercasedName]) { 3281 if (isDefined(value)) { 3282 if (!!value) { 3283 element[name] = true; 3284 element.setAttribute(name, lowercasedName); 3285 } else { 3286 element[name] = false; 3287 element.removeAttribute(lowercasedName); 3288 } 3289 } else { 3290 return (element[name] || 3291 (element.attributes.getNamedItem(name) || noop).specified) 3292 ? lowercasedName 3293 : undefined; 3294 } 3295 } else if (isDefined(value)) { 3296 element.setAttribute(name, value); 3297 } else if (element.getAttribute) { 3298 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3299 // some elements (e.g. Document) don't have get attribute, so return undefined 3300 var ret = element.getAttribute(name, 2); 3301 // normalize non-existing attributes to undefined (as jQuery) 3302 return ret === null ? undefined : ret; 3303 } 3304 }, 3305 3306 prop: function(element, name, value) { 3307 if (isDefined(value)) { 3308 element[name] = value; 3309 } else { 3310 return element[name]; 3311 } 3312 }, 3313 3314 text: (function() { 3315 getText.$dv = ''; 3316 return getText; 3317 3318 function getText(element, value) { 3319 if (isUndefined(value)) { 3320 var nodeType = element.nodeType; 3321 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3322 } 3323 element.textContent = value; 3324 } 3325 })(), 3326 3327 val: function(element, value) { 3328 if (isUndefined(value)) { 3329 if (element.multiple && nodeName_(element) === 'select') { 3330 var result = []; 3331 forEach(element.options, function(option) { 3332 if (option.selected) { 3333 result.push(option.value || option.text); 3334 } 3335 }); 3336 return result.length === 0 ? null : result; 3337 } 3338 return element.value; 3339 } 3340 element.value = value; 3341 }, 3342 3343 html: function(element, value) { 3344 if (isUndefined(value)) { 3345 return element.innerHTML; 3346 } 3347 jqLiteDealoc(element, true); 3348 element.innerHTML = value; 3349 }, 3350 3351 empty: jqLiteEmpty 3352}, function(fn, name) { 3353 /** 3354 * Properties: writes return selection, reads return first value 3355 */ 3356 JQLite.prototype[name] = function(arg1, arg2) { 3357 var i, key; 3358 var nodeCount = this.length; 3359 3360 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3361 // in a way that survives minification. 3362 // jqLiteEmpty takes no arguments but is a setter. 3363 if (fn !== jqLiteEmpty && 3364 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3365 if (isObject(arg1)) { 3366 3367 // we are a write, but the object properties are the key/values 3368 for (i = 0; i < nodeCount; i++) { 3369 if (fn === jqLiteData) { 3370 // data() takes the whole object in jQuery 3371 fn(this[i], arg1); 3372 } else { 3373 for (key in arg1) { 3374 fn(this[i], key, arg1[key]); 3375 } 3376 } 3377 } 3378 // return self for chaining 3379 return this; 3380 } else { 3381 // we are a read, so read the first child. 3382 // TODO: do we still need this? 3383 var value = fn.$dv; 3384 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3385 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3386 for (var j = 0; j < jj; j++) { 3387 var nodeValue = fn(this[j], arg1, arg2); 3388 value = value ? value + nodeValue : nodeValue; 3389 } 3390 return value; 3391 } 3392 } else { 3393 // we are a write, so apply to all children 3394 for (i = 0; i < nodeCount; i++) { 3395 fn(this[i], arg1, arg2); 3396 } 3397 // return self for chaining 3398 return this; 3399 } 3400 }; 3401}); 3402 3403function createEventHandler(element, events) { 3404 var eventHandler = function(event, type) { 3405 // jQuery specific api 3406 event.isDefaultPrevented = function() { 3407 return event.defaultPrevented; 3408 }; 3409 3410 var eventFns = events[type || event.type]; 3411 var eventFnsLength = eventFns ? eventFns.length : 0; 3412 3413 if (!eventFnsLength) return; 3414 3415 if (isUndefined(event.immediatePropagationStopped)) { 3416 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3417 event.stopImmediatePropagation = function() { 3418 event.immediatePropagationStopped = true; 3419 3420 if (event.stopPropagation) { 3421 event.stopPropagation(); 3422 } 3423 3424 if (originalStopImmediatePropagation) { 3425 originalStopImmediatePropagation.call(event); 3426 } 3427 }; 3428 } 3429 3430 event.isImmediatePropagationStopped = function() { 3431 return event.immediatePropagationStopped === true; 3432 }; 3433 3434 // Some events have special handlers that wrap the real handler 3435 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 3436 3437 // Copy event handlers in case event handlers array is modified during execution. 3438 if ((eventFnsLength > 1)) { 3439 eventFns = shallowCopy(eventFns); 3440 } 3441 3442 for (var i = 0; i < eventFnsLength; i++) { 3443 if (!event.isImmediatePropagationStopped()) { 3444 handlerWrapper(element, event, eventFns[i]); 3445 } 3446 } 3447 }; 3448 3449 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3450 // events on `element` 3451 eventHandler.elem = element; 3452 return eventHandler; 3453} 3454 3455function defaultHandlerWrapper(element, event, handler) { 3456 handler.call(element, event); 3457} 3458 3459function specialMouseHandlerWrapper(target, event, handler) { 3460 // Refer to jQuery's implementation of mouseenter & mouseleave 3461 // Read about mouseenter and mouseleave: 3462 // http://www.quirksmode.org/js/events_mouse.html#link8 3463 var related = event.relatedTarget; 3464 // For mousenter/leave call the handler if related is outside the target. 3465 // NB: No relatedTarget if the mouse left/entered the browser window 3466 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 3467 handler.call(target, event); 3468 } 3469} 3470 3471////////////////////////////////////////// 3472// Functions iterating traversal. 3473// These functions chain results into a single 3474// selector. 3475////////////////////////////////////////// 3476forEach({ 3477 removeData: jqLiteRemoveData, 3478 3479 on: function jqLiteOn(element, type, fn, unsupported) { 3480 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3481 3482 // Do not add event handlers to non-elements because they will not be cleaned up. 3483 if (!jqLiteAcceptsData(element)) { 3484 return; 3485 } 3486 3487 var expandoStore = jqLiteExpandoStore(element, true); 3488 var events = expandoStore.events; 3489 var handle = expandoStore.handle; 3490 3491 if (!handle) { 3492 handle = expandoStore.handle = createEventHandler(element, events); 3493 } 3494 3495 // http://jsperf.com/string-indexof-vs-split 3496 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3497 var i = types.length; 3498 3499 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 3500 var eventFns = events[type]; 3501 3502 if (!eventFns) { 3503 eventFns = events[type] = []; 3504 eventFns.specialHandlerWrapper = specialHandlerWrapper; 3505 if (type !== '$destroy' && !noEventListener) { 3506 addEventListenerFn(element, type, handle); 3507 } 3508 } 3509 3510 eventFns.push(fn); 3511 }; 3512 3513 while (i--) { 3514 type = types[i]; 3515 if (MOUSE_EVENT_MAP[type]) { 3516 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 3517 addHandler(type, undefined, true); 3518 } else { 3519 addHandler(type); 3520 } 3521 } 3522 }, 3523 3524 off: jqLiteOff, 3525 3526 one: function(element, type, fn) { 3527 element = jqLite(element); 3528 3529 //add the listener twice so that when it is called 3530 //you can remove the original function and still be 3531 //able to call element.off(ev, fn) normally 3532 element.on(type, function onFn() { 3533 element.off(type, fn); 3534 element.off(type, onFn); 3535 }); 3536 element.on(type, fn); 3537 }, 3538 3539 replaceWith: function(element, replaceNode) { 3540 var index, parent = element.parentNode; 3541 jqLiteDealoc(element); 3542 forEach(new JQLite(replaceNode), function(node) { 3543 if (index) { 3544 parent.insertBefore(node, index.nextSibling); 3545 } else { 3546 parent.replaceChild(node, element); 3547 } 3548 index = node; 3549 }); 3550 }, 3551 3552 children: function(element) { 3553 var children = []; 3554 forEach(element.childNodes, function(element) { 3555 if (element.nodeType === NODE_TYPE_ELEMENT) { 3556 children.push(element); 3557 } 3558 }); 3559 return children; 3560 }, 3561 3562 contents: function(element) { 3563 return element.contentDocument || element.childNodes || []; 3564 }, 3565 3566 append: function(element, node) { 3567 var nodeType = element.nodeType; 3568 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3569 3570 node = new JQLite(node); 3571 3572 for (var i = 0, ii = node.length; i < ii; i++) { 3573 var child = node[i]; 3574 element.appendChild(child); 3575 } 3576 }, 3577 3578 prepend: function(element, node) { 3579 if (element.nodeType === NODE_TYPE_ELEMENT) { 3580 var index = element.firstChild; 3581 forEach(new JQLite(node), function(child) { 3582 element.insertBefore(child, index); 3583 }); 3584 } 3585 }, 3586 3587 wrap: function(element, wrapNode) { 3588 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); 3589 }, 3590 3591 remove: jqLiteRemove, 3592 3593 detach: function(element) { 3594 jqLiteRemove(element, true); 3595 }, 3596 3597 after: function(element, newElement) { 3598 var index = element, parent = element.parentNode; 3599 newElement = new JQLite(newElement); 3600 3601 for (var i = 0, ii = newElement.length; i < ii; i++) { 3602 var node = newElement[i]; 3603 parent.insertBefore(node, index.nextSibling); 3604 index = node; 3605 } 3606 }, 3607 3608 addClass: jqLiteAddClass, 3609 removeClass: jqLiteRemoveClass, 3610 3611 toggleClass: function(element, selector, condition) { 3612 if (selector) { 3613 forEach(selector.split(' '), function(className) { 3614 var classCondition = condition; 3615 if (isUndefined(classCondition)) { 3616 classCondition = !jqLiteHasClass(element, className); 3617 } 3618 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3619 }); 3620 } 3621 }, 3622 3623 parent: function(element) { 3624 var parent = element.parentNode; 3625 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3626 }, 3627 3628 next: function(element) { 3629 return element.nextElementSibling; 3630 }, 3631 3632 find: function(element, selector) { 3633 if (element.getElementsByTagName) { 3634 return element.getElementsByTagName(selector); 3635 } else { 3636 return []; 3637 } 3638 }, 3639 3640 clone: jqLiteClone, 3641 3642 triggerHandler: function(element, event, extraParameters) { 3643 3644 var dummyEvent, eventFnsCopy, handlerArgs; 3645 var eventName = event.type || event; 3646 var expandoStore = jqLiteExpandoStore(element); 3647 var events = expandoStore && expandoStore.events; 3648 var eventFns = events && events[eventName]; 3649 3650 if (eventFns) { 3651 // Create a dummy event to pass to the handlers 3652 dummyEvent = { 3653 preventDefault: function() { this.defaultPrevented = true; }, 3654 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3655 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3656 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3657 stopPropagation: noop, 3658 type: eventName, 3659 target: element 3660 }; 3661 3662 // If a custom event was provided then extend our dummy event with it 3663 if (event.type) { 3664 dummyEvent = extend(dummyEvent, event); 3665 } 3666 3667 // Copy event handlers in case event handlers array is modified during execution. 3668 eventFnsCopy = shallowCopy(eventFns); 3669 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3670
3671 forEach(eventFnsCopy, function(fn) { 3672 if (!dummyEvent.isImmediatePropagationStopped()) { 3673 fn.apply(element, handlerArgs); 3674 } 3675 }); 3676 } 3677 } 3678}, function(fn, name) { 3679 /** 3680 * chaining functions 3681 */ 3682 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3683 var value; 3684 3685 for (var i = 0, ii = this.length; i < ii; i++) { 3686 if (isUndefined(value)) { 3687 value = fn(this[i], arg1, arg2, arg3); 3688 if (isDefined(value)) { 3689 // any function which returns a value needs to be wrapped 3690 value = jqLite(value); 3691 } 3692 } else { 3693 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3694 } 3695 } 3696 return isDefined(value) ? value : this; 3697 }; 3698 3699 // bind legacy bind/unbind to on/off 3700 JQLite.prototype.bind = JQLite.prototype.on; 3701 JQLite.prototype.unbind = JQLite.prototype.off; 3702}); 3703 3704 3705// Provider for private $$jqLite service 3706function $$jqLiteProvider() { 3707 this.$get = function $$jqLite() { 3708 return extend(JQLite, { 3709 hasClass: function(node, classes) { 3710 if (node.attr) node = node[0]; 3711 return jqLiteHasClass(node, classes); 3712 }, 3713 addClass: function(node, classes) { 3714 if (node.attr) node = node[0]; 3715 return jqLiteAddClass(node, classes); 3716 }, 3717 removeClass: function(node, classes) { 3718 if (node.attr) node = node[0]; 3719 return jqLiteRemoveClass(node, classes); 3720 } 3721 }); 3722 }; 3723} 3724 3725/** 3726 * Computes a hash of an 'obj'. 3727 * Hash of a: 3728 * string is string 3729 * number is number as string 3730 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3731 * that is also assigned to the $$hashKey property of the object. 3732 * 3733 * @param obj 3734 * @returns {string} hash string such that the same input will have the same hash string. 3735 * The resulting string key is in 'type:hashKey' format. 3736 */ 3737function hashKey(obj, nextUidFn) { 3738 var key = obj && obj.$$hashKey; 3739 3740 if (key) { 3741 if (typeof key === 'function') { 3742 key = obj.$$hashKey(); 3743 } 3744 return key; 3745 } 3746 3747 var objType = typeof obj; 3748 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3749 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3750 } else { 3751 key = objType + ':' + obj; 3752 } 3753 3754 return key; 3755} 3756 3757/** 3758 * HashMap which can use objects as keys 3759 */ 3760function HashMap(array, isolatedUid) { 3761 if (isolatedUid) { 3762 var uid = 0; 3763 this.nextUid = function() { 3764 return ++uid; 3765 }; 3766 } 3767 forEach(array, this.put, this); 3768} 3769HashMap.prototype = { 3770 /** 3771 * Store key value pair 3772 * @param key key to store can be any type 3773 * @param value value to store can be any type 3774 */ 3775 put: function(key, value) { 3776 this[hashKey(key, this.nextUid)] = value; 3777 }, 3778 3779 /** 3780 * @param key 3781 * @returns {Object} the value for the key 3782 */ 3783 get: function(key) { 3784 return this[hashKey(key, this.nextUid)]; 3785 }, 3786 3787 /** 3788 * Remove the key/value pair 3789 * @param key 3790 */ 3791 remove: function(key) { 3792 var value = this[key = hashKey(key, this.nextUid)]; 3793 delete this[key]; 3794 return value; 3795 } 3796}; 3797 3798var $$HashMapProvider = [function() { 3799 this.$get = [function() { 3800 return HashMap; 3801 }]; 3802}]; 3803 3804/** 3805 * @ngdoc function 3806 * @module ng 3807 * @name angular.injector 3808 * @kind function 3809 * 3810 * @description 3811 * Creates an injector object that can be used for retrieving services as well as for 3812 * dependency injection (see {@link guide/di dependency injection}). 3813 * 3814 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 3815 * {@link angular.module}. The `ng` module must be explicitly added. 3816 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3817 * disallows argument name annotation inference. 3818 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3819 * 3820 * @example 3821 * Typical usage 3822 * ```js 3823 * // create an injector 3824 * var $injector = angular.injector(['ng']); 3825 * 3826 * // use the injector to kick off your application 3827 * // use the type inference to auto inject arguments, or use impl
3827icit injection 3828 * $injector.invoke(function($rootScope, $compile, $document) { 3829 * $compile($document)($rootScope); 3830 * $rootScope.$digest(); 3831 * }); 3832 * ``` 3833 * 3834 * Sometimes you want to get access to the injector of a currently running Angular app 3835 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3836 * application has been bootstrapped. You can do this using the extra `injector()` added 3837 * to JQuery/jqLite elements. See {@link angular.element}. 3838 * 3839 * *This is fairly rare but could be the case if a third party library is injecting the 3840 * markup.* 3841 * 3842 * In the following example a new block of HTML containing a `ng-controller` 3843 * directive is added to the end of the document body by JQuery. We then compile and link 3844 * it into the current AngularJS scope. 3845 * 3846 * ```js 3847 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3848 * $(document.body).append($div); 3849 * 3850 * angular.element(document).injector().invoke(function($compile) { 3851 * var scope = angular.element($div).scope(); 3852 * $compile($div)(scope); 3853 * }); 3854 * ``` 3855 */ 3856 3857 3858/** 3859 * @ngdoc module 3860 * @name auto 3861 * @installation 3862 * @description 3863 * 3864 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3865 */ 3866 3867var ARROW_ARG = /^([^\(]+?)=>/; 3868var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 3869var FN_ARG_SPLIT = /,/; 3870var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3871var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3872var $injectorMinErr = minErr('$injector'); 3873 3874function extractArgs(fn) { 3875 var fnText = Function.prototype.toString.call(fn).replace(STRIP_COMMENTS, ''), 3876 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 3877 return args; 3878} 3879 3880function anonFn(fn) { 3881 // For anonymous functions, showing at the very least the function signature can help in 3882 // debugging. 3883 var args = extractArgs(fn); 3884 if (args) { 3885 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3886 } 3887 return 'fn'; 3888} 3889 3890function annotate(fn, strictDi, name) { 3891 var $inject, 3892 argDecl, 3893 last; 3894 3895 if (typeof fn === 'function') { 3896 if (!($inject = fn.$inject)) { 3897 $inject = []; 3898 if (fn.length) { 3899 if (strictDi) { 3900 if (!isString(name) || !name) { 3901 name = fn.name || anonFn(fn); 3902 } 3903 throw $injectorMinErr('strictdi', 3904 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3905 } 3906 argDecl = extractArgs(fn); 3907 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3908 arg.replace(FN_ARG, function(all, underscore, name) { 3909 $inject.push(name); 3910 }); 3911 }); 3912 } 3913 fn.$inject = $inject; 3914 } 3915 } else if (isArray(fn)) { 3916 last = fn.length - 1; 3917 assertArgFn(fn[last], 'fn'); 3918 $inject = fn.slice(0, last); 3919 } else { 3920 assertArgFn(fn, 'fn', true); 3921 } 3922 return $inject; 3923} 3924 3925/////////////////////////////////////// 3926 3927/** 3928 * @ngdoc service 3929 * @name $injector 3930 * 3931 * @description 3932 * 3933 * `$injector` is used to retrieve object instances as defined by 3934 * {@link auto.$provide provider}, instantiate types, invoke methods, 3935 * and load modules. 3936 * 3937 * The following always holds true: 3938 * 3939 * ```js 3940 * var $injector = angular.injector(); 3941 * expect($injector.get('$injector')).toBe($injector); 3942 * expect($injector.invoke(function($injector) { 3943 * return $injector; 3944 * })).toBe($injector); 3945 * ``` 3946 * 3947 * # Injection Function Annotation 3948 * 3949 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3950 * following are all valid ways of annotating function with injection arguments and are equivalent. 3951 * 3952 * ```js 3953 * // inferred (only works if code not minified/obfuscated) 3954 * $injector.invoke(function(serviceA){}); 3955 * 3956 * // annotated 3957 * function explicit(serviceA) {}; 3958 * explicit.$inject = ['serviceA']; 3959 * $injector.invoke(explicit); 3960 * 3961 * // inline 3962 * $injector.invoke(['serviceA', function(serviceA){}]); 3963 * ``` 3964 * 3965 * ## Inference 3966 * 3967 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3968 * can then be parsed and the function arguments can be extracted. This method of discovering 3969 * annotations is disallowed when the injector is in strict mode. 3970 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3971 * argument names. 3972 * 3973 * ## `$inject` Annotation 3974 * By adding an `$inject` property onto a function the injection parameters can be specified. 3975 * 3976 * ## Inline 3977 * As an array of injection names, where the last item in the array is the function to call. 3978 */ 3979 3980/** 3981 * @ngdoc method 3982 * @name $injector#get 3983 * 3984 * @description 3985 * Return an instance of the service. 3986 * 3987 * @param {string} name The name of the instance to retrieve. 3988 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 3989 * @return {*} The instance. 3990 */ 3991 3992/** 3993 * @ngdoc method 3994 * @name $injector#invoke 3995 * 3996 * @description 3997 * Invoke the method and supply the method arguments from the `$injector`. 3998 * 3999 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 4000 * injected according to the {@link guide/di $inject Annotation} rules. 4001 * @param {Object=} self The `this` for the invoked method. 4002 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4003 * object first, before the `$injector` is consulted. 4004 * @returns {*} the value returned by the invoked `fn` function. 4005 */ 4006 4007/** 4008 * @ngdoc method 4009 * @name $injector#has 4010 * 4011 * @description 4012 * Allows the user to query if the particular service exists. 4013 * 4014 * @param {string} name Name of the service to query. 4015 * @returns {boolean} `true` if injector has given service. 4016 */ 4017 4018/** 4019 * @ngdoc method 4020 * @name $injector#instantiate 4021 * @description 4022 * Create a new instance of JS type. The method takes a constructor function, invokes the new 4023 * operator, and supplies all of the arguments to the constructor function as specified by the 4024 * constructor annotation. 4025 * 4026 * @param {Function} Type Annotated constructor function. 4027 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4028 * object first, before the `$injector` is consulted. 4029 * @returns {Object} new instance of `Type`. 4030 */ 4031 4032/** 4033 * @ngdoc method 4034 * @name $injector#annotate 4035 * 4036 * @description 4037 * Returns an array of service names which the function is requesting for injection. This API is 4038 * used by the injector to determine which services need to be injected into the function when the 4039 * function is invoked. There are three ways in which the function can be annotated with the needed 4040 * dependencies. 4041 * 4042 * # Argument names 4043 * 4044 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4045 * by converting the function into a string using `toString()` method and extracting the argument 4046 * names. 4047 * ```js 4048 * // Given 4049 * function MyController($scope, $route) { 4050 * // ... 4051 * } 4052 * 4053 * // Then 4054 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4055 * ``` 4056 * 4057 * You can disallow this method by using strict injection mode. 4058 * 4059 * This method does not work with code minification / obfuscation. For this reason the following 4060 * annotation strategies are supported. 4061 * 4062 * # The `$inject` property 4063 * 4064 * If a function has an `$inject` property and its value is an array of strings, then the strings 4065 * represent names of services to be injected into the function. 4066 * ```js 4067 * // Given 4068 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 4069 * // ... 4070 * } 4071 * // Define function dependencies 4072 * MyController['$inject'] = ['$scope', '$route']; 4073 * 4074 * // Then 4075 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4076 * ``` 4077 * 4078 * # The array notation 4079 * 4080 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 4081 * is very inconvenient. In these situations using the array notation to specify the dependencies in 4082 * a way that survives minification is a better choice: 4083 * 4084 * ```js 4085 * // We wish to write this (not minification / obfuscation safe) 4086 * injector.invoke(function($compile, $rootScope) { 4087 * // ... 4088 * }); 4089 * 4090 * // We are forced to write break inlining 4091 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 4092 * // ... 4093 * }; 4094 * tmpFn.$inject = ['$compile', '$rootScope']; 4095 * injector.invoke(tmpFn); 4096 * 4097 * // To better support inline function the inline annotation is supported 4098 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 4099 * // ... 4100 * }]); 4101 * 4102 * // Therefore 4103 * expect(injector.annotate( 4104 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 4105 * ).toEqual(['$compile', '$rootScope']); 4106 * ``` 4107 * 4108 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 4109 * be retrieved as described above. 4110 * 4111 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 4112 * 4113 * @returns {Array.<string>} The names of the services which the function requires. 4114 */ 4115 4116 4117 4118 4119/** 4120 * @ngdoc service 4121 * @name $provide 4122 * 4123 * @description 4124 * 4125 * The {@link auto.$provide $provide} service has a number of methods for registering components 4126 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 4127 * {@link angular.Module}. 4128 * 4129 * An Angular **service** is a singleton object created by a **service factory**. These **service 4130 * factories** are functions which, in turn, are created by a **service provider**. 4131 * The **service providers** are constructor functions. When instantiated they must contain a 4132 * property called `$get`, which holds the **service factory** function. 4133 * 4134 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 4135 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 4136 * function to get the instance of the **service**. 4137 * 4138 * Often services have no configuration options and there is no need to add methods to the service 4139 * provider. The provider will be no more than a constructor function with a `$get` property. For 4140 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 4141 * services without specifying a provider. 4142 * 4143 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 4144 * {@link auto.$injector $injector} 4145 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 4146 * providers and services. 4147 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 4148 * services, not providers. 4149 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 4150 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 4151 * given factory function. 4152 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 4153 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 4154 * a new object using the given constructor function. 4155 * 4156 * See the individual methods for more information and examples. 4157 */ 4158 4159/** 4160 * @ngdoc method 4161 * @name $provide#provider 4162 * @description 4163 * 4164 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4165 * are constructor functions, whose instances are responsible for "providing" a factory for a 4166 * service. 4167 * 4168 * Service provider names start with the name of the service they provide followed by `Provider`. 4169 * For example, the {@link ng.$log $log} service has a provider called 4170 * {@link ng.$logProvider $logProvider}. 4171 * 4172 * Service provider objects can have additional methods which allow configuration of the provider 4173 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4174 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4175 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4176 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4177 * console or not. 4178 * 4179 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4180 'Provider'` key. 4181 * @param {(Object|function())} provider If the provider is: 4182 * 4183 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4184 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4185 * - `Constructor`: a new instance of the provider will be created using 4186 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4187 * 4188 * @returns {Object} registered provider instance 4189 4190 * @example 4191 * 4192 * The following example shows how to create a simple event tracking service and register it using 4193 * {@link auto.$provide#provider $provide.provider()}. 4194 * 4195 * ```js 4196 * // Define the eventTracker provider 4197 * function EventTrackerProvider() { 4198 * var trackingUrl = '/track'; 4199 * 4200 * // A provider method for configuring where the tracked events should been saved 4201 * this.setTrackingUrl = function(url) { 4202 * trackingUrl = url; 4203 * }; 4204 * 4205 * // The service factory function 4206 * this.$get = ['$http', function($http) { 4207 * var trackedEvents = {}; 4208 * return { 4209 * // Call this to track an event 4210 * event: function(event) { 4211 * var count = trackedEvents[event] || 0; 4212 * count += 1; 4213 * trackedEvents[event] = count; 4214 * return count; 4215 * }, 4216 * // Call this to save the tracked events to the trackingUrl 4217 * save: function() { 4218 * $http.post(trackingUrl, trackedEvents); 4219 * } 4220 * }; 4221 * }]; 4222 * } 4223 * 4224 * describe('eventTracker', function() { 4225 * var postSpy; 4226 * 4227 * beforeEach(module(function($provide) { 4228 * // Register the eventTracker provider 4229 * $provide.provider('eventTracker', EventTrackerProvider); 4230 * })); 4231 * 4232 * beforeEach(module(function(eventTrackerProvider) { 4233 * // Configure eventTracker provider 4234 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4235 * })); 4236 * 4237 * it('tracks events', inject(function(eventTracker) { 4238 * expect(eventTracker.event('login')).toEqual(1); 4239 * expect(eventTracker.event('login')).toEqual(2); 4240 * })); 4241 * 4242 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4243 * postSpy = spyOn($http, 'post'); 4244 * eventTracker.event('login'); 4245 * eventTracker.save(); 4246 * expect(postSpy).toHaveBeenCalled(); 4247 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4248 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4249 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4250 * })); 4251 * }); 4252 * ``` 4253 */ 4254 4255/** 4256 * @ngdoc method 4257 * @name $provide#factory 4258 * @description 4259 * 4260 * Register a **service factory**, which will be called to return the service instance. 4261 * This is short for registering a service where its provider consists of only a `$get` property, 4262 * which is the given service factory function. 4263 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4264 * configure your service in a provider. 4265 * 4266 * @param {string} name The name of the instance. 4267 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4268 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4269 * @returns {Object} registered provider instance 4270 * 4271 * @example 4272 * Here is an example of registering a service 4273 * ```js 4274 * $provide.factory('ping', ['$http', function($http) { 4275 * return function ping() { 4276 * return $http.send('/ping'); 4277 * }; 4278 * }]); 4279 * ``` 4280 * You would then inject and use this service like this: 4281 * ```js 4282 * someModule.controller('Ctrl', ['ping', function(ping) { 4283 * ping(); 4284 * }]); 4285 * ``` 4286 */ 4287 4288 4289/** 4290 * @ngdoc method 4291 * @name $provide#service 4292 * @description 4293 * 4294 * Register a **service constructor**, which will be invoked with `new` to create the service 4295 * instance. 4296 * This is short for registering a service where its provider's `$get` property is a factory 4297 * function that returns an instance instantiated by the injector from the service constructor 4298 * function. 4299 * 4300 * Internally it looks a bit like this: 4301 * 4302 * ``` 4303 * { 4304 * $get: function() { 4305 * return $injector.instantiate(constructor); 4306 * } 4307 * } 4308 * ``` 4309 * 4310 * 4311 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4312 * as a type/class. 4313 * 4314 * @param {string} name The name of the instance. 4315 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4316 * that will be instantiated. 4317 * @returns {Object} registered provider instance 4318 * 4319 * @example 4320 * Here is an example of registering a service using 4321 * {@link auto.$provide#service $provide.service(class)}. 4322 * ```js 4323 * var Ping = function($http) { 4324 * this.$http = $http; 4325 * }; 4326 * 4327 * Ping.$inject = ['$http']; 4328 * 4329 * Ping.prototype.send = function() { 4330 * return this.$http.get('/ping'); 4331 * }; 4332 * $provide.service('ping', Ping); 4333 * ``` 4334 * You would then inject and use this service like this: 4335 * ```js 4336 * someModule.controller('Ctrl', ['ping', function(ping) { 4337 * ping.send(); 4338 * }]); 4339 * ``` 4340 */ 4341 4342 4343/** 4344 * @ngdoc method 4345 * @name $provide#value 4346 * @description 4347 * 4348 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4349 * number, an array, an object or a function. This is short for registering a service where its 4350 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4351 * service**. That also means it is not possible to inject other services into a value service. 4352 * 4353 * Value services are similar to constant services, except that they cannot be injected into a 4354 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4355 * an Angular {@link auto.$provide#decorator decorator}. 4356 * 4357 * @param {string} name The name of the instance. 4358 * @param {*} value The value. 4359 * @returns {Object} registered provider instance 4360 * 4361 * @example 4362 * Here are some examples of creating value services. 4363 * ```js 4364 * $provide.value('ADMIN_USER', 'admin'); 4365 *
4366 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4367 * 4368 * $provide.value('halfOf', function(value) { 4369 * return value / 2; 4370 * }); 4371 * ``` 4372 */ 4373 4374 4375/** 4376 * @ngdoc method 4377 * @name $provide#constant 4378 * @description 4379 * 4380 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, 4381 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not 4382 * possible to inject other services into a constant. 4383 * 4384 * But unlike {@link auto.$provide#value value}, a constant can be 4385 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4386 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4387 * 4388 * @param {string} name The name of the constant. 4389 * @param {*} value The constant value. 4390 * @returns {Object} registered instance 4391 * 4392 * @example 4393 * Here a some examples of creating constants: 4394 * ```js 4395 * $provide.constant('SHARD_HEIGHT', 306); 4396 * 4397 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4398 * 4399 * $provide.constant('double', function(value) { 4400 * return value * 2; 4401 * }); 4402 * ``` 4403 */ 4404 4405 4406/** 4407 * @ngdoc method 4408 * @name $provide#decorator 4409 * @description 4410 * 4411 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4412 * intercepts the creation of a service, allowing it to override or modify the behavior of the 4413 * service. The object returned by the decorator may be the original service, or a new service 4414 * object which replaces or wraps and delegates to the original service. 4415 * 4416 * @param {string} name The name of the service to decorate. 4417 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4418 * instantiated and should return the decorated service instance. The function is called using 4419 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4420 * Local injection arguments: 4421 * 4422 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4423 * decorated or delegated to. 4424 * 4425 * @example 4426 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4427 * calls to {@link ng.$log#error $log.warn()}. 4428 * ```js 4429 * $provide.decorator('$log', ['$delegate', function($delegate) { 4430 * $delegate.warn = $delegate.error; 4431 * return $delegate; 4432 * }]); 4433 * ``` 4434 */ 4435 4436 4437function createInjector(modulesToLoad, strictDi) { 4438 strictDi = (strictDi === true); 4439 var INSTANTIATING = {}, 4440 providerSuffix = 'Provider', 4441 path = [], 4442 loadedModules = new HashMap([], true), 4443 providerCache = { 4444 $provide: { 4445 provider: supportObject(provider), 4446 factory: supportObject(factory), 4447 service: supportObject(service), 4448 value: supportObject(value), 4449 constant: supportObject(constant), 4450 decorator: decorator 4451 } 4452 }, 4453 providerInjector = (providerCache.$injector = 4454 createInternalInjector(providerCache, function(serviceName, caller) { 4455 if (angular.isString(caller)) { 4456 path.push(caller); 4457 } 4458 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4459 })), 4460 instanceCache = {}, 4461 protoInstanceInjector = 4462 createInternalInjector(instanceCache, function(serviceName, caller) { 4463 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4464 return instanceInjector.invoke( 4465 provider.$get, provider, undefined, serviceName); 4466 }), 4467 instanceInjector = protoInstanceInjector; 4468 4469 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 4470 var runBlocks = loadModules(modulesToLoad); 4471 instanceInjector = protoInstanceInjector.get('$injector'); 4472 instanceInjector.strictDi = strictDi; 4473 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 4474 4475 return instanceInjector; 4476 4477 //////////////////////////////////// 4478 // $provider 4479 //////////////////////////////////// 4480 4481 function supportObject(delegate) { 4482 return function(key, value) { 4483 if (isObject(key)) {
vendor: 10,003 bytes, lines 4484-4762
4484 forEach(key, reverseParams(delegate)); 4485 } else { 4486 return delegate(key, value); 4487 } 4488 }; 4489 } 4490 4491 function provider(name, provider_) { 4492 assertNotHasOwnProperty(name, 'service'); 4493 if (isFunction(provider_) || isArray(provider_)) { 4494 provider_ = providerInjector.instantiate(provider_); 4495 } 4496 if (!provider_.$get) { 4497 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4498 } 4499 return providerCache[name + providerSuffix] = provider_; 4500 } 4501 4502 function enforceReturnValue(name, factory) { 4503 return function enforcedReturnValue() { 4504 var result = instanceInjector.invoke(factory, this); 4505 if (isUndefined(result)) { 4506 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4507 } 4508 return result; 4509 }; 4510 } 4511 4512 function factory(name, factoryFn, enforce) { 4513 return provider(name, { 4514 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4515 }); 4516 } 4517 4518 function service(name, constructor) { 4519 return factory(name, ['$injector', function($injector) { 4520 return $injector.instantiate(constructor); 4521 }]); 4522 } 4523 4524 function value(name, val) { return factory(name, valueFn(val), false); } 4525 4526 function constant(name, value) { 4527 assertNotHasOwnProperty(name, 'constant'); 4528 providerCache[name] = value; 4529 instanceCache[name] = value; 4530 } 4531 4532 function decorator(serviceName, decorFn) { 4533 var origProvider = providerInjector.get(serviceName + providerSuffix), 4534 orig$get = origProvider.$get; 4535 4536 origProvider.$get = function() { 4537 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4538 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4539 }; 4540 } 4541 4542 //////////////////////////////////// 4543 // Module Loading 4544 //////////////////////////////////// 4545 function loadModules(modulesToLoad) { 4546 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 4547 var runBlocks = [], moduleFn; 4548 forEach(modulesToLoad, function(module) { 4549 if (loadedModules.get(module)) return; 4550 loadedModules.put(module, true); 4551 4552 function runInvokeQueue(queue) { 4553 var i, ii; 4554 for (i = 0, ii = queue.length; i < ii; i++) { 4555 var invokeArgs = queue[i], 4556 provider = providerInjector.get(invokeArgs[0]); 4557 4558 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4559 } 4560 } 4561 4562 try { 4563 if (isString(module)) { 4564 moduleFn = angularModule(module); 4565 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4566 runInvokeQueue(moduleFn._invokeQueue); 4567 runInvokeQueue(moduleFn._configBlocks); 4568 } else if (isFunction(module)) { 4569 runBlocks.push(providerInjector.invoke(module)); 4570 } else if (isArray(module)) { 4571 runBlocks.push(providerInjector.invoke(module)); 4572 } else { 4573 assertArgFn(module, 'module'); 4574 } 4575 } catch (e) { 4576 if (isArray(module)) { 4577 module = module[module.length - 1]; 4578 } 4579 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4580 // Safari & FF's stack traces don't contain error.message content 4581 // unlike those of Chrome and IE 4582 // So if stack doesn't contain message, we create a new string that contains both. 4583 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4584 /* jshint -W022 */ 4585 e = e.message + '\n' + e.stack; 4586 } 4587 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4588 module, e.stack || e.message || e); 4589 } 4590 }); 4591 return runBlocks; 4592 } 4593 4594 //////////////////////////////////// 4595 // internal Injector 4596 //////////////////////////////////// 4597 4598 function createInternalInjector(cache, factory) { 4599 4600 function getService(serviceName, caller) { 4601 if (cache.hasOwnProperty(serviceName)) { 4602 if (cache[serviceName] === INSTANTIATING) { 4603 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4604 serviceName + ' <- ' + path.join(' <- ')); 4605 } 4606 return cache[serviceName]; 4607 } else { 4608 try { 4609 path.unshift(serviceName); 4610 cache[serviceName] = INSTANTIATING; 4611 return cache[serviceName] = factory(serviceName, caller); 4612 } catch (err) { 4613 if (cache[serviceName] === INSTANTIATING) { 4614 delete cache[serviceName]; 4615 } 4616 throw err; 4617 } finally { 4618 path.shift(); 4619 } 4620 } 4621 } 4622 4623 4624 function injectionArgs(fn, locals, serviceName) { 4625 var args = [], 4626 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 4627 4628 for (var i = 0, length = $inject.length; i < length; i++) { 4629 var key = $inject[i]; 4630 if (typeof key !== 'string') { 4631 throw $injectorMinErr('itkn', 4632 'Incorrect injection token! Expected service name as string, got {0}', key); 4633 } 4634 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 4635 getService(key, serviceName)); 4636 } 4637 return args; 4638 } 4639 4640 function isClass(func) { 4641 // IE 9-11 do not support classes and IE9 leaks with the code below. 4642 if (msie <= 11) { 4643 return false; 4644 } 4645 // Workaround for MS Edge. 4646 // Check https://connect.microsoft.com/IE/Feedback/Details/2211653 4647 return typeof func === 'function' 4648 && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func)); 4649 } 4650 4651 function invoke(fn, self, locals, serviceName) { 4652 if (typeof locals === 'string') { 4653 serviceName = locals; 4654 locals = null; 4655 } 4656 4657 var args = injectionArgs(fn, locals, serviceName); 4658 if (isArray(fn)) { 4659 fn = fn[fn.length - 1]; 4660 } 4661 4662 if (!isClass(fn)) { 4663 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4664 // #5388 4665 return fn.apply(self, args); 4666 } else { 4667 args.unshift(null); 4668 return new (Function.prototype.bind.apply(fn, args))(); 4669 } 4670 } 4671 4672 4673 function instantiate(Type, locals, serviceName) { 4674 // Check if Type is annotated and use just the given function at n-1 as parameter 4675 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4676 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 4677 var args = injectionArgs(Type, locals, serviceName); 4678 // Empty object at position 0 is ignored for invocation with `new`, but required. 4679 args.unshift(null); 4680 return new (Function.prototype.bind.apply(ctor, args))(); 4681 } 4682 4683 4684 return { 4685 invoke: invoke, 4686 instantiate: instantiate, 4687 get: getService, 4688 annotate: createInjector.$$annotate, 4689 has: function(name) { 4690 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4691 } 4692 }; 4693 } 4694} 4695 4696createInjector.$$annotate = annotate; 4697 4698/** 4699 * @ngdoc provider 4700 * @name $anchorScrollProvider 4701 * 4702 * @description 4703 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4704 * {@link ng.$location#hash $location.hash()} changes. 4705 */ 4706function $AnchorScrollProvider() { 4707 4708 var autoScrollingEnabled = true; 4709 4710 /** 4711 * @ngdoc method 4712 * @name $anchorScrollProvider#disableAutoScrolling 4713 * 4714 * @description 4715 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4716 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4717 * Use this method to disable automatic scrolling. 4718 * 4719 * If automatic scrolling is disabled, one must explicitly call 4720 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4721 * current hash. 4722 */ 4723 this.disableAutoScrolling = function() { 4724 autoScrollingEnabled = false; 4725 }; 4726 4727 /** 4728 * @ngdoc service 4729 * @name $anchorScroll 4730 * @kind function 4731 * @requires $window 4732 * @requires $location 4733 * @requires $rootScope 4734 * 4735 * @description 4736 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4737 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4738 * in the 4739 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document). 4740 * 4741 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4742 * match any anchor whenever it changes. This can be disabled by calling 4743 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4744 * 4745 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4746 * vertical scroll-offset (either fixed or dynamic). 4747 * 4748 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4749 * {@link ng.$location#hash $location.hash()} will be used. 4750 * 4751 * @property {(number|function|jqLite)} yOffset 4752 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4753 * positioned elements at the top of the page, such as navbars, headers etc. 4754 * 4755 * `yOffset` can be specified in various ways: 4756 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4757 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4758 * a number representing the offset (in pixels).<br /><br /> 4759 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4760 * the top of the page to the element's bottom will be used as offset.<br /> 4761 * **Note**: The element will be taken into account only as long as its `position` is set to 4762 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adju
4762st 4763 * their height and/or positioning according to the viewport's size. 4764 * 4765 * <br /> 4766 * <div class="alert alert-warning"> 4767 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4768 * not some child element. 4769 * </div> 4770 * 4771 * @example 4772 <example module="anchorScrollExample"> 4773 <file name="index.html"> 4774 <div id="scrollArea" ng-controller="ScrollController"> 4775 <a ng-click="gotoBottom()">Go to bottom</a> 4776 <a id="bottom"></a> You're at the bottom! 4777 </div> 4778 </file> 4779 <file name="script.js"> 4780 angular.module('anchorScrollExample', []) 4781 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4782 function ($scope, $location, $anchorScroll) { 4783 $scope.gotoBottom = function() { 4784 // set the location.hash to the id of 4785 // the element you wish to scroll to. 4786 $location.hash('bottom'); 4787 4788 // call $anchorScroll() 4789 $anchorScroll(); 4790 }; 4791 }]); 4792 </file> 4793 <file name="style.css"> 4794 #scrollArea { 4795 height: 280px; 4796 overflow: auto; 4797 } 4798 4799 #bottom { 4800 display: block; 4801 margin-top: 2000px; 4802 } 4803 </file> 4804 </example> 4805 * 4806 * <hr /> 4807 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4808 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4809 * 4810 * @example 4811 <example module="anchorScrollOffsetExample"> 4812 <file name="index.html"> 4813 <div class="fixed-header" ng-controller="headerCtrl"> 4814 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4815 Go to anchor {{x}} 4816 </a> 4817 </div> 4818 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4819 Anchor {{x}} of 5 4820 </div> 4821 </file> 4822 <file name="script.js"> 4823 angular.module('anchorScrollOffsetExample', []) 4824 .run(['$anchorScroll', function($anchorScroll) { 4825 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4826 }]) 4827 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4828 function ($anchorScroll, $location, $scope) {
vendor: 10,126 bytes, lines 4829-5154
4829 $scope.gotoAnchor = function(x) { 4830 var newHash = 'anchor' + x; 4831 if ($location.hash() !== newHash) { 4832 // set the $location.hash to `newHash` and 4833 // $anchorScroll will automatically scroll to it 4834 $location.hash('anchor' + x); 4835 } else { 4836 // call $anchorScroll() explicitly, 4837 // since $location.hash hasn't changed 4838 $anchorScroll(); 4839 } 4840 }; 4841 } 4842 ]); 4843 </file> 4844 <file name="style.css"> 4845 body { 4846 padding-top: 50px; 4847 } 4848 4849 .anchor { 4850 border: 2px dashed DarkOrchid; 4851 padding: 10px 10px 200px 10px; 4852 } 4853 4854 .fixed-header { 4855 background-color: rgba(0, 0, 0, 0.2); 4856 height: 50px; 4857 position: fixed; 4858 top: 0; left: 0; right: 0; 4859 } 4860 4861 .fixed-header > a { 4862 display: inline-block; 4863 margin: 5px 15px; 4864 } 4865 </file> 4866 </example> 4867 */ 4868 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4869 var document = $window.document; 4870 4871 // Helper function to get first anchor from a NodeList 4872 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4873 // and working in all supported browsers.) 4874 function getFirstAnchor(list) { 4875 var result = null; 4876 Array.prototype.some.call(list, function(element) { 4877 if (nodeName_(element) === 'a') { 4878 result = element; 4879 return true; 4880 } 4881 }); 4882 return result; 4883 } 4884 4885 function getYOffset() { 4886 4887 var offset = scroll.yOffset; 4888 4889 if (isFunction(offset)) { 4890 offset = offset(); 4891 } else if (isElement(offset)) { 4892 var elem = offset[0]; 4893 var style = $window.getComputedStyle(elem); 4894 if (style.position !== 'fixed') { 4895 offset = 0; 4896 } else { 4897 offset = elem.getBoundingClientRect().bottom; 4898 } 4899 } else if (!isNumber(offset)) { 4900 offset = 0; 4901 } 4902 4903 return offset; 4904 } 4905 4906 function scrollTo(elem) { 4907 if (elem) { 4908 elem.scrollIntoView(); 4909 4910 var offset = getYOffset(); 4911 4912 if (offset) { 4913 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4914 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4915 // top of the viewport. 4916 // 4917 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4918 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4919 // way down the page. 4920 // 4921 // This is often the case for elements near the bottom of the page. 4922 // 4923 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4924 // the top of the element and the offset, which is enough to align the top of `elem` at the 4925 // desired position. 4926 var elemTop = elem.getBoundingClientRect().top; 4927 $window.scrollBy(0, elemTop - offset); 4928 } 4929 } else { 4930 $window.scrollTo(0, 0); 4931 } 4932 } 4933 4934 function scroll(hash) { 4935 hash = isString(hash) ? hash : $location.hash(); 4936 var elm; 4937 4938 // empty hash, scroll to the top of the page 4939 if (!hash) scrollTo(null); 4940 4941 // element with given id 4942 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4943 4944 // first anchor with given name :-D 4945 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4946 4947 // no element and hash == 'top', scroll to the top of the page 4948 else if (hash === 'top') scrollTo(null); 4949 } 4950 4951 // does not scroll when user clicks on anchor link that is currently on 4952 // (no url change, no $location.hash() change), browser native does scroll 4953 if (autoScrollingEnabled) { 4954 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4955 function autoScrollWatchAction(newVal, oldVal) { 4956 // skip the initial scroll if $location.hash is empty 4957 if (newVal === oldVal && newVal === '') return; 4958 4959 jqLiteDocumentLoaded(function() { 4960 $rootScope.$evalAsync(scroll); 4961 }); 4962 }); 4963 } 4964 4965 return scroll; 4966 }]; 4967} 4968 4969var $animateMinErr = minErr('$animate'); 4970var ELEMENT_NODE = 1; 4971var NG_ANIMATE_CLASSNAME = 'ng-animate'; 4972 4973function mergeClasses(a,b) { 4974 if (!a && !b) return ''; 4975 if (!a) return b; 4976 if (!b) return a; 4977 if (isArray(a)) a = a.join(' '); 4978 if (isArray(b)) b = b.join(' '); 4979 return a + ' ' + b; 4980} 4981 4982function extractElementNode(element) { 4983 for (var i = 0; i < element.length; i++) { 4984 var elm = element[i]; 4985 if (elm.nodeType === ELEMENT_NODE) { 4986 return elm; 4987 } 4988 } 4989} 4990 4991function splitClasses(classes) { 4992 if (isString(classes)) { 4993 classes = classes.split(' '); 4994 } 4995 4996 // Use createMap() to prevent class assumptions involving property names in 4997 // Object.prototype 4998 var obj = createMap(); 4999 forEach(classes, function(klass) { 5000 // sometimes the split leaves empty string values 5001 // incase extra spaces were applied to the options 5002 if (klass.length) { 5003 obj[klass] = true; 5004 } 5005 }); 5006 return obj; 5007} 5008 5009// if any other type of options value besides an Object value is 5010// passed into the $animate.method() animation then this helper code 5011// will be run which will ignore it. While this patch is not the 5012// greatest solution to this, a lot of existing plugins depend on 5013// $animate to either call the callback (< 1.2) or return a promise 5014// that can be changed. This helper function ensures that the options 5015// are wiped clean incase a callback function is provided. 5016function prepareAnimateOptions(options) { 5017 return isObject(options) 5018 ? options 5019 : {}; 5020} 5021 5022var $$CoreAnimateJsProvider = function() { 5023 this.$get = noop; 5024}; 5025 5026// this is prefixed with Core since it conflicts with 5027// the animateQueueProvider defined in ngAnimate/animateQueue.js 5028var $$CoreAnimateQueueProvider = function() { 5029 var postDigestQueue = new HashMap(); 5030 var postDigestElements = []; 5031 5032 this.$get = ['$$AnimateRunner', '$rootScope', 5033 function($$AnimateRunner, $rootScope) { 5034 return { 5035 enabled: noop, 5036 on: noop, 5037 off: noop, 5038 pin: noop, 5039 5040 push: function(element, event, options, domOperation) { 5041 domOperation && domOperation(); 5042 5043 options = options || {}; 5044 options.from && element.css(options.from); 5045 options.to && element.css(options.to); 5046 5047 if (options.addClass || options.removeClass) { 5048 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 5049 } 5050 5051 var runner = new $$AnimateRunner(); // jshint ignore:line 5052 5053 // since there are no animations to run the runner needs to be 5054 // notified that the animation call is complete. 5055 runner.complete(); 5056 return runner; 5057 } 5058 }; 5059 5060 5061 function updateData(data, classes, value) { 5062 var changed = false; 5063 if (classes) { 5064 classes = isString(classes) ? classes.split(' ') : 5065 isArray(classes) ? classes : []; 5066 forEach(classes, function(className) { 5067 if (className) { 5068 changed = true; 5069 data[className] = value; 5070 } 5071 }); 5072 } 5073 return changed; 5074 } 5075 5076 function handleCSSClassChanges() { 5077 forEach(postDigestElements, function(element) { 5078 var data = postDigestQueue.get(element); 5079 if (data) { 5080 var existing = splitClasses(element.attr('class')); 5081 var toAdd = ''; 5082 var toRemove = ''; 5083 forEach(data, function(status, className) { 5084 var hasClass = !!existing[className]; 5085 if (status !== hasClass) { 5086 if (status) { 5087 toAdd += (toAdd.length ? ' ' : '') + className; 5088 } else { 5089 toRemove += (toRemove.length ? ' ' : '') + className; 5090 } 5091 } 5092 }); 5093 5094 forEach(element, function(elm) { 5095 toAdd && jqLiteAddClass(elm, toAdd); 5096 toRemove && jqLiteRemoveClass(elm, toRemove); 5097 }); 5098 postDigestQueue.remove(element); 5099 } 5100 }); 5101 postDigestElements.length = 0; 5102 } 5103 5104 5105 function addRemoveClassesPostDigest(element, add, remove) { 5106 var data = postDigestQueue.get(element) || {}; 5107 5108 var classesAdded = updateData(data, add, true); 5109 var classesRemoved = updateData(data, remove, false); 5110 5111 if (classesAdded || classesRemoved) { 5112 5113 postDigestQueue.put(element, data); 5114 postDigestElements.push(element); 5115 5116 if (postDigestElements.length === 1) { 5117 $rootScope.$$postDigest(handleCSSClassChanges); 5118 } 5119 } 5120 } 5121 }]; 5122}; 5123 5124/** 5125 * @ngdoc provider 5126 * @name $animateProvider 5127 * 5128 * @description 5129 * Default implementation of $animate that doesn't perform any animations, instead just 5130 * synchronously performs DOM updates and resolves the returned runner promise. 5131 * 5132 * In order to enable animations the `ngAnimate` module has to be loaded. 5133 * 5134 * To see the functional implementation check out `src/ngAnimate/animate.js`. 5135 */ 5136var $AnimateProvider = ['$provide', function($provide) { 5137 var provider = this; 5138 5139 this.$$registeredAnimations = Object.create(null); 5140 5141 /** 5142 * @ngdoc method 5143 * @name $animateProvider#register 5144 * 5145 * @description 5146 * Registers a new injectable animation factory function. The factory function produces the 5147 * animation object which contains callback functions for each event that is expected to be 5148 * animated. 5149 * 5150 * * `eventFn`: `function(element, ... , doneFunction, options)` 5151 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 5152 * on the type of animation additional arguments will be injected into the animation function. The 5153 * list below explains the function signatures for the different animation methods: 5154 *
5155 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 5156 * - addClass: function(element, addedClasses, doneFunction, options) 5157 * - removeClass: function(element, removedClasses, doneFunction, options) 5158 * - enter, leave, move: function(element, doneFunction, options) 5159 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 5160 * 5161 * Make sure to trigger the `doneFunction` once the animation is fully complete. 5162 * 5163 * ```js 5164 * return { 5165 * //enter, leave, move signature 5166 * eventFn : function(element, done, options) { 5167 * //code to run the animation 5168 * //once complete, then run done() 5169 * return function endFunction(wasCancelled) { 5170 * //code to cancel the animation 5171 * } 5172 * } 5173 * } 5174 * ``` 5175 * 5176 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 5177 * @param {Function} factory The factory function that will be executed to return the animation 5178 * object. 5179 */ 5180 this.register = function(name, factory) { 5181 if (name && name.charAt(0) !== '.') { 5182 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 5183 } 5184 5185 var key = name + '-animation'; 5186 provider.$$registeredAnimations[name.substr(1)] = key; 5187 $provide.factory(key, factory); 5188 }; 5189 5190 /** 5191 * @ngdoc method 5192 * @name $animateProvider#classNameFilter 5193 * 5194 * @description 5195 * Sets and/or returns the CSS class regular expression that is checked when performing 5196 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5197 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5198 * When setting the `classNameFilter` value, animations will only be performed on elements 5199 * that successfully match the filter expression. This in turn can boost performance 5200 * for low-powered devices as well as applications containing a lot of structural operations. 5201 * @param {RegExp=} expression The className expression which will be checked against all animations 5202 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5203 */ 5204 this.classNameFilter = function(expression) { 5205 if (arguments.length === 1) { 5206 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 5207 if (this.$$classNameFilter) { 5208 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 5209 if (reservedRegex.test(this.$$classNameFilter.toString())) { 5210 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5211 5212 } 5213 } 5214 } 5215 return this.$$classNameFilter; 5216 }; 5217 5218 this.$get = ['$$animateQueue', function($$animateQueue) { 5219 function domInsert(element, parentElement, afterElement) { 5220 // if for some reason the previous element was removed 5221 // from the dom sometime before this code runs then let's 5222 // just stick to using the parent element as the anchor 5223 if (afterElement) { 5224 var afterNode = extractElementNode(afterElement); 5225 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5226 afterElement = null; 5227 } 5228 } 5229 afterElement ? afterElement.after(element) : parentElement.prepend(element); 5230 } 5231 5232 /** 5233 * @ngdoc service 5234 * @name $animate 5235 * @description The $animate service exposes a series of DOM utility methods that provide support 5236 * for animation hooks. The default behavior is the application of DOM operations, however, 5237 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5238 * to ensure that animation runs with the triggered DOM operation. 5239 * 5240 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 5241 * included and only when it is active then the animation hooks that `$animate` triggers will be 5242 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5243 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5244 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5245 * 5246 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5247 * 5248 * To learn more about enabling animation support, click here to visit the 5249 * {@link ngAnimate ngAnimate module page}. 5250 */ 5251 return { 5252 // we don't call it directly since non-existant arguments may 5253 // be interpreted as null within the sub enabled function 5254 5255 /** 5256 * 5257 * @ngdoc method 5258 * @name $animate#on 5259 * @kind function
5260 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5261 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5262 * is fired with the following params: 5263 * 5264 * ```js 5265 * $animate.on('enter', container, 5266 * function callback(element, phase) { 5267 * // cool we detected an enter animation within the container 5268 * } 5269 * ); 5270 * ``` 5271 * 5272 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5273 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5274 * as well as among its children 5275 * @param {Function} callback the callback function that will be fired when the listener is triggered 5276 * 5277 * The arguments present in the callback function are: 5278 * * `element` - The captured DOM element that the animation was fired on. 5279 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5280 */ 5281 on: $$animateQueue.on, 5282 5283 /** 5284 * 5285 * @ngdoc method 5286 * @name $animate#off 5287 * @kind function 5288 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5289 * can be used in three different ways depending on the arguments: 5290 * 5291 * ```js 5292 * // remove all the animation event listeners listening for `enter` 5293 * $animate.off('enter'); 5294 * 5295 * // remove listeners for all animation events from the container element 5296 * $animate.off(container); 5297 * 5298 * // remove all the animation event listeners listening for `enter` on the given element and its children 5299 * $animate.off('enter', container); 5300 * 5301 * // remove the event listener function provided by `callback` that is set 5302 * // to listen for `enter` on the given `container` as well as its children 5303 * $animate.off('enter', container, callback); 5304 * ``` 5305 * 5306 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, 5307 * addClass, removeClass, etc...), or the container element. If it is the element, all other 5308 * arguments are ignored. 5309 * @param {DOMElement=} container the container element the event listener was placed on 5310 * @param {Function=} callback the callback function that was registered as the listener 5311 */ 5312 off: $$animateQueue.off, 5313 5314 /** 5315 * @ngdoc method 5316 * @name $animate#pin 5317 * @kind function 5318 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5319 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5320 * element despite being outside the realm of the application or within another application. Say for example if the application 5321 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5322 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5323 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5324 * 5325 * Note that this feature is only active when the `ngAnimate` module is used. 5326 * 5327 * @param {DOMElement} element the external element that will be pinned 5328 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5329 */ 5330 pin: $$animateQueue.pin, 5331 5332 /** 5333 * 5334 * @ngdoc method 5335 * @name $animate#enabled 5336 * @kind function 5337 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5338 * function can be called in four ways: 5339 * 5340 * ```js 5341 * // returns true or false 5342 * $animate.enabled(); 5343 * 5344 * // changes the enabled state for all animations 5345 * $animate.enabled(false); 5346 * $animate.enabled(true); 5347 * 5348 * // returns true or false if animations are enabled for an element 5349 * $animate.enabled(element); 5350 * 5351 * // changes the enabled state for an element and its children 5352 * $animate.enabled(element, true); 5353 * $animate.enabled(element, false); 5354 * ``` 5355 * 5356 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5357 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5358 * 5359 * @return {boolean} whether or not animations are enabled 5360 */ 5361 enabled: $$animateQueue.enabled, 5362 5363 /** 5364 * @ngdoc method 5365 * @name $animate#cancel 5366 * @kind function
5367 * @description Cancels the provided animation. 5368 * 5369 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5370 */ 5371 cancel: function(runner) { 5372 runner.end && runner.end(); 5373 }, 5374 5375 /** 5376 * 5377 * @ngdoc method 5378 * @name $animate#enter 5379 * @kind function 5380 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5381 * as the first child within the `parent` element and then triggers an animation. 5382 * A promise is returned that will be resolved during the next digest once the animation 5383 * has completed. 5384 * 5385 * @param {DOMElement} element the element which will be inserted into the DOM 5386 * @param {DOMElement} parent the parent element which will append the element as 5387 * a child (so long as the after element is not present) 5388 * @param {DOMElement=} after the sibling element after which the element will be appended 5389 * @param {object=} options an optional collection of options/styles that will be applied to the element 5390 * 5391 * @return {Promise} the animation callback promise 5392 */ 5393 enter: function(element, parent, after, options) { 5394 parent = parent && jqLite(parent); 5395 after = after && jqLite(after); 5396 parent = parent || after.parent(); 5397 domInsert(element, parent, after); 5398 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5399 }, 5400 5401 /** 5402 * 5403 * @ngdoc method 5404 * @name $animate#move 5405 * @kind function 5406 * @description Inserts (moves) the element into its new position in the DOM either after 5407 * the `after` element (if provided) or as the first child within the `parent` element 5408 * and then triggers an animation. A promise is returned that will be resolved 5409 * during the next digest once the animation has completed. 5410 * 5411 * @param {DOMElement} element the element which will be moved into the new DOM position 5412 * @param {DOMElement} parent the parent element which will append the element as 5413 * a child (so long as the after element is not present) 5414 * @param {DOMElement=} after the sibling element after which the element will be appended 5415 * @param {object=} options an optional collection of options/styles that will be applied to the element 5416 * 5417 * @return {Promise} the animation callback promise 5418 */ 5419 move: function(element, parent, after, options) { 5420 parent = parent && jqLite(parent); 5421 after = after && jqLite(after); 5422 parent = parent || after.parent(); 5423 domInsert(element, parent, after); 5424 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5425 }, 5426 5427 /** 5428 * @ngdoc method 5429 * @name $animate#leave 5430 * @kind function 5431 * @description Triggers an animation and then removes the element from the DOM. 5432 * When the function is called a promise is returned that will be resolved during the next 5433 * digest once the animation has completed. 5434 * 5435 * @param {DOMElement} element the element which will be removed from the DOM 5436 * @param {object=} options an optional collection of options/styles that will be applied to the element 5437 * 5438 * @return {Promise} the animation callback promise 5439 */ 5440 leave: function(element, options) { 5441 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5442 element.remove(); 5443 }); 5444 }, 5445 5446 /** 5447 * @ngdoc method 5448 * @name $animate#addClass 5449 * @kind function 5450 * 5451 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5452 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5453 * animation if element already contains the CSS class or if the class is removed at a later step. 5454 * Note that class-based animations are treated differently compared to structural animations 5455 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5456 * depending if CSS or JavaScript animations are used. 5457 * 5458 * @param {DOMElement} element the element which the CSS classes will be applied to 5459 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5460 * @param {object=} options an optional collection of options/styles that will be applied to the element 5461 * 5462 * @return {Promise} the animation callback promise 5463 */ 5464 addClass: function(element, className, options) { 5465 options = prepareAnimateOptions(options); 5466 options.addClass = mergeClasses(options.addclass, className); 5467 return $$animateQueue.push(element, 'addClass', options); 5468 }, 5469 5470 /** 5471 * @ngdoc method 5472 * @name $animate#removeClass 5473 * @kind function 5474 *
5475 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5476 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5477 * animation if element does not contain the CSS class or if the class is added at a later step. 5478 * Note that class-based animations are treated differently compared to structural animations 5479 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5480 * depending if CSS or JavaScript animations are used. 5481 * 5482 * @param {DOMElement} element the element which the CSS classes will be applied to 5483 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5484 * @param {object=} options an optional collection of options/styles that will be applied to the element 5485 * 5486 * @return {Promise} the animation callback promise 5487 */ 5488 removeClass: function(element, className, options) { 5489 options = prepareAnimateOptions(options); 5490 options.removeClass = mergeClasses(options.removeClass, className); 5491 return $$animateQueue.push(element, 'removeClass', options); 5492 }, 5493 5494 /** 5495 * @ngdoc method 5496 * @name $animate#setClass 5497 * @kind function 5498 * 5499 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5500 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5501 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5502 * passed. Note that class-based animations are treated differently compared to structural animations 5503 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5504 * depending if CSS or JavaScript animations are used. 5505 * 5506 * @param {DOMElement} element the element which the CSS classes will be applied to 5507 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5508 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5509 * @param {object=} options an optional collection of options/styles that will be applied to the element 5510 * 5511 * @return {Promise} the animation callback promise 5512 */ 5513 setClass: function(element, add, remove, options) { 5514 options = prepareAnimateOptions(options); 5515 options.addClass = mergeClasses(options.addClass, add); 5516 options.removeClass = mergeClasses(options.removeClass, remove); 5517 return $$animateQueue.push(element, 'setClass', options); 5518 }, 5519 5520 /** 5521 * @ngdoc method 5522 * @name $animate#animate 5523 * @kind function 5524 * 5525 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5526 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 5527 * on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and 5528 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 5529 * style in `to`, the style in `from` is applied immediately, and no animation is run. 5530 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 5531 * method (or as part of the `options` parameter): 5532 * 5533 * ```js 5534 * ngModule.animation('.my-inline-animation', function() { 5535 * return { 5536 * animate : function(element, from, to, done, options) { 5537 * //animation 5538 * done(); 5539 * } 5540 * } 5541 * }); 5542 * ``` 5543 * 5544 * @param {DOMElement} element the element which the CSS styles will be applied to 5545 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5546 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5547 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5548 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5549 * (Note that if no animation is detected then this value will not be applied to the element.) 5550 * @param {object=} options an optional collection of options/styles that will be applied to the element 5551 * 5552 * @return {Promise} the animation callback promise 5553 */ 5554 animate: function(element, from, to, className, options) { 5555 options = prepareAnimateOptions(options); 5556 options.from = options.from ? extend(options.from, from) : from; 5557 options.to = options.to ? extend(options.to, to) : to; 5558 5559 className = className || 'ng-inline-animate';
vendor: 4,147 bytes, lines 5560-5737
5560 options.tempClasses = mergeClasses(options.tempClasses, className); 5561 return $$animateQueue.push(element, 'animate', options); 5562 } 5563 }; 5564 }]; 5565}]; 5566 5567var $$AnimateAsyncRunFactoryProvider = function() { 5568 this.$get = ['$$rAF', function($$rAF) { 5569 var waitQueue = []; 5570 5571 function waitForTick(fn) { 5572 waitQueue.push(fn); 5573 if (waitQueue.length > 1) return; 5574 $$rAF(function() { 5575 for (var i = 0; i < waitQueue.length; i++) { 5576 waitQueue[i](); 5577 } 5578 waitQueue = []; 5579 }); 5580 } 5581 5582 return function() { 5583 var passed = false; 5584 waitForTick(function() { 5585 passed = true; 5586 }); 5587 return function(callback) { 5588 passed ? callback() : waitForTick(callback); 5589 }; 5590 }; 5591 }]; 5592}; 5593 5594var $$AnimateRunnerFactoryProvider = function() { 5595 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', 5596 function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { 5597 5598 var INITIAL_STATE = 0; 5599 var DONE_PENDING_STATE = 1; 5600 var DONE_COMPLETE_STATE = 2; 5601 5602 AnimateRunner.chain = function(chain, callback) { 5603 var index = 0; 5604 5605 next(); 5606 function next() { 5607 if (index === chain.length) { 5608 callback(true); 5609 return; 5610 } 5611 5612 chain[index](function(response) { 5613 if (response === false) { 5614 callback(false); 5615 return; 5616 } 5617 index++; 5618 next(); 5619 }); 5620 } 5621 }; 5622 5623 AnimateRunner.all = function(runners, callback) { 5624 var count = 0; 5625 var status = true; 5626 forEach(runners, function(runner) { 5627 runner.done(onProgress); 5628 }); 5629 5630 function onProgress(response) { 5631 status = status && response; 5632 if (++count === runners.length) { 5633 callback(status); 5634 } 5635 } 5636 }; 5637 5638 function AnimateRunner(host) { 5639 this.setHost(host); 5640 5641 var rafTick = $$animateAsyncRun(); 5642 var timeoutTick = function(fn) { 5643 $timeout(fn, 0, false); 5644 }; 5645 5646 this._doneCallbacks = []; 5647 this._tick = function(fn) { 5648 var doc = $document[0]; 5649 5650 // the document may not be ready or attached 5651 // to the module for some internal tests 5652 if (doc && doc.hidden) { 5653 timeoutTick(fn); 5654 } else { 5655 rafTick(fn); 5656 } 5657 }; 5658 this._state = 0; 5659 } 5660 5661 AnimateRunner.prototype = { 5662 setHost: function(host) { 5663 this.host = host || {}; 5664 }, 5665 5666 done: function(fn) { 5667 if (this._state === DONE_COMPLETE_STATE) { 5668 fn(); 5669 } else { 5670 this._doneCallbacks.push(fn); 5671 } 5672 }, 5673 5674 progress: noop, 5675 5676 getPromise: function() { 5677 if (!this.promise) { 5678 var self = this; 5679 this.promise = $q(function(resolve, reject) { 5680 self.done(function(status) { 5681 status === false ? reject() : resolve(); 5682 }); 5683 }); 5684 } 5685 return this.promise; 5686 }, 5687 5688 then: function(resolveHandler, rejectHandler) { 5689 return this.getPromise().then(resolveHandler, rejectHandler); 5690 }, 5691 5692 'catch': function(handler) { 5693 return this.getPromise()['catch'](handler); 5694 }, 5695 5696 'finally': function(handler) { 5697 return this.getPromise()['finally'](handler); 5698 }, 5699 5700 pause: function() { 5701 if (this.host.pause) { 5702 this.host.pause(); 5703 } 5704 }, 5705 5706 resume: function() { 5707 if (this.host.resume) { 5708 this.host.resume(); 5709 } 5710 }, 5711 5712 end: function() { 5713 if (this.host.end) { 5714 this.host.end(); 5715 } 5716 this._resolve(true); 5717 }, 5718 5719 cancel: function() { 5720 if (this.host.cancel) { 5721 this.host.cancel(); 5722 } 5723 this._resolve(false); 5724 }, 5725 5726 complete: function(response) { 5727 var self = this; 5728 if (self._state === INITIAL_STATE) { 5729 self._state = DONE_PENDING_STATE; 5730 self._tick(function() { 5731 self._resolve(response); 5732 }); 5733 } 5734 }, 5735 5736 _resolve: function(response) { 5737 if (this._state !== DONE_COMPLETE_STATE) {
5738 forEach(this._doneCallbacks, function(fn) { 5739 fn(response); 5740 }); 5741 this._doneCallbacks.length = 0; 5742 this._state = DONE_COMPLETE_STATE; 5743 } 5744 } 5745 }; 5746 5747 return AnimateRunner; 5748 }]; 5749}; 5750 5751/** 5752 * @ngdoc service 5753 * @name $animateCss 5754 * @kind object 5755 * 5756 * @description 5757 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 5758 * then the `$animateCss` service will actually perform animations. 5759 * 5760 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 5761 */ 5762var $CoreAnimateCssProvider = function() { 5763 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 5764 5765 return function(element, initialOptions) { 5766 // all of the animation functions should create 5767 // a copy of the options data, however, if a 5768 // parent service has already created a copy then 5769 // we should stick to using that 5770 var options = initialOptions || {}; 5771 if (!options.$$prepared) { 5772 options = copy(options); 5773 } 5774 5775 // there is no point in applying the styles since 5776 // there is no animation that goes on at all in 5777 // this version of $animateCss. 5778 if (options.cleanupStyles) { 5779 options.from = options.to = null; 5780 } 5781 5782 if (options.from) { 5783 element.css(options.from); 5784 options.from = null; 5785 } 5786 5787 /* jshint newcap: false */ 5788 var closed, runner = new $$AnimateRunner(); 5789 return { 5790 start: run, 5791 end: run 5792 }; 5793 5794 function run() { 5795 $$rAF(function() { 5796 applyAnimationContents(); 5797 if (!closed) { 5798 runner.complete(); 5799 } 5800 closed = true; 5801 }); 5802 return runner; 5803 } 5804 5805 function applyAnimationContents() { 5806 if (options.addClass) { 5807 element.addClass(options.addClass); 5808 options.addClass = null; 5809 } 5810 if (options.removeClass) { 5811 element.removeClass(options.removeClass); 5812 options.removeClass = null; 5813 } 5814 if (options.to) { 5815 element.css(options.to); 5816 options.to = null; 5817 } 5818 } 5819 }; 5820 }]; 5821}; 5822 5823/* global stripHash: true */ 5824 5825/** 5826 * ! This is a private undocumented service ! 5827 * 5828 * @name $browser 5829 * @requires $log 5830 * @description 5831 * This object has two goals: 5832 * 5833 * - hide all the global state in the browser caused by the window object 5834 * - abstract away all the browser specific features and inconsistencies 5835 * 5836 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 5837 * service, which can be used for convenient testing of the application without the interaction with 5838 * the real browser apis. 5839 */ 5840/** 5841 * @param {object} window The global window object. 5842 * @param {object} document jQuery wrapped document. 5843 * @param {object} $log window.console or an object with the same interface. 5844 * @param {object} $sniffer $sniffer service 5845 */ 5846function Browser(window, document, $log, $sniffer) { 5847 var self = this, 5848 location = window.location, 5849 history = window.history, 5850 setTimeout = window.setTimeout, 5851 clearTimeout = window.clearTimeout, 5852 pendingDeferIds = {}; 5853 5854 self.isMock = false; 5855 5856 var outstandingRequestCount = 0; 5857 var outstandingRequestCallbacks = []; 5858 5859 // TODO(vojta): remove this temporary api 5860 self.$$completeOutstandingRequest = completeOutstandingRequest; 5861 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5862 5863 /** 5864 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5865 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5866 */ 5867 function completeOutstandingRequest(fn) { 5868 try { 5869 fn.apply(null, sliceArgs(arguments, 1)); 5870 } finally { 5871 outstandingRequestCount--; 5872 if (outstandingRequestCount === 0) { 5873 while (outstandingRequestCallbacks.length) { 5874 try { 5875 outstandingRequestCallbacks.pop()(); 5876 } catch (e) { 5877 $log.error(e); 5878 } 5879 } 5880 } 5881 } 5882 } 5883 5884 function getHash(url) { 5885 var index = url.indexOf('#'); 5886 return index === -1 ? '' : url.substr(index); 5887 } 5888 5889 /** 5890 * @private 5891 * Note: this method is used only by scenario runner 5892 * TODO(vojta): prefix this method with $$ ? 5893 * @param {function()} callback Function that will be called when no outstanding request 5894 */ 5895 self.notifyWhenNoOutstandingRequests = function(callback) { 5896 if (outstandingRequestCount === 0) { 5897 callback(); 5898 } else { 5899 outstandingRequestCallbacks.push(callback); 5900 } 5901 }; 5902 5903 ////////////////////////////////////////////////////////////// 5904 // URL API 5905 ////////////////////////////////////////////////////////////// 5906 5907 var cachedState, lastHistoryState, 5908 lastBrowserUrl = location.href, 5909 baseElement = document.find('base'), 5910 pendingLocation = null, 5911 getCurrentState = !$sniffer.history ? noop : function getCurrentState() { 5912 try { 5913 return history.state; 5914 } catch (e) { 5915 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5916 } 5917 }; 5918 5919 cacheState(); 5920 lastHistoryState = cachedState; 5921 5922 /** 5923 * @name $browser#url 5924 * 5925 * @description 5926 * GETTER: 5927 * Without any argument, this method just returns current value of location.href. 5928 * 5929 * SETTER: 5930 * With at least one argument, this method sets url to new value. 5931 * If html5 history api supported, pushState/replaceState is used, otherwise 5932 * location.href/location.replace is used. 5933 * Returns its own instance to allow chaining 5934 * 5935 * NOTE: this api is intended for use only by the $location service. Please use the 5936 * {@link ng.$location $location service} to change url. 5937 * 5938 * @param {string} url New url (when used as setter) 5939 * @param {boolean=} replace Should new url replace current history record? 5940 * @param {object=} state object to use with pushState/replaceState 5941 */ 5942 self.url = function(url, replace, state) { 5943 // In modern browsers `history.state` is `null` by default; treating it separately 5944 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5945 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5946 if (isUndefined(state)) { 5947 state = null; 5948 } 5949 5950 // Android Browser BFCache causes location, history reference to become stale. 5951 if (location !== window.location) location = window.location; 5952 if (history !== window.history) history = window.history; 5953 5954 // setter 5955 if (url) { 5956 var sameState = lastHistoryState === state; 5957 5958 // Don't change anything if previous and current URLs and states match. This also prevents 5959 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5960 // See https://github.com/angular/angular.js/commit/ffb2701 5961 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5962 return self; 5963 } 5964 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5965 lastBrowserUrl = url; 5966 lastHistoryState = state; 5967 // Don't use history API if only the hash changed 5968 // due to a bug in IE10/IE11 which leads 5969 // to not firing a `hashchange` nor `popstate` event 5970 // in some cases (see #9143). 5971 if ($sniffer.history && (!sameBase || !sameState)) { 5972 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5973 cacheState(); 5974 // Do the assignment again so that those two variables are referentially identical. 5975 lastHistoryState = cachedState; 5976 } else { 5977 if (!sameBase || pendingLocation) { 5978 pendingLocation = url; 5979 } 5980 if (replace) { 5981 location.replace(url); 5982 } else if (!sameBase) { 5983 location.href = url; 5984 } else { 5985 location.hash = getHash(url); 5986 } 5987 if (location.href !== url) { 5988 pendingLocation = url; 5989 } 5990 } 5991 return self; 5992 // getter 5993 } else { 5994 // - pendingLocation is needed as browsers don't allow to read out 5995 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 5996 // https://openradar.appspot.com/22186109). 5997 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5998 return pendingLocation || location.href.replace(/%27/g,"'"); 5999 } 6000 }; 6001 6002 /** 6003 * @name $browser#state 6004 * 6005 * @description 6006 * This method is a getter. 6007 * 6008 * Return history.state or null if history.state is undefined. 6009 * 6010 * @returns {object} state 6011 */ 6012 self.state = function() { 6013 return cachedState; 6014 }; 6015 6016 var urlChangeListeners = [], 6017 urlChangeInit = false;
vendor: 8,547 bytes, lines 6018-6283
6018 6019 function cacheStateAndFireUrlChange() { 6020 pendingLocation = null; 6021 cacheState(); 6022 fireUrlChange(); 6023 } 6024 6025 // This variable should be used *only* inside the cacheState function. 6026 var lastCachedState = null; 6027 function cacheState() { 6028 // This should be the only place in $browser where `history.state` is read. 6029 cachedState = getCurrentState(); 6030 cachedState = isUndefined(cachedState) ? null : cachedState; 6031 6032 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 6033 if (equals(cachedState, lastCachedState)) { 6034 cachedState = lastCachedState; 6035 } 6036 lastCachedState = cachedState; 6037 } 6038 6039 function fireUrlChange() { 6040 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 6041 return; 6042 } 6043 6044 lastBrowserUrl = self.url(); 6045 lastHistoryState = cachedState; 6046 forEach(urlChangeListeners, function(listener) { 6047 listener(self.url(), cachedState); 6048 }); 6049 } 6050 6051 /** 6052 * @name $browser#onUrlChange 6053 * 6054 * @description 6055 * Register callback function that will be called, when url changes. 6056 * 6057 * It's only called when the url is changed from outside of angular: 6058 * - user types different url into address bar 6059 * - user clicks on history (forward/back) button 6060 * - user clicks on a link 6061 * 6062 * It's not called when url is changed by $browser.url() method 6063 * 6064 * The listener gets called with new url as parameter. 6065 * 6066 * NOTE: this api is intended for use only by the $location service. Please use the 6067 * {@link ng.$location $location service} to monitor url changes in angular apps. 6068 * 6069 * @param {function(string)} listener Listener function to be called when url changes. 6070 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 6071 */ 6072 self.onUrlChange = function(callback) { 6073 // TODO(vojta): refactor to use node's syntax for events 6074 if (!urlChangeInit) { 6075 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 6076 // don't fire popstate when user change the address bar and don't fire hashchange when url 6077 // changed by push/replaceState 6078 6079 // html5 history api - popstate event 6080 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 6081 // hashchange event 6082 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 6083 6084 urlChangeInit = true; 6085 } 6086 6087 urlChangeListeners.push(callback); 6088 return callback; 6089 }; 6090 6091 /** 6092 * @private 6093 * Remove popstate and hashchange handler from window. 6094 * 6095 * NOTE: this api is intended for use only by $rootScope. 6096 */ 6097 self.$$applicationDestroyed = function() { 6098 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 6099 }; 6100 6101 /** 6102 * Checks whether the url has changed outside of Angular. 6103 * Needs to be exported to be able to check for changes that have been done in sync, 6104 * as hashchange/popstate events fire in async. 6105 */ 6106 self.$$checkUrlChange = fireUrlChange; 6107 6108 ////////////////////////////////////////////////////////////// 6109 // Misc API 6110 ////////////////////////////////////////////////////////////// 6111 6112 /** 6113 * @name $browser#baseHref 6114 * 6115 * @description 6116 * Returns current <base href> 6117 * (always relative - without domain) 6118 * 6119 * @returns {string} The current base href 6120 */ 6121 self.baseHref = function() { 6122 var href = baseElement.attr('href'); 6123 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 6124 }; 6125 6126 /** 6127 * @name $browser#defer 6128 * @param {function()} fn A function, who's execution should be deferred. 6129 * @param {number=} [delay=0] of milliseconds to defer the function execution. 6130 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 6131 * 6132 * @description 6133 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 6134 * 6135 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 6136 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 6137 * via `$browser.defer.flush()`. 6138 * 6139 */ 6140 self.defer = function(fn, delay) { 6141 var timeoutId; 6142 outstandingRequestCount++; 6143 timeoutId = setTimeout(function() { 6144 delete pendingDeferIds[timeoutId]; 6145 completeOutstandingRequest(fn); 6146 }, delay || 0); 6147 pendingDeferIds[timeoutId] = true; 6148 return timeoutId; 6149 }; 6150 6151 6152 /** 6153 * @name $browser#defer.cancel 6154 * 6155 * @description 6156 * Cancels a deferred task identified with `deferId`. 6157 * 6158 * @param {*} deferId Token returned by the `$browser.defer` function. 6159 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 6160 * canceled. 6161 */ 6162 self.defer.cancel = function(deferId) { 6163 if (pendingDeferIds[deferId]) { 6164 delete pendingDeferIds[deferId]; 6165 clearTimeout(deferId); 6166 completeOutstandingRequest(noop); 6167 return true; 6168 } 6169 return false; 6170 }; 6171 6172} 6173 6174function $BrowserProvider() { 6175 this.$get = ['$window', '$log', '$sniffer', '$document', 6176 function($window, $log, $sniffer, $document) { 6177 return new Browser($window, $document, $log, $sniffer); 6178 }]; 6179} 6180 6181/** 6182 * @ngdoc service 6183 * @name $cacheFactory 6184 * 6185 * @description 6186 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 6187 * them. 6188 * 6189 * ```js 6190 * 6191 * var cache = $cacheFactory('cacheId'); 6192 * expect($cacheFactory.get('cacheId')).toBe(cache); 6193 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 6194 * 6195 * cache.put("key", "value"); 6196 * cache.put("another key", "another value"); 6197 * 6198 * // We've specified no options on creation 6199 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 6200 * 6201 * ``` 6202 * 6203 * 6204 * @param {string} cacheId Name or id of the newly created cache. 6205 * @param {object=} options Options object that specifies the cache behavior. Properties: 6206 * 6207 * - `{number=}` `capacity` — turns the cache into LRU cache. 6208 * 6209 * @returns {object} Newly created cache object with the following set of methods: 6210 * 6211 * - `{object}` `info()` — Returns id, size, and options of cache. 6212 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns 6213 * it. 6214 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. 6215 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. 6216 * - `{void}` `removeAll()` — Removes all cached values. 6217 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. 6218 * 6219 * @example 6220 <example module="cacheExampleApp"> 6221 <file name="index.html"> 6222 <div ng-controller="CacheController"> 6223 <input ng-model="newCacheKey" placeholder="Key"> 6224 <input ng-model="newCacheValue" placeholder="Value"> 6225 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 6226 6227 <p ng-if="keys.length">Cached Values</p> 6228 <div ng-repeat="key in keys"> 6229 <span ng-bind="key"></span> 6230 <span>: </span> 6231 <b ng-bind="cache.get(key)"></b> 6232 </div> 6233 6234 <p>Cache Info</p> 6235 <div ng-repeat="(key, value) in cache.info()"> 6236 <span ng-bind="key"></span> 6237 <span>: </span> 6238 <b ng-bind="value"></b> 6239 </div> 6240 </div> 6241 </file> 6242 <file name="script.js"> 6243 angular.module('cacheExampleApp', []). 6244 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 6245 $scope.keys = []; 6246 $scope.cache = $cacheFactory('cacheId'); 6247 $scope.put = function(key, value) { 6248 if (angular.isUndefined($scope.cache.get(key))) { 6249 $scope.keys.push(key); 6250 } 6251 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 6252 }; 6253 }]); 6254 </file> 6255 <file name="style.css"> 6256 p { 6257 margin: 10px 0 3px; 6258 } 6259 </file> 6260 </example> 6261 */ 6262function $CacheFactoryProvider() { 6263 6264 this.$get = function() { 6265 var caches = {}; 6266 6267 function cacheFactory(cacheId, options) { 6268 if (cacheId in caches) { 6269 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 6270 } 6271 6272 var size = 0, 6273 stats = extend({}, options, {id: cacheId}), 6274 data = createMap(), 6275 capacity = (options && options.capacity) || Number.MAX_VALUE, 6276 lruHash = createMap(), 6277 freshEnd = null, 6278 staleEnd = null; 6279 6280 /** 6281 * @ngdoc type 6282 * @name $cacheFactory.Cache 6283 *
6284 * @description 6285 * A cache object used to store and retrieve data, primarily used by 6286 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 6287 * templates and other data. 6288 * 6289 * ```js 6290 * angular.module('superCache') 6291 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 6292 * return $cacheFactory('super-cache'); 6293 * }]); 6294 * ``` 6295 * 6296 * Example test: 6297 * 6298 * ```js 6299 * it('should behave like a cache', inject(function(superCache) { 6300 * superCache.put('key', 'value'); 6301 * superCache.put('another key', 'another value'); 6302 * 6303 * expect(superCache.info()).toEqual({ 6304 * id: 'super-cache', 6305 * size: 2 6306 * }); 6307 * 6308 * superCache.remove('another key'); 6309 * expect(superCache.get('another key')).toBeUndefined(); 6310 * 6311 * superCache.removeAll(); 6312 * expect(superCache.info()).toEqual({ 6313 * id: 'super-cache', 6314 * size: 0 6315 * }); 6316 * })); 6317 * ``` 6318 */ 6319 return caches[cacheId] = { 6320 6321 /** 6322 * @ngdoc method 6323 * @name $cacheFactory.Cache#put 6324 * @kind function 6325 * 6326 * @description 6327 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 6328 * retrieved later, and incrementing the size of the cache if the key was not already 6329 * present in the cache. If behaving like an LRU cache, it will also remove stale 6330 * entries from the set. 6331 * 6332 * It will not insert undefined values into the cache. 6333 * 6334 * @param {string} key the key under which the cached data is stored. 6335 * @param {*} value the value to store alongside the key. If it is undefined, the key 6336 * will not be stored. 6337 * @returns {*} the value stored. 6338 */ 6339 put: function(key, value) { 6340 if (isUndefined(value)) return; 6341 if (capacity < Number.MAX_VALUE) { 6342 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 6343 6344 refresh(lruEntry); 6345 } 6346 6347 if (!(key in data)) size++; 6348 data[key] = value; 6349 6350 if (size > capacity) { 6351 this.remove(staleEnd.key); 6352 } 6353 6354 return value; 6355 }, 6356 6357 /** 6358 * @ngdoc method 6359 * @name $cacheFactory.Cache#get 6360 * @kind function 6361 * 6362 * @description 6363 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 6364 * 6365 * @param {string} key the key of the data to be retrieved 6366 * @returns {*} the value stored. 6367 */ 6368 get: function(key) { 6369 if (capacity < Number.MAX_VALUE) { 6370 var lruEntry = lruHash[key]; 6371 6372 if (!lruEntry) return; 6373 6374 refresh(lruEntry); 6375 } 6376 6377 return data[key]; 6378 }, 6379 6380 6381 /** 6382 * @ngdoc method 6383 * @name $cacheFactory.Cache#remove 6384 * @kind function 6385 * 6386 * @description 6387 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 6388 * 6389 * @param {string} key the key of the entry to be removed 6390 */ 6391 remove: function(key) { 6392 if (capacity < Number.MAX_VALUE) { 6393 var lruEntry = lruHash[key]; 6394 6395 if (!lruEntry) return; 6396 6397 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 6398 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 6399 link(lruEntry.n,lruEntry.p); 6400 6401 delete lruHash[key]; 6402 } 6403 6404 if (!(key in data)) return; 6405 6406 delete data[key]; 6407 size--; 6408 }, 6409 6410 6411 /** 6412 * @ngdoc method 6413 * @name $cacheFactory.Cache#removeAll 6414 * @kind function 6415 * 6416 * @description 6417 * Clears the cache object of any entries. 6418 */ 6419 removeAll: function() { 6420 data = createMap(); 6421 size = 0; 6422 lruHash = createMap(); 6423 freshEnd = staleEnd = null; 6424 }, 6425 6426 6427 /** 6428 * @ngdoc method 6429 * @name $cacheFactory.Cache#destroy 6430 * @kind function 6431 *
6432 * @description 6433 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 6434 * removing it from the {@link $cacheFactory $cacheFactory} set. 6435 */ 6436 destroy: function() { 6437 data = null; 6438 stats = null; 6439 lruHash = null; 6440 delete caches[cacheId]; 6441 }, 6442 6443 6444 /** 6445 * @ngdoc method 6446 * @name $cacheFactory.Cache#info 6447 * @kind function 6448 * 6449 * @description 6450 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 6451 * 6452 * @returns {object} an object with the following properties: 6453 * <ul> 6454 * <li>**id**: the id of the cache instance</li> 6455 * <li>**size**: the number of entries kept in the cache instance</li> 6456 * <li>**...**: any additional properties from the options object when creating the 6457 * cache.</li> 6458 * </ul> 6459 */ 6460 info: function() { 6461 return extend({}, stats, {size: size}); 6462 } 6463 }; 6464 6465 6466 /** 6467 * makes the `entry` the freshEnd of the LRU linked list 6468 */ 6469 function refresh(entry) { 6470 if (entry != freshEnd) { 6471 if (!staleEnd) { 6472 staleEnd = entry; 6473 } else if (staleEnd == entry) { 6474 staleEnd = entry.n; 6475 } 6476 6477 link(entry.n, entry.p); 6478 link(entry, freshEnd); 6479 freshEnd = entry; 6480 freshEnd.n = null; 6481 } 6482 } 6483 6484 6485 /** 6486 * bidirectionally links two entries of the LRU linked list 6487 */ 6488 function link(nextEntry, prevEntry) { 6489 if (nextEntry != prevEntry) { 6490 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 6491 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 6492 } 6493 } 6494 } 6495 6496 6497 /** 6498 * @ngdoc method 6499 * @name $cacheFactory#info 6500 * 6501 * @description 6502 * Get information about all the caches that have been created 6503 * 6504 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6505 */ 6506 cacheFactory.info = function() { 6507 var info = {}; 6508 forEach(caches, function(cache, cacheId) { 6509 info[cacheId] = cache.info(); 6510 }); 6511 return info; 6512 }; 6513 6514 6515 /** 6516 * @ngdoc method 6517 * @name $cacheFactory#get 6518 * 6519 * @description 6520 * Get access to a cache object by the `cacheId` used when it was created. 6521 * 6522 * @param {string} cacheId Name or id of a cache to access. 6523 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6524 */ 6525 cacheFactory.get = function(cacheId) { 6526 return caches[cacheId]; 6527 }; 6528 6529 6530 return cacheFactory; 6531 }; 6532} 6533 6534/** 6535 * @ngdoc service 6536 * @name $templateCache 6537 * 6538 * @description 6539 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6540 * can load templates directly into the cache in a `script` tag, or by consuming the 6541 * `$templateCache` service directly. 6542 * 6543 * Adding via the `script` tag: 6544 * 6545 * ```html 6546 * <script type="text/ng-template" id="templateId.html"> 6547 * <p>This is the content of the template</p> 6548 * </script> 6549 * ``` 6550 * 6551 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6552 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6553 * element with ng-app attribute), otherwise the template will be ignored. 6554 * 6555 * Adding via the `$templateCache` service: 6556 * 6557 * ```js 6558 * var myApp = angular.module('myApp', []); 6559 * myApp.run(function($templateCache) { 6560 * $templateCache.put('templateId.html', 'This is the content of the template'); 6561 * }); 6562 * ``` 6563 * 6564 * To retrieve the template later, simply use it in your HTML: 6565 * ```html 6566 * <div ng-include=" 'templateId.html' "></div> 6567 * ``` 6568 * 6569 * or get it via Javascript: 6570 * ```js 6571 * $templateCache.get('templateId.html') 6572 * ``` 6573 * 6574 * See {@link ng.$cacheFactory $cacheFactory}. 6575 * 6576 */ 6577function $TemplateCacheProvider() { 6578 this.$get = ['$cacheFactory', function($cacheFactory) { 6579 return $cacheFactory('templates'); 6580 }]; 6581} 6582 6583/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6584 * Any commits to this file should be reviewed with security in mind. * 6585 * Changes to this file can potentially create security vulnerabilities. * 6586 * An approval from 2 Core members with history of modifying * 6587 * this file is required. * 6588 * * 6589 * Does the change somehow allow for arbitrary javascript to be executed? * 6590 * Or allows for someone to change the prototype of built-in objects? * 6591 * Or gives undesired access to variables likes document or window? * 6592 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6593
6594/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6595 * 6596 * DOM-related variables: 6597 * 6598 * - "node" - DOM Node 6599 * - "element" - DOM Element or Node 6600 * - "$node" or "$element" - jqLite-wrapped node or element 6601 * 6602 * 6603 * Compiler related stuff: 6604 * 6605 * - "linkFn" - linking fn of a single directive 6606 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6607 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6608 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6609 */ 6610 6611 6612/** 6613 * @ngdoc service 6614 * @name $compile 6615 * @kind function 6616 * 6617 * @description 6618 * Compiles an HTML string or DOM into a template and produces a template function, which 6619 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6620 * 6621 * The compilation is a process of walking the DOM tree and matching DOM elements to 6622 * {@link ng.$compileProvider#directive directives}. 6623 * 6624 * <div class="alert alert-warning"> 6625 * **Note:** This document is an in-depth reference of all directive options. 6626 * For a gentle introduction to directives with examples of common use cases, 6627 * see the {@link guide/directive directive guide}. 6628 * </div> 6629 * 6630 * ## Comprehensive Directive API 6631 * 6632 * There are many different options for a directive. 6633 * 6634 * The difference resides in the return value of the factory function. 6635 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6636 * or just the `postLink` function (all other properties will have the default values). 6637 * 6638 * <div class="alert alert-success"> 6639 * **Best Practice:** It's recommended to use the "directive definition object" form. 6640 * </div> 6641 * 6642 * Here's an example directive declared with a Directive Definition Object: 6643 * 6644 * ```js 6645 * var myModule = angular.module(...); 6646 * 6647 * myModule.directive('directiveName', function factory(injectables) { 6648 * var directiveDefinitionObject = { 6649 * priority: 0, 6650 * template: '<div></div>
6650', // or // function(tElement, tAttrs) { ... }, 6651 * // or 6652 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6653 * transclude: false, 6654 * restrict: 'A', 6655 * templateNamespace: 'html', 6656 * scope: false, 6657 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6658 * controllerAs: 'stringIdentifier', 6659 * bindToController: false, 6660 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6661 * compile: function compile(tElement, tAttrs, transclude) { 6662 * return { 6663 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6664 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6665 * } 6666 * // or 6667 * // return function postLink( ... ) { ... } 6668 * }, 6669 * // or 6670 * // link: { 6671 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6672 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6673 * // } 6674 * // or 6675 * // link: function postLink( ... ) { ... } 6676 * }; 6677 * return directiveDefinitionObject; 6678 * }); 6679 * ``` 6680 * 6681 * <div class="alert alert-warning"> 6682 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6683 * </div> 6684 * 6685 * Therefore the above can be simplified as: 6686 * 6687 * ```js 6688 * var myModule = angular.module(...); 6689 * 6690 * myModule.directive('directiveName', function factory(injectables) { 6691 * var directiveDefinitionObject = { 6692 * link: function postLink(scope, iElement, iAttrs) { ... } 6693 * }; 6694 * return directiveDefinitionObject; 6695 * // or 6696 * // return function postLink(scope, iElement, iAttrs) { ... } 6697 * }); 6698 * ``` 6699 * 6700 * 6701 * 6702 * ### Directive Definition Object 6703 * 6704 * The directive definition object provides instructions to the {@link ng.$compile 6705 * compiler}. The attributes are: 6706 * 6707 * #### `multiElement` 6708 * When this property is set to true, the HTML compiler will collect DOM nodes between 6709 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6710 * together as the directive elements. It is recommended that this feature be used on directives 6711 * which are not strictly behavioral (such as {@link ngClick}), and which 6712 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6713 * 6714 * #### `priority` 6715 * When there are multiple directives defined on a single DOM element, sometimes it 6716 * is necessary to specify the order in which the directives are applied. The `priority` is used 6717 * to sort the directives before their `compile` functions get called. Priority is defined as a 6718 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6719 * are also run in priority order, but post-link functions are run in reverse order. The order 6720 * of directives with the same priority is undefined. The default priority is `0`. 6721 * 6722 * #### `terminal` 6723 * If set to true then the current `priority` will be the last set of directives 6724 * which will execute (any directives at the current priority will still execute 6725 * as the order of execution on same `priority` is undefined). Note that expressions 6726 * and other directives used in the directive's template will also be excluded from execution. 6727 * 6728 * #### `scope` 6729 * The scope property can be `true`, an object or a falsy value: 6730 * 6731 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 6732 * 6733 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 6734 * the directive's element. If multiple directives on the same element request a new scope, 6735 * only one new scope is created. The new scope rule does not apply for the root of the template 6736 * since the root of the template always gets a new scope. 6737 * 6738 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 6739 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 6740 * scope. This is useful when creating reusable components, which should not accidentally read or modify 6741 * data in the parent scope. 6742 * 6743 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 6744 * directive's element. These local properties are useful for aliasing values for templates. The keys in 6745 * the object hash map to the name of the property on the isolate scope; the values define how the property 6746 * is bound to the parent scope, via matching attributes on the directive's element: 6747 * 6748 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6749 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6750 * attribute name is assumed to be the same as the local name. Given `<my-component 6751 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`, 6752 * the directive's scope property `localName` will reflect the interpolated value of `hello 6753 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's 6754 * scope. The `name` is read from the parent scope (not the directive's scope). 6755 * 6756 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression 6757 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. 6758 * If no `attr` name is specified then the attribute name is assumed to be the same as the local 6759 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { 6760 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the 6761 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in 6762 * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: 6763 * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't 6764 * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) 6765 * will be thrown upon discovering changes to the local value, since it will be impossible to sync 6766 * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 6767 * method is used for tracking changes, and the equality check is based on object identity. 6768 * However, if an object literal or an array literal is passed as the binding expression, the 6769 * equality check is done by value (using the {@link angular.equals} function). It's also possible 6770 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection 6771 * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). 6772 * 6773 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an 6774 * expression passed via the attribute `attr`. The expression is evaluated in the context of the 6775 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the 6776 * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`. 6777 * 6778 * For example, given `<my-component my-attr="parentModel">` and directive definition of 6779 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the 6780 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6781 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however 6782 * two caveats: 6783 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply 6784 * sets the same value. That means if your bound value is an object, changes to its properties 6785 * in the isolated scope will be reflected in the parent scope (because both reference the same object). 6786 * 2. one-way binding watches changes to the **identity** of the parent value. That means the 6787 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference 6788 * to the value has changed. In most cases, this should not be of concern, but can be important 6789 * to know if you one-way bind to an object, and then replace that object in the isolated scope. 6790 * If you now change a property of the object in your parent scope, the change will not be 6791 * propagated to the isolated scope, because the identity of the object on the parent scope 6792 * has not changed. Instead you must assign a new object. 6793 * 6794 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings 6795 * back to the parent. However, it does not make this completely impossible. 6796 * 6797 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If 6798 * no `attr` name is specified then the attribute name is assumed to be the same as the local name. 6799 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: { 6800 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for 6801 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope 6802 * via an expression to the parent scope. This can be done by passing a map of local variable names 6803 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` 6804 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. 6805 * 6806 * In general it's possible to apply more than one directive to one element, but there might be limitations 6807 * depending on the type of scope required by the directives. The following points will help explain these limitations. 6808 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 6809 * 6810 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 6811 * * **child scope** + **no scope** => Both directives will share one single child scope 6812 * * **child scope** + **child scope** => Both directives will share one single child scope 6813 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 6814 * its parent's scope 6815 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 6816 * be applied to the same element. 6817 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 6818 * cannot be applied to the same element. 6819 * 6820 * 6821 * #### `bindToController` 6822 * This property is used to bind scope properties directly to the controller. It can be either 6823 * `true` or an object hash with the same format as the `scope` property. Additionally, a controller 6824 * alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller 6825 * definition: `controller: 'myCtrl as myAlias'`. 6826 * 6827 * When an isolate scope is used for a directive (see above), `bindToController: true` will 6828 * allow a component to have its properties bound to the controller, rather than to scope. 6829 * 6830 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller 6831 * properties. You can access these bindings once they have been initialized by providing a controller method called 6832 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings 6833 * initialized. 6834 * 6835 * <div class="alert alert-warning"> 6836 * **Deprecation warning:** although bindings for non-ES6 class controllers are currently
6837 * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization 6838 * code that relies upon bindings inside a `$onInit` method on the controller, instead. 6839 * </div> 6840 * 6841 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 6842 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 6843 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 6844 * scope (useful for component directives). 6845 * 6846 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 6847 * 6848 * 6849 * #### `controller` 6850 * Controller constructor function. The controller is instantiated before the 6851 * pre-linking phase and can be accessed by other directives (see 6852 * `require` attribute). This allows the directives to communicate with each other and augment 6853 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6854 * 6855 * * `$scope` - Current scope associated with the element 6856 * * `$element` - Current element 6857 * * `$attrs` - Current attributes object for the element 6858 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6859 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 6860 * * `scope`: (optional) override the scope. 6861 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 6862 * * `futureParentElement` (optional): 6863 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6864 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6865 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6866 * and when the `cloneLinkinFn` is passed, 6867 * as those elements need to created and cloned in a special way when they are defined outside their 6868 * usual containers (e.g. like `<svg>`). 6869 * * See also the `directive.templateNamespace` property. 6870 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 6871 * then the default translusion is provided. 6872 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 6873 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 6874 * 6875 * The controller can provide the following methods that act as life-cycle hooks: 6876 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and 6877 * had their bindings initialized (and before the pre & post linking functions for the directives on 6878 * this element). This is a good place to put initialization code for your controller. 6879 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The 6880 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an 6881 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a 6882 * component such as cloning the bound value to prevent accidental mutation of the outer value. 6883 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing 6884 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in 6885 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent 6886 * components will have their `$onDestroy()` hook called before child components. 6887 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link 6888 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. 6889 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since 6890 * they are waiting for their template to load asynchronously and their own compilation and linking has been 6891 * suspended until that occurs. 6892 * 6893 * 6894 * #### `require` 6895 * Require another directive and inject its controller as the fourth argument to the linking function. The 6896 * `require` property can be a string, an array or an object: 6897 * * a **string** containing the name of the directive to pass to the linking function 6898 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the 6899 * linking function will be an array of controllers in the same order as the names in the `require` property 6900 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument 6901 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding 6902 * controllers. 6903 * 6904 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are 6905 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers 6906 * have been constructed but before `$onInit` is called. 6907 * See the {@link $compileProvider#component} helper for an example of how this can be used. 6908 * 6909 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is 6910 * raised (unless no link function is specified and the required controllers are not being bound to the directive 6911 * controller, in which case error checking is skipped). The name can be prefixed with: 6912 * 6913 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6914 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6915 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6916 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6917 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6918 * `null` to the `link` fn if not found. 6919 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6920 * `null` to the `link` fn if not found. 6921 * 6922 * 6923 * #### `controllerAs` 6924 * Identifier name for a reference to the controller in the directive's scope. 6925 * This allows the controller to be referenced from the directive template. This is especially 6926 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 6927 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 6928 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 6929 * 6930 * 6931 * #### `restrict` 6932 * String of subset of `EACM` which restricts the directive to a specific directive 6933 * declaration style. If omitted, the defaults (elements and attributes) are used. 6934 * 6935 * * `E` - Element name (default): `<my-directive></my-directive>` 6936 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6937 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6938 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6939 * 6940 * 6941 * #### `templateNamespace` 6942 * String representing the document type used by the markup in the template. 6943 * AngularJS needs this information as those elements need to be created and cloned 6944 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6945 * 6946 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6947 * top-level elements such as `<svg>` or `<math>`. 6948 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6949 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6950 * 6951 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6952 * 6953 * #### `template` 6954 * HTML markup that may: 6955 * * Replace the contents of the directive's element (default). 6956 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6957 * * Wrap the contents of the directive's element (if `transclude` is true). 6958 * 6959 * Value may be: 6960 * 6961 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6962 * * A function which takes two arguments `tElement` and `tAttrs` (described
6962in the `compile` 6963 * function api below) and returns a string value. 6964 * 6965 * 6966 * #### `templateUrl` 6967 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6968 * 6969 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6970 * for later when the template has been resolved. In the meantime it will continue to compile and link 6971 * sibling and parent elements as though this element had not contained any directives. 6972 * 6973 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6974 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6975 * case when only one deeply nested directive has `templateUrl`. 6976 * 6977 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6978 * 6979 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 6980 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6981 * a string value representing the url. In either case, the template URL is passed through {@link 6982 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6983 * 6984 * 6985 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6986 * specify what the template should replace. Defaults to `false`. 6987 * 6988 * * `true` - the template will replace the directive's element. 6989 * * `false` - the template will replace the contents of the directive's element. 6990 * 6991 * The replacement process migrates all of the attributes / classes from the old element to the new 6992 * one. See the {@link guide/directive#template-expanding-directive 6993 * Directives Guide} for an example. 6994 * 6995 * There are very few scenarios where element replacement is required for the application function, 6996 * the main one being reusable custom components that are used within SVG contexts 6997 * (because SVG doesn't work with custom elements in the DOM tree). 6998 * 6999 * #### `transclude` 7000 * Extract the contents of the element where the directive appears and make it available to the directive. 7001 * The contents are compiled and provided to the directive as a **transclusion function**. See the 7002 * {@link $compile#transclusion Transclusion} section below. 7003 * 7004 * 7005 * #### `compile` 7006 * 7007 * ```js 7008 * function compile(tElement, tAttrs, transclude) { ... } 7009 * ``` 7010 * 7011 * The compile function deals with transforming the template DOM. Since most directives do not do 7012 * template transformation, it is not used often. The compile function takes the following arguments: 7013 * 7014 * * `tElement` - template element - The element where the directive has been declared. It is 7015 * safe to do template transformation on the element and child elements only. 7016 * 7017 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 7018 * between all directive compile functions. 7019 * 7020 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 7021 * 7022 * <div class="alert alert-warning"> 7023 * **Note:** The template instance and the link instance may be different objects if the template has 7024 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 7025 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 7026 * should be done in a linking function rather than in a compile function. 7027 * </div> 7028 7029 * <div class="alert alert-warning"> 7030 * **Note:** The compile function cannot handle directives that recursively use themselves in their 7031 * own templates or compile functions. Compiling these directives results in an infinite loop and 7032 * stack overflow errors. 7033 * 7034 * This can be avoided by manually using $compile in the postLink function to imperatively compile 7035 * a directive's template instead of relying on automatic template compilation via `template` or 7036 * `templateUrl` declaration or manual compilation inside the compile function. 7037 * </div> 7038 * 7039 * <div class="alert alert-danger"> 7040 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 7041 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 7042 * to the link function instead. 7043 * </div> 7044 7045 * A compile function can have a return value which can be either a function or an object. 7046 * 7047 * * returning a (post-link) function - is equivalent to registering the linking function via the 7048 * `link` property of the config object when the compile function is empty. 7049 * 7050 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 7051 * control when a linking function should be called during the linking phase. See info about 7052 * pre-linking and post-linking functions below. 7053 * 7054 * 7055 * #### `link` 7056 * This property is used only if the `compile` property is not defined. 7057 * 7058 * ```js 7059 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 7060 * ``` 7061 * 7062 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 7063 * executed after the template has been cloned. This is where most of the directive logic will be 7064 * put. 7065 * 7066 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 7067 * directive for registering {@link ng.$rootScope.Scope#$watch w
7067atches}. 7068 * 7069 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 7070 * manipulate the children of the element only in `postLink` function since the children have 7071 * already been linked. 7072 * 7073 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 7074 * between all directive linking functions. 7075 * 7076 * * `controller` - the directive's required controller instance(s) - Instances are shared 7077 * among all directives, which allows the directives to use the controllers as a communication 7078 * channel. The exact value depends on the directive's `require` property: 7079 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 7080 * * `string`: the controller instance 7081 * * `array`: array of controller instances 7082 * 7083 * If a required controller cannot be found, and it is optional, the instance is `null`, 7084 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 7085 * 7086 * Note that you can also require the directive's own controller - it will be made available like 7087 * any other controller. 7088 * 7089 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 7090 * This is the same as the `$transclude` 7091 * parameter of directive controllers, see there for details. 7092 * `function([scope], cloneLinkingFn, futureParentElement)`. 7093 * 7094 * #### Pre-linking function 7095 * 7096 * Executed before the child elements are linked. Not safe to do DOM transformation since the 7097 * compiler linking function will fail to locate the correct elements for linking. 7098 * 7099 * #### Post-linking function 7100 * 7101 * Executed after the child elements are linked. 7102 *
7103 * Note that child elements that contain `templateUrl` directives will not have been compiled 7104 * and linked since they are waiting for their template to load asynchronously and their own 7105 * compilation and linking has been suspended until that occurs. 7106 * 7107 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 7108 * for their async templates to be resolved. 7109 * 7110 * 7111 * ### Transclusion 7112 * 7113 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 7114 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 7115 * scope from where they were taken. 7116 * 7117 * Transclusion is used (often with {@link ngTransclude}) to insert the 7118 * original contents of a directive's element into a specified place in the template of the directive. 7119 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 7120 * content has access to the properties on the scope from which it was taken, even if the directive 7121 * has isolated scope. 7122 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 7123 * 7124 * This makes it possible for the widget to have private state for its template, while the transcluded 7125 * content has access to its originating scope. 7126 * 7127 * <div class="alert alert-warning"> 7128 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 7129 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 7130 * Testing Transclusion Directives}. 7131 * </div> 7132 * 7133 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 7134 * directive's element, the entire element or multiple parts of the element contents: 7135 * 7136 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 7137 * * `'element'` - transclude the whole of the directive's element including any directives on this 7138 * element that defined at a lower priority than this directive. When used, the `template` 7139 * property is ignored. 7140 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 7141 * 7142 * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. 7143 * 7144 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 7145 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 7146 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 7147 * 7148 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7149 * 7150 * If the element selector is prefixed with a `?` then that slot is optional. 7151 * 7152 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 7153 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 7154 * 7155 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 7156 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 7157 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 7158 * injectable into the directive's controller. 7159 * 7160 * 7161 * #### Transclusion Functions 7162 * 7163 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 7164 * function** to the directive's `link` function and `controller`. This transclusion function is a special 7165 * **linking function** that will return the compiled contents linked to a new transclusion scope. 7166 * 7167 * <div class="alert alert-info"> 7168 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 7169 * ngTransclude will deal with it for us. 7170 * </div> 7171 * 7172 * If you want to manually control the insertion and removal of the transcluded content in your directive 7173 * then you must use this transclude function. When you call a transclude function it returns a a jq
7173Lite/JQuery 7174 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 7175 * 7176 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 7177 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 7178 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 7179 * 7180 * <div class="alert alert-info"> 7181 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 7182 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 7183 * </div> 7184 * 7185 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 7186 * attach function**: 7187 * 7188 * ```js 7189 * var transcludedContent, transclusionScope; 7190 * 7191 * $transclude(function(clone, scope) { 7192 * element.append(clone); 7193 * transcludedContent = clone; 7194 * transclusionScope = scope; 7195 * }); 7196 * ``` 7197 * 7198 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 7199 * associated transclusion scope: 7200 * 7201 * ```js 7202 * transcludedContent.remove(); 7203 * transclusionScope.$destroy(); 7204 * ``` 7205 * 7206 * <div class="alert alert-info"> 7207 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 7208 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 7209 * then you are also responsible for calling `$destroy` on the transclusion scope. 7210 * </div> 7211 * 7212 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 7213 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 7214 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 7215 * 7216 * 7217 * #### Transclusion Scopes 7218 * 7219 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 7220 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 7221 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 7222 * was taken. 7223 * 7224 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 7225 * like this: 7226 * 7227 * ```html 7228 * <div ng-app> 7229 * <div isolate> 7230 * <div transclusion> 7231 * </div> 7232 * </div> 7233 * </div> 7234 * ``` 7235 * 7236 * The `$parent` scope hierarchy will look like this: 7237 * 7238 ``` 7239 - $rootScope 7240 - isolate 7241 - transclusion 7242 ``` 7243 * 7244 * but the scopes will inherit prototypically from different scopes to their `$parent`. 7245 * 7246 ``` 7247 - $rootScope 7248 - transclusion 7249 - isolate 7250 ``` 7251 * 7252 * 7253 * ### Attributes 7254 * 7255 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 7256 * `link()` or `compile()` functions. It has a variety of uses. 7257 * 7258 * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways: 7259 * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access 7260 * to the attributes. 7261 * 7262 * * *Directive inter-communication:* All directives share the same instance of the attributes 7263 * object which allows the directives to use the attributes object as inter directive 7264 * communication. 7265 * 7266 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 7267 * allowing other directives to read the interpolated value. 7268 * 7269 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 7270 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 7271 * the only way to easily get the actual value because during the linking phase the interpolation 7272 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 7273 * 7274 * ```js 7275 * function linkingFn(scope, elm, attrs, ctrl) { 7276 * // get the attribute value 7277 * console.log(attrs.ngModel); 7278 * 7279 * // change the attribute 7280 * attrs.$set('ngModel', 'new value'); 7281 * 7282 * // observe changes to interpolated attribute 7283 * attrs.$observe('ngModel', function(value) { 7284 * console.log('ngModel has changed value to ' + value); 7285 * }); 7286 * } 7287 * ``` 7288 * 7289 * ## Example 7290 * 7291 * <div class="alert alert-warning"> 7292 * **Note**: Typically directives are registered with `module.directive`. The example below is 7293 * to illustrate how `$compile` works. 7294 * </div> 7295 * 7296 <example module="compileExample"> 7297 <file name="index.html"> 7298 <script>
7299 angular.module('compileExample', [], function($compileProvider) { 7300 // configure new 'compile' directive by passing a directive 7301 // factory function. The factory function injects the '$compile' 7302 $compileProvider.directive('compile', function($compile) { 7303 // directive factory creates a link function 7304 return function(scope, element, attrs) { 7305 scope.$watch( 7306 function(scope) { 7307 // watch the 'compile' expression for changes 7308 return scope.$eval(attrs.compile); 7309 }, 7310 function(value) { 7311 // when the 'compile' expression changes 7312 // assign it into the current DOM 7313 element.html(value); 7314 7315 // compile the new DOM and link it to the current 7316 // scope. 7317 // NOTE: we only compile .childNodes so that 7318 // we don't get into infinite loop compiling ourselves 7319 $compile(element.contents())(scope); 7320 } 7321 ); 7322 }; 7323 }); 7324 }) 7325 .controller('GreeterController', ['$scope', function($scope) { 7326 $scope.name = 'Angular'; 7327 $scope.html = 'Hello {{name}}'; 7328 }]); 7329 </script> 7330 <div ng-controller="GreeterController"> 7331 <input ng-model="name"> <br/> 7332 <textarea ng-model="html"></textarea> <br/> 7333 <div compile="html"></div> 7334 </div> 7335 </file> 7336 <file name="protractor.js" type="protractor"> 7337 it('should auto compile', function() { 7338 var textarea = $('textarea'); 7339 var output = $('div[compile]'); 7340 // The initial state reads 'Hello Angular'. 7341 expect(output.getText()).toBe('Hello Angular'); 7342 textarea.clear(); 7343 textarea.sendKeys('{{name}}!'); 7344 expect(output.getText()).toBe('Angular!'); 7345 }); 7346 </file> 7347 </example> 7348 7349 * 7350 * 7351 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 7352 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 7353 * 7354 * <div class="alert alert-danger"> 7355 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 7356 * e.g. will not use the right outer scope. Please pass the transclude function as a 7357 * `parentBoundTranscludeFn` to the link function instead. 7358 * </div> 7359 * 7360 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 7361 * root element(s), not their children) 7362 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 7363 * (a DOM element/tree) to a scope. Where: 7364 * 7365 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 7366 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 7367 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 7368 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 7369 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 7370 * 7371 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 7372 * * `scope` - is the current scope with which the linking function is working with. 7373 * 7374 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 7375 * keys may be used to control linking behavior: 7376 * 7377 * * `parentBoundTranscludeFn` - the transclude function made available to 7378 * directives; if given, it will be passed through to the link functions of 7379 * directives found in `element` during compilation. 7380 * * `transcludeControllers` - an object hash with keys that map controller names 7381 * to a hash with the key `instance`, which maps to the controller instance; 7382 * if given, it will make the controllers available to directives on the compileNode: 7383 * ``` 7384 * { 7385 * parent: { 7386 * instance: parentControllerInstance 7387 * } 7388 * } 7389 * ``` 7390 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 7391 * the cloned elements; only needed for transcludes that are allowed to contain non html 7392 * elements (e.g. SVG elements). See also the directive.controller property. 7393 * 7394 * Calling the linking function returns the element of the template. It is either the original 7395 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 7396 * 7397 * After linking the view is not updated until after a call to $digest which typically is done by 7398 * Angular automatically. 7399 * 7400 * If you need access to the bound view, there are two ways to do it: 7401 * 7402 * - If you are not asking the linking function to clone the template, create the DOM element(s) 7403 * before you send them to the compiler and keep this reference around. 7404 * ```js 7405 * var element = $compile('<p>{{total}}</p>')(scope); 7406 * ``` 7407 * 7408 * - if on the other hand, you need the element to be cloned, the view reference from the original 7409 * example would not point to the clone, but rather to the original template that was cloned. In 7410 * this case, you can access the clone via the cloneAttachFn: 7411 * ```js 7412 * var templateElement = angular.element('<p>{{total}}</p>'), 7413 * scope = ....; 7414 * 7415 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 7416 * //attach the clone to DOM document at the right place 7417 * }); 7418 * 7419 * //now we have reference to the cloned DOM via `clonedElement` 7420 * ``` 7421 * 7422 * 7423 * For information on how the compiler works, see the 7424 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 7425 */ 7426 7427var $compileMinErr = minErr('$compile'); 7428 7429function UNINITIALIZED_VALUE() {} 7430var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); 7431 7432/** 7433 * @ngdoc provider 7434 * @name $compileProvider 7435 * 7436 * @description 7437 */
vendor: 16,166 bytes, lines 7438-7826
7438$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 7439function $CompileProvider($provide, $$sanitizeUriProvider) { 7440 var hasDirectives = {}, 7441 Suffix = 'Directive', 7442 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 7443 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 7444 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 7445 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 7446 7447 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 7448 // The assumption is that future DOM event attribute names will begin with 7449 // 'on' and be composed of only English letters. 7450 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 7451 var bindingCache = createMap(); 7452 7453 function parseIsolateBindings(scope, directiveName, isController) { 7454 var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/; 7455 7456 var bindings = createMap(); 7457 7458 forEach(scope, function(definition, scopeName) { 7459 if (definition in bindingCache) { 7460 bindings[scopeName] = bindingCache[definition]; 7461 return; 7462 } 7463 var match = definition.match(LOCAL_REGEXP); 7464 7465 if (!match) { 7466 throw $compileMinErr('iscp', 7467 "Invalid {3} for directive '{0}'." + 7468 " Definition: {... {1}: '{2}' ...}", 7469 directiveName, scopeName, definition, 7470 (isController ? "controller bindings definition" : 7471 "isolate scope definition")); 7472 } 7473 7474 bindings[scopeName] = { 7475 mode: match[1][0], 7476 collection: match[2] === '*', 7477 optional: match[3] === '?', 7478 attrName: match[4] || scopeName 7479 }; 7480 if (match[4]) { 7481 bindingCache[definition] = bindings[scopeName]; 7482 } 7483 }); 7484 7485 return bindings; 7486 } 7487 7488 function parseDirectiveBindings(directive, directiveName) { 7489 var bindings = { 7490 isolateScope: null, 7491 bindToController: null 7492 }; 7493 if (isObject(directive.scope)) { 7494 if (directive.bindToController === true) { 7495 bindings.bindToController = parseIsolateBindings(directive.scope, 7496 directiveName, true); 7497 bindings.isolateScope = {}; 7498 } else { 7499 bindings.isolateScope = parseIsolateBindings(directive.scope, 7500 directiveName, false); 7501 } 7502 } 7503 if (isObject(directive.bindToController)) { 7504 bindings.bindToController = 7505 parseIsolateBindings(directive.bindToController, directiveName, true); 7506 } 7507 if (isObject(bindings.bindToController)) { 7508 var controller = directive.controller; 7509 var controllerAs = directive.controllerAs; 7510 if (!controller) { 7511 // There is no controller, there may or may not be a controllerAs property 7512 throw $compileMinErr('noctrl', 7513 "Cannot bind to controller without directive '{0}'s controller.", 7514 directiveName); 7515 } else if (!identifierForController(controller, controllerAs)) { 7516 // There is a controller, but no identifier or controllerAs property 7517 throw $compileMinErr('noident', 7518 "Cannot bind to controller without identifier for directive '{0}'.", 7519 directiveName); 7520 } 7521 } 7522 return bindings; 7523 } 7524 7525 function assertValidDirectiveName(name) { 7526 var letter = name.charAt(0); 7527 if (!letter || letter !== lowercase(letter)) { 7528 throw $compileMinErr('baddir', "Directive/Component name '{0}' is invalid. The first character must be a lowercase letter", name); 7529 } 7530 if (name !== name.trim()) { 7531 throw $compileMinErr('baddir', 7532 "Directive/Component name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 7533 name); 7534 } 7535 } 7536 7537 /** 7538 * @ngdoc method 7539 * @name $compileProvider#directive 7540 * @kind function 7541 * 7542 * @description 7543 * Register a new directive with the compiler. 7544 * 7545 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 7546 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 7547 * names and the values are the factories. 7548 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 7549 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 7550 * @returns {ng.$compileProvider} Self for chaining. 7551 */ 7552 this.directive = function registerDirective(name, directiveFactory) { 7553 assertNotHasOwnProperty(name, 'directive'); 7554 if (isString(name)) { 7555 assertValidDirectiveName(name); 7556 assertArg(directiveFactory, 'directiveFactory'); 7557 if (!hasDirectives.hasOwnProperty(name)) { 7558 hasDirectives[name] = []; 7559 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 7560 function($injector, $exceptionHandler) { 7561 var directives = []; 7562 forEach(hasDirectives[name], function(directiveFactory, index) { 7563 try { 7564 var directive = $injector.invoke(directiveFactory); 7565 if (isFunction(directive)) { 7566 directive = { compile: valueFn(directive) }; 7567 } else if (!directive.compile && directive.link) { 7568 directive.compile = valueFn(directive.link); 7569 } 7570 directive.priority = directive.priority || 0; 7571 directive.index = index; 7572 directive.name = directive.name || name; 7573 directive.require = directive.require || (directive.controller && directive.name); 7574 directive.restrict = directive.restrict || 'EA'; 7575 directive.$$moduleName = directiveFactory.$$moduleName; 7576 directives.push(directive); 7577 } catch (e) { 7578 $exceptionHandler(e); 7579 } 7580 }); 7581 return directives; 7582 }]); 7583 } 7584 hasDirectives[name].push(directiveFactory); 7585 } else { 7586 forEach(name, reverseParams(registerDirective)); 7587 } 7588 return this; 7589 }; 7590 7591 /** 7592 * @ngdoc method 7593 * @name $compileProvider#component 7594 * @module ng 7595 * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`) 7596 * @param {Object} options Component definition object (a simplified 7597 * {@link ng.$compile#directive-definition-object directive definition object}), 7598 * with the following properties (all optional): 7599 * 7600 * - `controller` – `{(string|function()=}` – controller constructor function that should be 7601 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 7602 * registered controller} if passed as a string. An empty `noop` function by default. 7603 * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope. 7604 * If present, the controller will be published to scope under the `controllerAs` name. 7605 * If not present, this will default to be `$ctrl`. 7606 * - `template` – `{string=|function()=}` – html template as a string or a function that 7607 * returns an html template as a string which should be used as the contents of this component. 7608 * Empty string by default. 7609 * 7610 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 7611 * the following locals: 7612 * 7613 * - `$element` - Current element 7614 * - `$attrs` - Current attributes object for the element 7615 * 7616 * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 7617 * template that should be used as the contents of this component. 7618 * 7619 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 7620 * the following locals: 7621 * 7622 * - `$element` - Current element 7623 * - `$attrs` - Current attributes object for the element 7624 * 7625 * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties. 7626 * Component properties are always bound to the component controller and not to the scope. 7627 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 7628 * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled. 7629 * Disabled by default. 7630 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to 7631 * this component's controller. The object keys specify the property names under which the required 7632 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. 7633 * - `$...` – additional properties to attach to the directive factory function and the controller 7634 * constructor function. (This is used by the component router to annotate) 7635 * 7636 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 7637 * @description 7638 * Register a **component definition** with the compiler. This is a shorthand for registering a special 7639 * type of directive, which represents a self-contained UI component in your application. Such components 7640 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 7641 * 7642 * Component definitions are very simple and do not require as much configuration as defining general 7643 * directives. Component definitions usually consist only of a template and a controller backing it. 7644 * 7645 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 7646 * `bindToController`. They always have **isolate scope** and are restricted to elements. 7647 * 7648 * Here are a few examples of how you would usually define components: 7649 * 7650 * ```js 7651 * var myMod = angular.module(...); 7652 * myMod.component('myComp', { 7653 * template: '<div>My name is {{$ctrl.name}}</div>', 7654 * controller: function() { 7655 * this.name = 'shahar'; 7656 * } 7657 * }); 7658 * 7659 * myMod.component('myComp', { 7660 * template: '<div>My name is {{$ctrl.name}}</div>', 7661 * bindings: {name: '@'} 7662 * }); 7663 * 7664 * myMod.component('myComp', { 7665 * templateUrl: 'views/my-comp.html', 7666 * controller: 'MyCtrl', 7667 * controllerAs: 'ctrl', 7668 * bindings: {name: '@'} 7669 * }); 7670 * 7671 * ``` 7672 * For more examples, and an in-depth guide, see the {@link guide/component component guide}. 7673 * 7674 * <br /> 7675 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 7676 */ 7677 this.component = function registerComponent(name, options) { 7678 var controller = options.controller || function() {}; 7679 7680 function factory($injector) { 7681 function makeInjectable(fn) { 7682 if (isFunction(fn) || isArray(fn)) { 7683 return function(tElement, tAttrs) { 7684 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 7685 }; 7686 } else { 7687 return fn; 7688 } 7689 } 7690 7691 var template = (!options.template && !options.templateUrl ? '' : options.template); 7692 var ddo = { 7693 controller: controller, 7694 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 7695 template: makeInjectable(template), 7696 templateUrl: makeInjectable(options.templateUrl), 7697 transclude: options.transclude, 7698 scope: {}, 7699 bindToController: options.bindings || {}, 7700 restrict: 'E', 7701 require: options.require 7702 }; 7703 7704 // Copy annotations (starting with $) over to the DDO 7705 forEach(options, function(val, key) { 7706 if (key.charAt(0) === '$') ddo[key] = val; 7707 }); 7708 7709 return ddo; 7710 } 7711 7712 // TODO(pete) remove the following `forEach` before we release 1.6.0 7713 // The [email protected] looks for the annotations on the controller constructor 7714 // Nothing in Angular looks for annotations on the factory function but we can't remove 7715 // it from 1.5.x yet. 7716 7717 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions 7718 // These could be used by libraries such as the new component router 7719 forEach(options, function(val, key) { 7720 if (key.charAt(0) === '$') { 7721 factory[key] = val; 7722 // Don't try to copy over annotations to named controller 7723 if (isFunction(controller)) controller[key] = val; 7724 } 7725 }); 7726 7727 factory.$inject = ['$injector']; 7728 7729 return this.directive(name, factory); 7730 }; 7731 7732 7733 /** 7734 * @ngdoc method 7735 * @name $compileProvider#aHrefSanitizationWhitelist 7736 * @kind function 7737 * 7738 * @description 7739 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7740 * urls during a[href] sanitization. 7741 * 7742 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 7743 * 7744 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 7745 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 7746 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7747 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7748 * 7749 * @param {RegExp=} regexp New regexp to whitelist urls with. 7750 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7751 * chaining otherwise. 7752 */ 7753 this.aHrefSanitizationWhitelist = function(regexp) { 7754 if (isDefined(regexp)) { 7755 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 7756 return this; 7757 } else { 7758 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 7759 } 7760 }; 7761 7762 7763 /** 7764 * @ngdoc method 7765 * @name $compileProvider#imgSrcSanitizationWhitelist 7766 * @kind function 7767 * 7768 * @description 7769 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7770 * urls during img[src] sanitization. 7771 * 7772 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 7773 * 7774 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 7775 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 7776 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7777 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7778 * 7779 * @param {RegExp=} regexp New regexp to whitelist urls with. 7780 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7781 * chaining otherwise. 7782 */ 7783 this.imgSrcSanitizationWhitelist = function(regexp) { 7784 if (isDefined(regexp)) { 7785 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 7786 return this; 7787 } else { 7788 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7789 } 7790 }; 7791 7792 /** 7793 * @ngdoc method 7794 * @name $compileProvider#debugInfoEnabled 7795 * 7796 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7797 * current debugInfoEnabled state 7798 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7799 * 7800 * @kind function 7801 * 7802 * @description 7803 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7804 * binding information and a reference to the current scope on to DOM elements. 7805 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7806 * * `ng-binding` CSS class 7807 * * `$binding` data property containing an array of the binding expressions 7808 * 7809 * You may want to disable this in production for a significant performance boost. See 7810 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7811 * 7812 * The default value is true. 7813 */ 7814 var debugInfoEnabled = true; 7815 this.debugInfoEnabled = function(enabled) { 7816 if (isDefined(enabled)) { 7817 debugInfoEnabled = enabled; 7818 return this; 7819 } 7820 return debugInfoEnabled; 7821 }; 7822 7823 7824 var TTL = 10; 7825 /** 7826 * @ngdoc method
7827 * @name $compileProvider#onChangesTtl 7828 * @description 7829 * 7830 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and 7831 * assuming that the model is unstable. 7832 * 7833 * The current default is 10 iterations. 7834 * 7835 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result 7836 * in several iterations of calls to these hooks. However if an application needs more than the default 10 7837 * iterations to stabilize then you should investigate what is causing the model to continuously change during 7838 * the `$onChanges` hook execution. 7839 * 7840 * Increasing the TTL could have performance implications, so you should not change it without proper justification. 7841 * 7842 * @param {number} limit The number of `$onChanges` hook iterations. 7843 * @returns {number|object} the current limit (or `this` if called as a setter for chaining) 7844 */ 7845 this.onChangesTtl = function(value) { 7846 if (arguments.length) { 7847 TTL = value; 7848 return this; 7849 } 7850 return TTL; 7851 }; 7852 7853 this.$get = [ 7854 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7855 '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', 7856 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7857 $controller, $rootScope, $sce, $animate, $$sanitizeUri) { 7858 7859 var SIMPLE_ATTR_NAME = /^\w/; 7860 var specialAttrHolder = window.document.createElement('div'); 7861 7862 7863 7864 var onChangesTtl = TTL; 7865 // The onChanges hooks should all be run together in a single digest 7866 // When changes occur, the call to trigger their hooks will be added to this queue 7867 var onChangesQueue; 7868 7869 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest 7870 function flushOnChangesQueue() { 7871 try { 7872 if (!(--onChangesTtl)) { 7873 // We have hit the TTL limit so reset everything 7874 onChangesQueue = undefined; 7875 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); 7876 } 7877 // We must run this hook in an apply since the $$postDigest runs outside apply 7878 $rootScope.$apply(function() { 7879 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { 7880 onChangesQueue[i](); 7881 } 7882 // Reset the queue to trigger a new schedule next time there is a change 7883 onChangesQueue = undefined; 7884 }); 7885 } finally { 7886 onChangesTtl++; 7887 } 7888 } 7889 7890 7891 function Attributes(element, attributesToCopy) { 7892 if (attributesToCopy) { 7893 var keys = Object.keys(attributesToCopy); 7894 var i, l, key; 7895 7896 for (i = 0, l = keys.length; i < l; i++) { 7897 key = keys[i]; 7898 this[key] = attributesToCopy[key]; 7899 } 7900 } else { 7901 this.$attr = {}; 7902 } 7903 7904 this.$$element = element; 7905 } 7906 7907 Attributes.prototype = { 7908 /** 7909 * @ngdoc method 7910 * @name $compile.directive.Attributes#$normalize 7911 * @kind function 7912 * 7913 * @description 7914 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7915 * `data-`) to its normalized, camelCase form. 7916 * 7917 * Also there is special case for Moz prefix starting with upper case letter. 7918 * 7919 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7920 * 7921 * @param {string} name Name to normalize 7922 */ 7923 $normalize: directiveNormalize, 7924 7925 7926 /** 7927 * @ngdoc method 7928 * @name $compile.directive.Attributes#$addClass 7929 * @kind function 7930 * 7931 * @description 7932 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7933 * are enabled then an animation will be triggered for the class addition. 7934 * 7935 * @param {string} classVal The className value that will be added to the element 7936 */ 7937 $addClass: function(classVal) { 7938 if (classVal && classVal.length > 0) { 7939 $animate.addClass(this.$$element, classVal); 7940 } 7941 }, 7942 7943 /** 7944 * @ngdoc method 7945 * @name $compile.directive.Attributes#$removeClass 7946 * @kind function 7947 *
7948 * @description 7949 * Removes the CSS class value specified by the classVal parameter from the element. If 7950 * animations are enabled then an animation will be triggered for the class removal. 7951 * 7952 * @param {string} classVal The className value that will be removed from the element 7953 */ 7954 $removeClass: function(classVal) { 7955 if (classVal && classVal.length > 0) { 7956 $animate.removeClass(this.$$element, classVal); 7957 } 7958 }, 7959 7960 /** 7961 * @ngdoc method 7962 * @name $compile.directive.Attributes#$updateClass 7963 * @kind function 7964 * 7965 * @description 7966 * Adds and removes the appropriate CSS class values to the element based on the difference 7967 * between the new and old CSS class values (specified as newClasses and oldClasses). 7968 * 7969 * @param {string} newClasses The current CSS className value 7970 * @param {string} oldClasses The former CSS className value 7971 */ 7972 $updateClass: function(newClasses, oldClasses) { 7973 var toAdd = tokenDifference(newClasses, oldClasses); 7974 if (toAdd && toAdd.length) { 7975 $animate.addClass(this.$$element, toAdd); 7976 } 7977 7978 var toRemove = tokenDifference(oldClasses, newClasses); 7979 if (toRemove && toRemove.length) { 7980 $animate.removeClass(this.$$element, toRemove); 7981 } 7982 }, 7983 7984 /** 7985 * Set a normalized attribute on the element in a way such that all directives 7986 * can share the attribute. This function properly handles boolean attributes. 7987 * @param {string} key Normalized key. (ie ngAttribute) 7988 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7989 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7990 * Defaults to true. 7991 * @param {string=} attrName Optional none normalized name. Defaults to key. 7992 */ 7993 $set: function(key, value, writeAttr, attrName) { 7994 // TODO: decide whether or not to throw an error if "class" 7995 //is set through this function since it may cause $updateClass to 7996 //become unstable. 7997 7998 var node = this.$$element[0], 7999 booleanKey = getBooleanAttrName(node, key), 8000 aliasedKey = getAliasedAttrName(key), 8001 observer = key, 8002 nodeName; 8003 8004 if (booleanKey) { 8005 this.$$element.prop(key, value); 8006 attrName = booleanKey; 8007 } else if (aliasedKey) { 8008 this[aliasedKey] = value; 8009 observer = aliasedKey; 8010 } 8011 8012 this[key] = value; 8013 8014 // translate normalized key to actual key 8015 if (attrName) { 8016 this.$attr[key] = attrName; 8017 } else { 8018 attrName = this.$attr[key]; 8019 if (!attrName) { 8020 this.$attr[key] = attrName = snake_case(key, '-'); 8021 } 8022 } 8023 8024 nodeName = nodeName_(this.$$element); 8025 8026 if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || 8027 (nodeName === 'img' && key === 'src')) { 8028 // sanitize a[href] and img[src] values 8029 this[key] = value = $$sanitizeUri(value, key === 'src'); 8030 } else if (nodeName === 'img' && key === 'srcset') { 8031 // sanitize img[srcset] values 8032 var result = ""; 8033 8034 // first check if there are spaces because it's not the same pattern 8035 var trimmedSrcset = trim(value); 8036 // ( 999x ,| 999w ,| ,|, ) 8037 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 8038 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 8039 8040 // split srcset into tuple of uri and descriptor except for the last item 8041 var rawUris = trimmedSrcset.split(pattern); 8042 8043 // for each tuples 8044 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 8045 for (var i = 0; i < nbrUrisWith2parts; i++) { 8046 var innerIdx = i * 2; 8047 // sanitize the uri 8048 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 8049 // add the descriptor 8050 result += (" " + trim(rawUris[innerIdx + 1])); 8051 } 8052 8053 // split the last item into uri and descriptor 8054 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 8055 8056 // sanitize the last uri 8057 result += $$sanitizeUri(trim(lastTuple[0]), true); 8058 8059 // and add the last descriptor if any 8060 if (lastTuple.length === 2) { 8061 result += (" " + trim(lastTuple[1])); 8062 } 8063 this[key] = value = result; 8064 } 8065 8066 if (writeAttr !== false) { 8067 if (value === null || isUndefined(value)) { 8068 this.$$element.removeAttr(attrName); 8069 } else { 8070 if (SIMPLE_ATTR_NAME.test(attrName)) { 8071 this.$$element.attr(attrName, value); 8072 } else { 8073 setSpecialAttr(this.$$element[0], attrName, value); 8074 } 8075 } 8076 } 8077 8078 // fire observers 8079 var $$observers = this.$$observers;
vendor: 28,621 bytes, lines 8080-8755
8080 $$observers && forEach($$observers[observer], function(fn) { 8081 try { 8082 fn(value); 8083 } catch (e) { 8084 $exceptionHandler(e); 8085 } 8086 }); 8087 }, 8088 8089 8090 /** 8091 * @ngdoc method 8092 * @name $compile.directive.Attributes#$observe 8093 * @kind function 8094 * 8095 * @description 8096 * Observes an interpolated attribute. 8097 * 8098 * The observer function will be invoked once during the next `$digest` following 8099 * compilation. The observer is then invoked whenever the interpolated value 8100 * changes. 8101 * 8102 * @param {string} key Normalized key. (ie ngAttribute) . 8103 * @param {function(interpolatedValue)} fn Function that will be called whenever 8104 the interpolated value of the attribute changes. 8105 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 8106 * guide} for more info. 8107 * @returns {function()} Returns a deregistration function for this observer. 8108 */ 8109 $observe: function(key, fn) { 8110 var attrs = this, 8111 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 8112 listeners = ($$observers[key] || ($$observers[key] = [])); 8113 8114 listeners.push(fn); 8115 $rootScope.$evalAsync(function() { 8116 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 8117 // no one registered attribute interpolation function, so lets call it manually 8118 fn(attrs[key]); 8119 } 8120 }); 8121 8122 return function() { 8123 arrayRemove(listeners, fn); 8124 }; 8125 } 8126 }; 8127 8128 function setSpecialAttr(element, attrName, value) { 8129 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 8130 // so we have to jump through some hoops to get such an attribute 8131 // https://github.com/angular/angular.js/pull/13318 8132 specialAttrHolder.innerHTML = "<span " + attrName + ">"; 8133 var attributes = specialAttrHolder.firstChild.attributes; 8134 var attribute = attributes[0]; 8135 // We have to remove the attribute from its container element before we can add it to the destination element 8136 attributes.removeNamedItem(attribute.name); 8137 attribute.value = value; 8138 element.attributes.setNamedItem(attribute); 8139 } 8140 8141 function safeAddClass($element, className) { 8142 try { 8143 $element.addClass(className); 8144 } catch (e) { 8145 // ignore, since it means that we are trying to set class on 8146 // SVG element, where class name is read-only. 8147 } 8148 } 8149 8150 8151 var startSymbol = $interpolate.startSymbol(), 8152 endSymbol = $interpolate.endSymbol(), 8153 denormalizeTemplate = (startSymbol == '{{' && endSymbol == '}}') 8154 ? identity 8155 : function denormalizeTemplate(template) { 8156 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 8157 }, 8158 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 8159 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 8160 8161 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 8162 var bindings = $element.data('$binding') || []; 8163 8164 if (isArray(binding)) { 8165 bindings = bindings.concat(binding); 8166 } else { 8167 bindings.push(binding); 8168 } 8169 8170 $element.data('$binding', bindings); 8171 } : noop; 8172 8173 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 8174 safeAddClass($element, 'ng-binding'); 8175 } : noop; 8176 8177 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 8178 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 8179 $element.data(dataName, scope); 8180 } : noop; 8181 8182 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 8183 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 8184 } : noop; 8185 8186 compile.$$createComment = function(directiveName, comment) { 8187 var content = ''; 8188 if (debugInfoEnabled) { 8189 content = ' ' + (directiveName || '') + ': ' + (comment || '') + ' '; 8190 } 8191 return window.document.createComment(content); 8192 }; 8193 8194 return compile; 8195 8196 //================================ 8197 8198 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 8199 previousCompileContext) { 8200 if (!($compileNodes instanceof jqLite)) { 8201 // jquery always rewraps, whereas we need to preserve the original selector so that we can 8202 // modify it. 8203 $compileNodes = jqLite($compileNodes); 8204 } 8205 8206 var NOT_EMPTY = /\S+/; 8207 8208 // We can not compile top level text elements since text nodes can be merged and we will 8209 // not be able to attach scope data to them, so we will wrap them in <span> 8210 for (var i = 0, len = $compileNodes.length; i < len; i++) { 8211 var domNode = $compileNodes[i]; 8212 8213 if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) { 8214 jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span')); 8215 } 8216 } 8217 8218 var compositeLinkFn = 8219 compileNodes($compileNodes, transcludeFn, $compileNodes, 8220 maxPriority, ignoreDirective, previousCompileContext); 8221 compile.$$addScopeClass($compileNodes); 8222 var namespace = null; 8223 return function publicLinkFn(scope, cloneConnectFn, options) { 8224 assertArg(scope, 'scope'); 8225 8226 if (previousCompileContext && previousCompileContext.needsNewScope) { 8227 // A parent directive did a replace and a directive on this element asked 8228 // for transclusion, which caused us to lose a layer of element on which 8229 // we could hold the new transclusion scope, so we will create it manually 8230 // here. 8231 scope = scope.$parent.$new(); 8232 } 8233 8234 options = options || {}; 8235 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 8236 transcludeControllers = options.transcludeControllers, 8237 futureParentElement = options.futureParentElement; 8238 8239 // When `parentBoundTranscludeFn` is passed, it is a 8240 // `controllersBoundTransclude` function (it was previously passed 8241 // as `transclude` to directive.link) so we must unwrap it to get 8242 // its `boundTranscludeFn` 8243 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 8244 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 8245 } 8246 8247 if (!namespace) { 8248 namespace = detectNamespaceForChildElements(futureParentElement); 8249 } 8250 var $linkNode; 8251 if (namespace !== 'html') { 8252 // When using a directive with replace:true and templateUrl the $compileNodes 8253 // (or a child element inside of them) 8254 // might change, so we need to recreate the namespace adapted compileNodes 8255 // for call to the link function. 8256 // Note: This will already clone the nodes... 8257 $linkNode = jqLite( 8258 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 8259 ); 8260 } else if (cloneConnectFn) { 8261 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 8262 // and sometimes changes the structure of the DOM. 8263 $linkNode = JQLitePrototype.clone.call($compileNodes); 8264 } else { 8265 $linkNode = $compileNodes; 8266 } 8267 8268 if (transcludeControllers) { 8269 for (var controllerName in transcludeControllers) { 8270 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 8271 } 8272 } 8273 8274 compile.$$addScopeInfo($linkNode, scope); 8275 8276 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 8277 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 8278 return $linkNode; 8279 }; 8280 } 8281 8282 function detectNamespaceForChildElements(parentElement) { 8283 // TODO: Make this detect MathML as well... 8284 var node = parentElement && parentElement[0]; 8285 if (!node) { 8286 return 'html'; 8287 } else { 8288 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 8289 } 8290 } 8291 8292 /** 8293 * Compile function matches each node in nodeList against the directives. Once all directives 8294 * for a particular node are collected their compile functions are executed. The compile 8295 * functions return values - the linking functions - are combined into a composite linking 8296 * function, which is the a linking function for the node. 8297 * 8298 * @param {NodeList} nodeList an array of nodes or NodeList to compile 8299 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 8300 * scope argument is auto-generated to the new child of the transcluded parent scope. 8301 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 8302 * the rootElement must be set the jqLite collection of the compile root. This is 8303 * needed so that the jqLite collection items can be replaced with widgets. 8304 * @param {number=} maxPriority Max directive priority. 8305 * @returns {Function} A composite linking function of all of the matched directives or null. 8306 */ 8307 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 8308 previousCompileContext) { 8309 var linkFns = [], 8310 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 8311 8312 for (var i = 0; i < nodeList.length; i++) { 8313 attrs = new Attributes(); 8314 8315 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 8316 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 8317 ignoreDirective); 8318 8319 nodeLinkFn = (directives.length) 8320 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 8321 null, [], [], previousCompileContext) 8322 : null; 8323 8324 if (nodeLinkFn && nodeLinkFn.scope) { 8325 compile.$$addScopeClass(attrs.$$element); 8326 } 8327 8328 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 8329 !(childNodes = nodeList[i].childNodes) || 8330 !childNodes.length) 8331 ? null 8332 : compileNodes(childNodes, 8333 nodeLinkFn ? ( 8334 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 8335 && nodeLinkFn.transclude) : transcludeFn); 8336 8337 if (nodeLinkFn || childLinkFn) { 8338 linkFns.push(i, nodeLinkFn, childLinkFn); 8339 linkFnFound = true; 8340 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 8341 } 8342 8343 //use the previous context only for the first element in the virtual group 8344 previousCompileContext = null; 8345 } 8346 8347 // return a linking function if we have found anything, null otherwise 8348 return linkFnFound ? compositeLinkFn : null; 8349 8350 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 8351 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 8352 var stableNodeList; 8353 8354 8355 if (nodeLinkFnFound) { 8356 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 8357 // offsets don't get screwed up 8358 var nodeListLength = nodeList.length; 8359 stableNodeList = new Array(nodeListLength); 8360 8361 // create a sparse array by only copying the elements which have a linkFn 8362 for (i = 0; i < linkFns.length; i+=3) { 8363 idx = linkFns[i]; 8364 stableNodeList[idx] = nodeList[idx]; 8365 } 8366 } else { 8367 stableNodeList = nodeList; 8368 } 8369 8370 for (i = 0, ii = linkFns.length; i < ii;) { 8371 node = stableNodeList[linkFns[i++]]; 8372 nodeLinkFn = linkFns[i++]; 8373 childLinkFn = linkFns[i++]; 8374 8375 if (nodeLinkFn) { 8376 if (nodeLinkFn.scope) { 8377 childScope = scope.$new(); 8378 compile.$$addScopeInfo(jqLite(node), childScope); 8379 } else { 8380 childScope = scope; 8381 } 8382 8383 if (nodeLinkFn.transcludeOnThisElement) { 8384 childBoundTranscludeFn = createBoundTranscludeFn( 8385 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 8386 8387 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 8388 childBoundTranscludeFn = parentBoundTranscludeFn; 8389 8390 } else if (!parentBoundTranscludeFn && transcludeFn) { 8391 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 8392 8393 } else { 8394 childBoundTranscludeFn = null; 8395 } 8396 8397 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 8398 8399 } else if (childLinkFn) { 8400 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 8401 } 8402 } 8403 } 8404 } 8405 8406 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 8407 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 8408 8409 if (!transcludedScope) { 8410 transcludedScope = scope.$new(false, containingScope); 8411 transcludedScope.$$transcluded = true; 8412 } 8413 8414 return transcludeFn(transcludedScope, cloneFn, { 8415 parentBoundTranscludeFn: previousBoundTranscludeFn, 8416 transcludeControllers: controllers, 8417 futureParentElement: futureParentElement 8418 }); 8419 } 8420 8421 // We need to attach the transclusion slots onto the `boundTranscludeFn` 8422 // so that they are available inside the `controllersBoundTransclude` function 8423 var boundSlots = boundTranscludeFn.$$slots = createMap(); 8424 for (var slotName in transcludeFn.$$slots) { 8425 if (transcludeFn.$$slots[slotName]) { 8426 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 8427 } else { 8428 boundSlots[slotName] = null; 8429 } 8430 } 8431 8432 return boundTranscludeFn; 8433 } 8434 8435 /** 8436 * Looks for directives on the given node and adds them to the directive collection which is 8437 * sorted. 8438 * 8439 * @param node Node to search. 8440 * @param directives An array to which the directives are added to. This array is sorted before 8441 * the function returns. 8442 * @param attrs The shared attrs object which is used to populate the normalized attributes. 8443 * @param {number=} maxPriority Max directive priority. 8444 */ 8445 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 8446 var nodeType = node.nodeType, 8447 attrsMap = attrs.$attr, 8448 match, 8449 className; 8450 8451 switch (nodeType) { 8452 case NODE_TYPE_ELEMENT: /* Element */ 8453 // use the node name: <directive> 8454 addDirective(directives, 8455 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 8456 8457 // iterate over the attributes 8458 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 8459 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 8460 var attrStartName = false; 8461 var attrEndName = false; 8462 8463 attr = nAttrs[j]; 8464 name = attr.name; 8465 value = trim(attr.value); 8466 8467 // support ngAttr attribute binding 8468 ngAttrName = directiveNormalize(name); 8469 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 8470 name = name.replace(PREFIX_REGEXP, '') 8471 .substr(8).replace(/_(.)/g, function(match, letter) { 8472 return letter.toUpperCase(); 8473 }); 8474 } 8475 8476 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); 8477 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { 8478 attrStartName = name; 8479 attrEndName = name.substr(0, name.length - 5) + 'end'; 8480 name = name.substr(0, name.length - 6); 8481 } 8482 8483 nName = directiveNormalize(name.toLowerCase()); 8484 attrsMap[nName] = name; 8485 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 8486 attrs[nName] = value; 8487 if (getBooleanAttrName(node, nName)) { 8488 attrs[nName] = true; // presence means true 8489 } 8490 } 8491 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 8492 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 8493 attrEndName); 8494 } 8495 8496 // use class as directive 8497 className = node.className; 8498 if (isObject(className)) { 8499 // Maybe SVGAnimatedString 8500 className = className.animVal; 8501 } 8502 if (isString(className) && className !== '') { 8503 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 8504 nName = directiveNormalize(match[2]); 8505 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 8506 attrs[nName] = trim(match[3]); 8507 } 8508 className = className.substr(match.index + match[0].length); 8509 } 8510 } 8511 break; 8512 case NODE_TYPE_TEXT: /* Text Node */ 8513 if (msie === 11) { 8514 // Workaround for #11781 8515 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 8516 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 8517 node.parentNode.removeChild(node.nextSibling); 8518 } 8519 } 8520 addTextInterpolateDirective(directives, node.nodeValue); 8521 break; 8522 case NODE_TYPE_COMMENT: /* Comment */ 8523 try { 8524 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 8525 if (match) { 8526 nName = directiveNormalize(match[1]); 8527 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 8528 attrs[nName] = trim(match[2]); 8529 } 8530 } 8531 } catch (e) { 8532 // turns out that under some circumstances IE9 throws errors when one attempts to read 8533 // comment's node value. 8534 // Just ignore it and continue. (Can't seem to reproduce in test case.) 8535 } 8536 break; 8537 } 8538 8539 directives.sort(byPriority); 8540 return directives; 8541 } 8542 8543 /** 8544 * Given a node with an directive-start it collects all of the siblings until it finds 8545 * directive-end. 8546 * @param node 8547 * @param attrStart 8548 * @param attrEnd 8549 * @returns {*} 8550 */ 8551 function groupScan(node, attrStart, attrEnd) { 8552 var nodes = []; 8553 var depth = 0; 8554 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 8555 do { 8556 if (!node) { 8557 throw $compileMinErr('uterdir', 8558 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 8559 attrStart, attrEnd); 8560 } 8561 if (node.nodeType == NODE_TYPE_ELEMENT) { 8562 if (node.hasAttribute(attrStart)) depth++; 8563 if (node.hasAttribute(attrEnd)) depth--; 8564 } 8565 nodes.push(node); 8566 node = node.nextSibling; 8567 } while (depth > 0); 8568 } else { 8569 nodes.push(node); 8570 } 8571 8572 return jqLite(nodes); 8573 } 8574 8575 /** 8576 * Wrapper for linking function which converts normal linking function into a grouped 8577 * linking function. 8578 * @param linkFn 8579 * @param attrStart 8580 * @param attrEnd 8581 * @returns {Function} 8582 */ 8583 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 8584 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { 8585 element = groupScan(element[0], attrStart, attrEnd); 8586 return linkFn(scope, element, attrs, controllers, transcludeFn); 8587 }; 8588 } 8589 8590 /** 8591 * A function generator that is used to support both eager and lazy compilation 8592 * linking function. 8593 * @param eager 8594 * @param $compileNodes 8595 * @param transcludeFn 8596 * @param maxPriority 8597 * @param ignoreDirective 8598 * @param previousCompileContext 8599 * @returns {Function} 8600 */ 8601 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 8602 var compiled; 8603 8604 if (eager) { 8605 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 8606 } 8607 return function lazyCompilation() { 8608 if (!compiled) { 8609 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 8610 8611 // Null out all of these references in order to make them eligible for garbage collection 8612 // since this is a potentially long lived closure 8613 $compileNodes = transcludeFn = previousCompileContext = null; 8614 } 8615 return compiled.apply(this, arguments); 8616 }; 8617 } 8618 8619 /** 8620 * Once the directives have been collected, their compile functions are executed. This method 8621 * is responsible for inlining directive templates as well as terminating the application 8622 * of the directives if the terminal directive has been reached. 8623 * 8624 * @param {Array} directives Array of collected directives to execute their compile function. 8625 * this needs to be pre-sorted by priority order. 8626 * @param {Node} compileNode The raw DOM node to apply the compile functions to 8627 * @param {Object} templateAttrs The shared attribute function 8628 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 8629 * scope argument is auto-generated to the new 8630 * child of the transcluded parent scope. 8631 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 8632 * argument has the root jqLite array so that we can replace nodes 8633 * on it. 8634 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 8635 * compiling the transclusion. 8636 * @param {Array.<Function>} preLinkFns 8637 * @param {Array.<Function>} postLinkFns 8638 * @param {Object} previousCompileContext Context used for previous compilation of the current 8639 * node 8640 * @returns {Function} linkFn 8641 */ 8642 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 8643 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 8644 previousCompileContext) { 8645 previousCompileContext = previousCompileContext || {}; 8646 8647 var terminalPriority = -Number.MAX_VALUE, 8648 newScopeDirective = previousCompileContext.newScopeDirective, 8649 controllerDirectives = previousCompileContext.controllerDirectives, 8650 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 8651 templateDirective = previousCompileContext.templateDirective, 8652 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 8653 hasTranscludeDirective = false, 8654 hasTemplate = false, 8655 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 8656 $compileNode = templateAttrs.$$element = jqLite(compileNode), 8657 directive, 8658 directiveName, 8659 $template, 8660 replaceDirective = originalReplaceDirective, 8661 childTranscludeFn = transcludeFn, 8662 linkFn, 8663 didScanForMultipleTransclusion = false, 8664 mightHaveMultipleTransclusionError = false, 8665 directiveValue; 8666 8667 // executes all directives on the current element 8668 for (var i = 0, ii = directives.length; i < ii; i++) { 8669 directive = directives[i]; 8670 var attrStart = directive.$$start; 8671 var attrEnd = directive.$$end; 8672 8673 // collect multiblock sections 8674 if (attrStart) { 8675 $compileNode = groupScan(compileNode, attrStart, attrEnd); 8676 } 8677 $template = undefined; 8678 8679 if (terminalPriority > directive.priority) { 8680 break; // prevent further processing of directives 8681 } 8682 8683 if (directiveValue = directive.scope) { 8684 8685 // skip the check for directives with async templates, we'll check the derived sync 8686 // directive when the template arrives 8687 if (!directive.templateUrl) { 8688 if (isObject(directiveValue)) { 8689 // This directive is trying to add an isolated scope. 8690 // Check that there is no scope of any kind already 8691 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 8692 directive, $compileNode); 8693 newIsolateScopeDirective = directive; 8694 } else { 8695 // This directive is trying to add a child scope. 8696 // Check that there is no isolated scope already 8697 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 8698 $compileNode); 8699 } 8700 } 8701 8702 newScopeDirective = newScopeDirective || directive; 8703 } 8704 8705 directiveName = directive.name; 8706 8707 // If we encounter a condition that can result in transclusion on the directive, 8708 // then scan ahead in the remaining directives for others that may cause a multiple 8709 // transclusion error to be thrown during the compilation process. If a matching directive 8710 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 8711 // compile the `transclude` function rather than doing it lazily in order to throw 8712 // exceptions at the correct time 8713 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 8714 || (directive.transclude && !directive.$$tlb))) { 8715 var candidateDirective; 8716 8717 for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) { 8718 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 8719 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 8720 mightHaveMultipleTransclusionError = true; 8721 break; 8722 } 8723 } 8724 8725 didScanForMultipleTransclusion = true; 8726 } 8727 8728 if (!directive.templateUrl && directive.controller) { 8729 directiveValue = directive.controller; 8730 controllerDirectives = controllerDirectives || createMap(); 8731 assertNoDuplicate("'" + directiveName + "' controller", 8732 controllerDirectives[directiveName], directive, $compileNode); 8733 controllerDirectives[directiveName] = directive; 8734 } 8735 8736 if (directiveValue = directive.transclude) { 8737 hasTranscludeDirective = true; 8738 8739 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 8740 // This option should only be used by directives that know how to safely handle element transclusion, 8741 // where the transcluded nodes are added or replaced after linking. 8742 if (!directive.$$tlb) { 8743 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 8744 nonTlbTranscludeDirective = directive; 8745 } 8746 8747 if (directiveValue == 'element') { 8748 hasElementTranscludeDirective = true; 8749 terminalPriority = directive.priority; 8750 $template = $compileNode; 8751 $compileNode = templateAttrs.$$element = 8752 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); 8753 compileNode = $compileNode[0]; 8754 replaceWith(jqCollection, sliceArgs($template), compileNode); 8755
8756 // Support: Chrome < 50 8757 // https://github.com/angular/angular.js/issues/14041 8758 8759 // In the versions of V8 prior to Chrome 50, the document fragment that is created 8760 // in the `replaceWith` function is improperly garbage collected despite still 8761 // being referenced by the `parentNode` property of all of the child nodes. By adding 8762 // a reference to the fragment via a different property, we can avoid that incorrect 8763 // behavior. 8764 // TODO: remove this line after Chrome 50 has been released 8765 $template[0].$$parentNode = $template[0].parentNode; 8766 8767 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 8768 replaceDirective && replaceDirective.name, { 8769 // Don't pass in: 8770 // - controllerDirectives - otherwise we'll create duplicates controllers 8771 // - newIsolateScopeDirective or templateDirective - comb
vendor: 22,884 bytes, lines 8771-9296
8771ining templates with 8772 // element transclusion doesn't make sense. 8773 // 8774 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 8775 // on the same element more than once. 8776 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8777 }); 8778 } else { 8779 8780 var slots = createMap(); 8781 8782 $template = jqLite(jqLiteClone(compileNode)).contents(); 8783 8784 if (isObject(directiveValue)) { 8785 8786 // We have transclusion slots, 8787 // collect them up, compile them and store their transclusion functions 8788 $template = []; 8789 8790 var slotMap = createMap(); 8791 var filledSlots = createMap(); 8792 8793 // Parse the element selectors 8794 forEach(directiveValue, function(elementSelector, slotName) { 8795 // If an element selector starts with a ? then it is optional 8796 var optional = (elementSelector.charAt(0) === '?'); 8797 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 8798 8799 slotMap[elementSelector] = slotName; 8800 8801 // We explicitly assign `null` since this implies that a slot was defined but not filled. 8802 // Later when calling boundTransclusion functions with a slot name we only error if the 8803 // slot is `undefined` 8804 slots[slotName] = null; 8805 8806 // filledSlots contains `true` for all slots that are either optional or have been 8807 // filled. This is used to check that we have not missed any required slots 8808 filledSlots[slotName] = optional; 8809 }); 8810 8811 // Add the matching elements into their slot 8812 forEach($compileNode.contents(), function(node) { 8813 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 8814 if (slotName) { 8815 filledSlots[slotName] = true; 8816 slots[slotName] = slots[slotName] || []; 8817 slots[slotName].push(node); 8818 } else { 8819 $template.push(node); 8820 } 8821 }); 8822 8823 // Check for required slots that were not filled 8824 forEach(filledSlots, function(filled, slotName) { 8825 if (!filled) { 8826 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 8827 } 8828 }); 8829 8830 for (var slotName in slots) { 8831 if (slots[slotName]) { 8832 // Only define a transclusion function if the slot was filled 8833 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); 8834 } 8835 } 8836 } 8837 8838 $compileNode.empty(); // clear contents 8839 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 8840 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 8841 childTranscludeFn.$$slots = slots; 8842 } 8843 } 8844 8845 if (directive.template) { 8846 hasTemplate = true; 8847 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8848 templateDirective = directive; 8849 8850 directiveValue = (isFunction(directive.template)) 8851 ? directive.template($compileNode, templateAttrs) 8852 : directive.template; 8853 8854 directiveValue = denormalizeTemplate(directiveValue); 8855 8856 if (directive.replace) { 8857 replaceDirective = directive; 8858 if (jqLiteIsTextNode(directiveValue)) { 8859 $template = []; 8860 } else { 8861 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 8862 } 8863 compileNode = $template[0]; 8864 8865 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8866 throw $compileMinErr('tplrt', 8867 "Template for directive '{0}' must have exactly one root element. {1}", 8868 directiveName, ''); 8869 } 8870 8871 replaceWith(jqCollection, $compileNode, compileNode); 8872 8873 var newTemplateAttrs = {$attr: {}}; 8874 8875 // combine directives from the original node and from the template: 8876 // - take the array of directives for this element 8877 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 8878 // - collect directives from the template and sort them by priority 8879 // - combine directives as: processed + template + unprocessed 8880 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 8881 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 8882 8883 if (newIsolateScopeDirective || newScopeDirective) { 8884 // The original directive caused the current element to be replaced but this element 8885 // also needs to have a new scope, so we need to tell the template directives 8886 // that they would need to get their scope from further up, if they require transclusion 8887 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 8888 } 8889 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 8890 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 8891 8892 ii = directives.length; 8893 } else { 8894 $compileNode.html(directiveValue); 8895 } 8896 } 8897 8898 if (directive.templateUrl) { 8899 hasTemplate = true; 8900 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8901 templateDirective = directive; 8902 8903 if (directive.replace) { 8904 replaceDirective = directive; 8905 } 8906 8907 /* jshint -W021 */ 8908 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 8909 /* jshint +W021 */ 8910 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 8911 controllerDirectives: controllerDirectives, 8912 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 8913 newIsolateScopeDirective: newIsolateScopeDirective, 8914 templateDirective: templateDirective, 8915 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8916 }); 8917 ii = directives.length; 8918 } else if (directive.compile) { 8919 try { 8920 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 8921 if (isFunction(linkFn)) { 8922 addLinkFns(null, linkFn, attrStart, attrEnd); 8923 } else if (linkFn) { 8924 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 8925 } 8926 } catch (e) { 8927 $exceptionHandler(e, startingTag($compileNode)); 8928 } 8929 } 8930 8931 if (directive.terminal) { 8932 nodeLinkFn.terminal = true; 8933 terminalPriority = Math.max(terminalPriority, directive.priority); 8934 } 8935 8936 } 8937 8938 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 8939 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 8940 nodeLinkFn.templateOnThisElement = hasTemplate; 8941 nodeLinkFn.transclude = childTranscludeFn; 8942 8943 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 8944 8945 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 8946 return nodeLinkFn; 8947 8948 //////////////////// 8949 8950 function addLinkFns(pre, post, attrStart, attrEnd) { 8951 if (pre) { 8952 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 8953 pre.require = directive.require; 8954 pre.directiveName = directiveName; 8955 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8956 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 8957 } 8958 preLinkFns.push(pre); 8959 } 8960 if (post) { 8961 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 8962 post.require = directive.require; 8963 post.directiveName = directiveName; 8964 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8965 post = cloneAndAnnotateFn(post, {isolateScope: true}); 8966 } 8967 postLinkFns.push(post); 8968 } 8969 } 8970 8971 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 8972 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 8973 attrs, scopeBindingInfo; 8974 8975 if (compileNode === linkNode) { 8976 attrs = templateAttrs; 8977 $element = templateAttrs.$$element; 8978 } else { 8979 $element = jqLite(linkNode); 8980 attrs = new Attributes($element, templateAttrs); 8981 } 8982 8983 controllerScope = scope; 8984 if (newIsolateScopeDirective) { 8985 isolateScope = scope.$new(true); 8986 } else if (newScopeDirective) { 8987 controllerScope = scope.$parent; 8988 } 8989 8990 if (boundTranscludeFn) { 8991 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8992 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8993 transcludeFn = controllersBoundTransclude; 8994 transcludeFn.$$boundTransclude = boundTranscludeFn; 8995 // expose the slots on the `$transclude` function 8996 transcludeFn.isSlotFilled = function(slotName) { 8997 return !!boundTranscludeFn.$$slots[slotName]; 8998 }; 8999 } 9000 9001 if (controllerDirectives) { 9002 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); 9003 } 9004 9005 if (newIsolateScopeDirective) { 9006 // Initialize isolate scope bindings for new isolate scope directive. 9007 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 9008 templateDirective === newIsolateScopeDirective.$$originalDirective))); 9009 compile.$$addScopeClass($element, true); 9010 isolateScope.$$isolateBindings = 9011 newIsolateScopeDirective.$$isolateBindings; 9012 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, 9013 isolateScope.$$isolateBindings, 9014 newIsolateScopeDirective); 9015 if (scopeBindingInfo.removeWatches) { 9016 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); 9017 } 9018 } 9019 9020 // Initialize bindToController bindings 9021 for (var name in elementControllers) { 9022 var controllerDirective = controllerDirectives[name]; 9023 var controller = elementControllers[name]; 9024 var bindings = controllerDirective.$$bindings.bindToController; 9025 9026 if (controller.identifier && bindings) { 9027 controller.bindingInfo = 9028 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 9029 } else { 9030 controller.bindingInfo = {}; 9031 } 9032 9033 var controllerResult = controller(); 9034 if (controllerResult !== controller.instance) { 9035 // If the controller constructor has a return value, overwrite the instance 9036 // from setupControllers 9037 controller.instance = controllerResult; 9038 $element.data('$' + controllerDirective.name + 'Controller', controllerResult); 9039 controller.bindingInfo.removeWatches && controller.bindingInfo.removeWatches(); 9040 controller.bindingInfo = 9041 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 9042 } 9043 } 9044 9045 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy 9046 forEach(controllerDirectives, function(controllerDirective, name) { 9047 var require = controllerDirective.require; 9048 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { 9049 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); 9050 } 9051 }); 9052 9053 // Handle the init and destroy lifecycle hooks on all controllers that have them 9054 forEach(elementControllers, function(controller) { 9055 var controllerInstance = controller.instance; 9056 if (isFunction(controllerInstance.$onChanges)) { 9057 controllerInstance.$onChanges(controller.bindingInfo.initialChanges); 9058 } 9059 if (isFunction(controllerInstance.$onInit)) { 9060 controllerInstance.$onInit(); 9061 } 9062 if (isFunction(controllerInstance.$onDestroy)) { 9063 controllerScope.$on('$destroy', function callOnDestroyHook() { 9064 controllerInstance.$onDestroy(); 9065 }); 9066 } 9067 }); 9068 9069 // PRELINKING 9070 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 9071 linkFn = preLinkFns[i]; 9072 invokeLinkFn(linkFn, 9073 linkFn.isolateScope ? isolateScope : scope, 9074 $element, 9075 attrs, 9076 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 9077 transcludeFn 9078 ); 9079 } 9080 9081 // RECURSION 9082 // We only pass the isolate scope, if the isolate directive has a template, 9083 // otherwise the child elements do not belong to the isolate directive. 9084 var scopeToChild = scope; 9085 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 9086 scopeToChild = isolateScope; 9087 } 9088 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 9089 9090 // POSTLINKING 9091 for (i = postLinkFns.length - 1; i >= 0; i--) { 9092 linkFn = postLinkFns[i]; 9093 invokeLinkFn(linkFn, 9094 linkFn.isolateScope ? isolateScope : scope, 9095 $element, 9096 attrs, 9097 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 9098 transcludeFn 9099 ); 9100 } 9101 9102 // Trigger $postLink lifecycle hooks 9103 forEach(elementControllers, function(controller) { 9104 var controllerInstance = controller.instance; 9105 if (isFunction(controllerInstance.$postLink)) { 9106 controllerInstance.$postLink(); 9107 } 9108 }); 9109 9110 // This is the function that is injected as `$transclude`. 9111 // Note: all arguments are optional! 9112 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 9113 var transcludeControllers; 9114 // No scope passed in: 9115 if (!isScope(scope)) { 9116 slotName = futureParentElement; 9117 futureParentElement = cloneAttachFn; 9118 cloneAttachFn = scope; 9119 scope = undefined; 9120 } 9121 9122 if (hasElementTranscludeDirective) { 9123 transcludeControllers = elementControllers; 9124 } 9125 if (!futureParentElement) { 9126 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 9127 } 9128 if (slotName) { 9129 // slotTranscludeFn can be one of three things: 9130 // * a transclude function - a filled slot 9131 // * `null` - an optional slot that was not filled 9132 // * `undefined` - a slot that was not declared (i.e. invalid) 9133 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 9134 if (slotTranscludeFn) { 9135 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 9136 } else if (isUndefined(slotTranscludeFn)) { 9137 throw $compileMinErr('noslot', 9138 'No parent directive that requires a transclusion with slot name "{0}". ' + 9139 'Element: {1}', 9140 slotName, startingTag($element)); 9141 } 9142 } else { 9143 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 9144 } 9145 } 9146 } 9147 } 9148 9149 function getControllers(directiveName, require, $element, elementControllers) { 9150 var value; 9151 9152 if (isString(require)) { 9153 var match = require.match(REQUIRE_PREFIX_REGEXP); 9154 var name = require.substring(match[0].length); 9155 var inheritType = match[1] || match[3]; 9156 var optional = match[2] === '?'; 9157 9158 //If only parents then start at the parent element 9159 if (inheritType === '^^') { 9160 $element = $element.parent(); 9161 //Otherwise attempt getting the controller from elementControllers in case 9162 //the element is transcluded (and has no data) and to avoid .data if possible 9163 } else { 9164 value = elementControllers && elementControllers[name]; 9165 value = value && value.instance; 9166 } 9167 9168 if (!value) { 9169 var dataName = '$' + name + 'Controller'; 9170 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 9171 } 9172 9173 if (!value && !optional) { 9174 throw $compileMinErr('ctreq', 9175 "Controller '{0}', required by directive '{1}', can't be found!", 9176 name, directiveName); 9177 } 9178 } else if (isArray(require)) { 9179 value = []; 9180 for (var i = 0, ii = require.length; i < ii; i++) { 9181 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 9182 } 9183 } else if (isObject(require)) { 9184 value = {}; 9185 forEach(require, function(controller, property) { 9186 value[property] = getControllers(directiveName, controller, $element, elementControllers); 9187 }); 9188 } 9189 9190 return value || null; 9191 } 9192 9193 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { 9194 var elementControllers = createMap(); 9195 for (var controllerKey in controllerDirectives) { 9196 var directive = controllerDirectives[controllerKey]; 9197 var locals = { 9198 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 9199 $element: $element, 9200 $attrs: attrs, 9201 $transclude: transcludeFn 9202 }; 9203 9204 var controller = directive.controller; 9205 if (controller == '@') { 9206 controller = attrs[directive.name]; 9207 } 9208 9209 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 9210 9211 // For directives with element transclusion the element is a comment. 9212 // In this case .data will not attach any data. 9213 // Instead, we save the controllers for the element in a local hash and attach to .data 9214 // later, once we have the actual element. 9215 elementControllers[directive.name] = controllerInstance; 9216 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 9217 } 9218 return elementControllers; 9219 } 9220 9221 // Depending upon the context in which a directive finds itself it might need to have a new isolated 9222 // or child scope created. For instance: 9223 // * if the directive has been pulled into a template because another directive with a higher priority 9224 // asked for element transclusion 9225 // * if the directive itself asks for transclusion but it is at the root of a template and the original 9226 // element was replaced. See https://github.com/angular/angular.js/issues/12936 9227 function markDirectiveScope(directives, isolateScope, newScope) { 9228 for (var j = 0, jj = directives.length; j < jj; j++) { 9229 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 9230 } 9231 } 9232 9233 /** 9234 * looks up the directive and decorates it with exception handling and proper parameters. We 9235 * call this the boundDirective. 9236 * 9237 * @param {string} name name of the directive to look up. 9238 * @param {string} location The directive must be found in specific format. 9239 * String containing any of theses characters: 9240 * 9241 * * `E`: element name 9242 * * `A': attribute 9243 * * `C`: class 9244 * * `M`: comment 9245 * @returns {boolean} true if directive was added. 9246 */ 9247 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 9248 endAttrName) { 9249 if (name === ignoreDirective) return null; 9250 var match = null; 9251 if (hasDirectives.hasOwnProperty(name)) { 9252 for (var directive, directives = $injector.get(name + Suffix), 9253 i = 0, ii = directives.length; i < ii; i++) { 9254 try { 9255 directive = directives[i]; 9256 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 9257 directive.restrict.indexOf(location) != -1) { 9258 if (startAttrName) { 9259 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 9260 } 9261 if (!directive.$$bindings) { 9262 var bindings = directive.$$bindings = 9263 parseDirectiveBindings(directive, directive.name); 9264 if (isObject(bindings.isolateScope)) { 9265 directive.$$isolateBindings = bindings.isolateScope; 9266 } 9267 } 9268 tDirectives.push(directive); 9269 match = directive; 9270 } 9271 } catch (e) { $exceptionHandler(e); } 9272 } 9273 } 9274 return match; 9275 } 9276 9277 9278 /** 9279 * looks up the directive and returns true if it is a multi-element directive, 9280 * and therefore requires DOM nodes between -start and -end markers to be grouped 9281 * together. 9282 * 9283 * @param {string} name name of the directive to look up. 9284 * @returns true if directive was registered as multi-element. 9285 */ 9286 function directiveIsMultiElement(name) { 9287 if (hasDirectives.hasOwnProperty(name)) { 9288 for (var directive, directives = $injector.get(name + Suffix), 9289 i = 0, ii = directives.length; i < ii; i++) { 9290 directive = directives[i]; 9291 if (directive.multiElement) { 9292 return true; 9293 } 9294 } 9295 } 9296 return false;
9297 } 9298 9299 /** 9300 * When the element is replaced with HTML template then the new attributes 9301 * on the template need to be merged with the existing attributes in the DOM. 9302 * The desired effect is to have both of the attributes present. 9303 * 9304 * @param {object} dst destination attributes (original DOM) 9305 * @param {object} src source attributes (from the directive template) 9306 */ 9307 function mergeTemplateAttributes(dst, src) { 9308 var srcAttr = src.$attr, 9309 dstAttr = dst.$attr, 9310 $element = dst.$$element; 9311 9312 // reapply the old attributes to the new element 9313 forEach(dst, function(value, key) { 9314 if (key.charAt(0) != '$') { 9315 if (src[key] && src[key] !== value) { 9316 value += (key === 'style' ? ';' : ' ') + src[key]; 9317 } 9318 dst.$set(key, value, true, srcAttr[key]); 9319 } 9320 }); 9321 9322 // copy the new attributes on the old attrs object 9323 forEach(src, function(value, key) { 9324 if (key == 'class') { 9325 safeAddClass($element, value); 9326 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 9327 } else if (key == 'style') { 9328 $element.attr('style', $element.attr('style') + ';' + value); 9329 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 9330 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 9331 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 9332 // have an attribute like "has-own-property" or "data-has-own-property", etc. 9333 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 9334 dst[key] = value; 9335 dstAttr[key] = srcAttr[key]; 9336 } 9337 }); 9338 } 9339 9340 9341 function compileTemplateUrl(directives, $compileNode, tAttrs, 9342 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 9343 var linkQueue = [], 9344 afterTemplateNodeLinkFn, 9345 afterTemplateChildLinkFn, 9346 beforeTemplateCompileNode = $compileNode[0], 9347 origAsyncDirective = directives.shift(), 9348 derivedSyncDirective = inherit(origAsyncDirective, { 9349 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 9350 }), 9351 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 9352 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 9353 : origAsyncDirective.templateUrl, 9354 templateNamespace = origAsyncDirective.templateNamespace; 9355 9356 $compileNode.empty(); 9357 9358 $templateRequest(templateUrl) 9359 .then(function(content) { 9360 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 9361 9362 content = denormalizeTemplate(content); 9363 9364 if (origAsyncDirective.replace) { 9365 if (jqLiteIsTextNode(content)) { 9366 $template = []; 9367 } else { 9368 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 9369 } 9370 compileNode = $template[0]; 9371 9372 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 9373 throw $compileMinErr('tplrt', 9374 "Template for directive '{0}' must have exactly one root element. {1}", 9375 origAsyncDirective.name, templateUrl); 9376 } 9377 9378 tempTemplateAttrs = {$attr: {}}; 9379 replaceWith($rootElement, $compileNode, compileNode); 9380 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 9381 9382 if (isObject(origAsyncDirective.scope)) { 9383 // the original directive that caused the template to be loaded async required 9384 // an isolate scope 9385 markDirectiveScope(templateDirectives, true); 9386 } 9387 directives = templateDirectives.concat(directives); 9388 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 9389 } else { 9390 compileNode = beforeTemplateCompileNode; 9391 $compileNode.html(content); 9392 } 9393 9394 directives.unshift(derivedSyncDirective); 9395 9396 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 9397 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 9398 previousCompileContext);
vendor: 11,992 bytes, lines 9399-9692
9399 forEach($rootElement, function(node, i) { 9400 if (node == compileNode) { 9401 $rootElement[i] = $compileNode[0]; 9402 } 9403 }); 9404 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 9405 9406 while (linkQueue.length) { 9407 var scope = linkQueue.shift(), 9408 beforeTemplateLinkNode = linkQueue.shift(), 9409 linkRootElement = linkQueue.shift(), 9410 boundTranscludeFn = linkQueue.shift(), 9411 linkNode = $compileNode[0]; 9412 9413 if (scope.$$destroyed) continue; 9414 9415 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 9416 var oldClasses = beforeTemplateLinkNode.className; 9417 9418 if (!(previousCompileContext.hasElementTranscludeDirective && 9419 origAsyncDirective.replace)) { 9420 // it was cloned therefore we have to clone as well. 9421 linkNode = jqLiteClone(compileNode); 9422 } 9423 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 9424 9425 // Copy in CSS classes from original node 9426 safeAddClass(jqLite(linkNode), oldClasses); 9427 } 9428 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 9429 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 9430 } else { 9431 childBoundTranscludeFn = boundTranscludeFn; 9432 } 9433 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 9434 childBoundTranscludeFn); 9435 } 9436 linkQueue = null; 9437 }); 9438 9439 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 9440 var childBoundTranscludeFn = boundTranscludeFn; 9441 if (scope.$$destroyed) return; 9442 if (linkQueue) { 9443 linkQueue.push(scope, 9444 node, 9445 rootElement, 9446 childBoundTranscludeFn); 9447 } else { 9448 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 9449 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 9450 } 9451 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 9452 } 9453 }; 9454 } 9455 9456 9457 /** 9458 * Sorting function for bound directives. 9459 */ 9460 function byPriority(a, b) { 9461 var diff = b.priority - a.priority; 9462 if (diff !== 0) return diff; 9463 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 9464 return a.index - b.index; 9465 } 9466 9467 function assertNoDuplicate(what, previousDirective, directive, element) { 9468 9469 function wrapModuleNameIfDefined(moduleName) { 9470 return moduleName ? 9471 (' (module: ' + moduleName + ')') : 9472 ''; 9473 } 9474 9475 if (previousDirective) { 9476 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 9477 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 9478 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 9479 } 9480 } 9481 9482 9483 function addTextInterpolateDirective(directives, text) { 9484 var interpolateFn = $interpolate(text, true); 9485 if (interpolateFn) { 9486 directives.push({ 9487 priority: 0, 9488 compile: function textInterpolateCompileFn(templateNode) { 9489 var templateNodeParent = templateNode.parent(), 9490 hasCompileParent = !!templateNodeParent.length; 9491 9492 // When transcluding a template that has bindings in the root 9493 // we don't have a parent and thus need to add the class during linking fn. 9494 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 9495 9496 return function textInterpolateLinkFn(scope, node) { 9497 var parent = node.parent(); 9498 if (!hasCompileParent) compile.$$addBindingClass(parent); 9499 compile.$$addBindingInfo(parent, interpolateFn.expressions); 9500 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 9501 node[0].nodeValue = value; 9502 }); 9503 }; 9504 } 9505 }); 9506 } 9507 } 9508 9509 9510 function wrapTemplate(type, template) { 9511 type = lowercase(type || 'html'); 9512 switch (type) { 9513 case 'svg': 9514 case 'math': 9515 var wrapper = window.document.createElement('div'); 9516 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 9517 return wrapper.childNodes[0].childNodes; 9518 default: 9519 return template; 9520 } 9521 } 9522 9523 9524 function getTrustedContext(node, attrNormalizedName) { 9525 if (attrNormalizedName == "srcdoc") { 9526 return $sce.HTML; 9527 } 9528 var tag = nodeName_(node); 9529 // maction[xlink:href] can source SVG. It's not limited to <maction>. 9530 if (attrNormalizedName == "xlinkHref" || 9531 (tag == "form" && attrNormalizedName == "action") || 9532 (tag != "img" && (attrNormalizedName == "src" || 9533 attrNormalizedName == "ngSrc"))) { 9534 return $sce.RESOURCE_URL; 9535 } 9536 } 9537 9538 9539 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 9540 var trustedContext = getTrustedContext(node, name); 9541 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 9542 9543 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 9544 9545 // no interpolation found -> ignore 9546 if (!interpolateFn) return; 9547 9548 9549 if (name === "multiple" && nodeName_(node) === "select") { 9550 throw $compileMinErr("selmulti", 9551 "Binding to the 'multiple' attribute is not supported. Element: {0}", 9552 startingTag(node)); 9553 } 9554 9555 directives.push({ 9556 priority: 100, 9557 compile: function() { 9558 return { 9559 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 9560 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 9561 9562 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 9563 throw $compileMinErr('nodomevents', 9564 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 9565 "ng- versions (such as ng-click instead of onclick) instead."); 9566 } 9567 9568 // If the attribute has changed since last $interpolate()ed 9569 var newValue = attr[name]; 9570 if (newValue !== value) { 9571 // we need to interpolate again since the attribute value has been updated 9572 // (e.g. by another directive's compile function) 9573 // ensure unset/empty values make interpolateFn falsy 9574 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 9575 value = newValue; 9576 } 9577 9578 // if attribute was updated so that there is no interpolation going on we don't want to 9579 // register any observers 9580 if (!interpolateFn) return; 9581 9582 // initialize attr object so that it's ready in case we need the value for isolate 9583 // scope initialization, otherwise the value would not be available from isolate 9584 // directive's linking fn during linking phase 9585 attr[name] = interpolateFn(scope); 9586 9587 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 9588 (attr.$$observers && attr.$$observers[name].$$scope || scope). 9589 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 9590 //special case for class attribute addition + removal 9591 //so that class changes can tap into the animation 9592 //hooks provided by the $animate service. Be sure to 9593 //skip animations when the first digest occurs (when 9594 //both the new and the old values are the same) since 9595 //the CSS classes are the non-interpolated values 9596 if (name === 'class' && newValue != oldValue) { 9597 attr.$updateClass(newValue, oldValue); 9598 } else { 9599 attr.$set(name, newValue); 9600 } 9601 }); 9602 } 9603 }; 9604 } 9605 }); 9606 } 9607 9608 9609 /** 9610 * This is a special jqLite.replaceWith, which can replace items which 9611 * have no parents, provided that the containing jqLite collection is provided. 9612 * 9613 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 9614 * in the root of the tree. 9615 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 9616 * the shell, but replace its DOM node reference. 9617 * @param {Node} newNode The new DOM node. 9618 */ 9619 function replaceWith($rootElement, elementsToRemove, newNode) { 9620 var firstElementToRemove = elementsToRemove[0], 9621 removeCount = elementsToRemove.length, 9622 parent = firstElementToRemove.parentNode, 9623 i, ii; 9624 9625 if ($rootElement) { 9626 for (i = 0, ii = $rootElement.length; i < ii; i++) { 9627 if ($rootElement[i] == firstElementToRemove) { 9628 $rootElement[i++] = newNode; 9629 for (var j = i, j2 = j + removeCount - 1, 9630 jj = $rootElement.length; 9631 j < jj; j++, j2++) { 9632 if (j2 < jj) { 9633 $rootElement[j] = $rootElement[j2]; 9634 } else { 9635 delete $rootElement[j]; 9636 } 9637 } 9638 $rootElement.length -= removeCount - 1; 9639 9640 // If the replaced element is also the jQuery .context then replace it 9641 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 9642 // http://api.jquery.com/context/ 9643 if ($rootElement.context === firstElementToRemove) { 9644 $rootElement.context = newNode; 9645 } 9646 break; 9647 } 9648 } 9649 } 9650 9651 if (parent) { 9652 parent.replaceChild(newNode, firstElementToRemove); 9653 } 9654 9655 // Append all the `elementsToRemove` to a fragment. This will... 9656 // - remove them from the DOM 9657 // - allow them to still be traversed with .nextSibling 9658 // - allow a single fragment.qSA to fetch all elements being removed 9659 var fragment = window.document.createDocumentFragment(); 9660 for (i = 0; i < removeCount; i++) { 9661 fragment.appendChild(elementsToRemove[i]); 9662 } 9663 9664 if (jqLite.hasData(firstElementToRemove)) { 9665 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 9666 // data here because there's no public interface in jQuery to do that and copying over 9667 // event listeners (which is the main use of private data) wouldn't work anyway. 9668 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 9669 9670 // Remove $destroy event listeners from `firstElementToRemove` 9671 jqLite(firstElementToRemove).off('$destroy'); 9672 } 9673 9674 // Cleanup any data/listeners on the elements and children. 9675 // This includes invoking the $destroy event on any elements with listeners. 9676 jqLite.cleanData(fragment.querySelectorAll('*')); 9677 9678 // Update the jqLite collection to only contain the `newNode` 9679 for (i = 1; i < removeCount; i++) { 9680 delete elementsToRemove[i]; 9681 } 9682 elementsToRemove[0] = newNode; 9683 elementsToRemove.length = 1; 9684 } 9685 9686 9687 function cloneAndAnnotateFn(fn, annotation) { 9688 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 9689 } 9690 9691 9692 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
vendor: 5,366 bytes, lines 9693-9813
9693 try { 9694 linkFn(scope, $element, attrs, controllers, transcludeFn); 9695 } catch (e) { 9696 $exceptionHandler(e, startingTag($element)); 9697 } 9698 } 9699 9700 9701 // Set up $watches for isolate scope and controller bindings. This process 9702 // only occurs for isolate scopes and new scopes with controllerAs. 9703 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 9704 var removeWatchCollection = []; 9705 var initialChanges = {}; 9706 var changes; 9707 forEach(bindings, function initializeBinding(definition, scopeName) { 9708 var attrName = definition.attrName, 9709 optional = definition.optional, 9710 mode = definition.mode, // @, =, or & 9711 lastValue, 9712 parentGet, parentSet, compare, removeWatch; 9713 9714 switch (mode) { 9715 9716 case '@': 9717 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 9718 destination[scopeName] = attrs[attrName] = void 0; 9719 } 9720 attrs.$observe(attrName, function(value) { 9721 if (isString(value) || isBoolean(value)) { 9722 var oldValue = destination[scopeName]; 9723 recordChanges(scopeName, value, oldValue); 9724 destination[scopeName] = value; 9725 } 9726 }); 9727 attrs.$$observers[attrName].$$scope = scope; 9728 lastValue = attrs[attrName]; 9729 if (isString(lastValue)) { 9730 // If the attribute has been provided then we trigger an interpolation to ensure 9731 // the value is there for use in the link fn 9732 destination[scopeName] = $interpolate(lastValue)(scope); 9733 } else if (isBoolean(lastValue)) { 9734 // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted 9735 // the value to boolean rather than a string, so we special case this situation 9736 destination[scopeName] = lastValue; 9737 } 9738 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 9739 break; 9740 9741 case '=': 9742 if (!hasOwnProperty.call(attrs, attrName)) { 9743 if (optional) break; 9744 attrs[attrName] = void 0; 9745 } 9746 if (optional && !attrs[attrName]) break; 9747 9748 parentGet = $parse(attrs[attrName]); 9749 if (parentGet.literal) { 9750 compare = equals; 9751 } else { 9752 compare = function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }; 9753 } 9754 parentSet = parentGet.assign || function() { 9755 // reset the change, or we will throw this exception on every $digest 9756 lastValue = destination[scopeName] = parentGet(scope); 9757 throw $compileMinErr('nonassign', 9758 "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!", 9759 attrs[attrName], attrName, directive.name); 9760 }; 9761 lastValue = destination[scopeName] = parentGet(scope); 9762 var parentValueWatch = function parentValueWatch(parentValue) { 9763 if (!compare(parentValue, destination[scopeName])) { 9764 // we are out of sync and need to copy 9765 if (!compare(parentValue, lastValue)) { 9766 // parent changed and it has precedence 9767 destination[scopeName] = parentValue; 9768 } else { 9769 // if the parent can be assigned then do so 9770 parentSet(scope, parentValue = destination[scopeName]); 9771 } 9772 } 9773 return lastValue = parentValue; 9774 }; 9775 parentValueWatch.$stateful = true; 9776 if (definition.collection) { 9777 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 9778 } else { 9779 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 9780 } 9781 removeWatchCollection.push(removeWatch); 9782 break; 9783 9784 case '<': 9785 if (!hasOwnProperty.call(attrs, attrName)) { 9786 if (optional) break; 9787 attrs[attrName] = void 0; 9788 } 9789 if (optional && !attrs[attrName]) break; 9790 9791 parentGet = $parse(attrs[attrName]); 9792 9793 destination[scopeName] = parentGet(scope); 9794 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 9795 9796 removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) { 9797 if (newValue === oldValue) { 9798 // If the new and old values are identical then this is the first time the watch has been triggered 9799 // So instead we use the current value on the destination as the old value 9800 oldValue = destination[scopeName]; 9801 } 9802 recordChanges(scopeName, newValue, oldValue); 9803 destination[scopeName] = newValue; 9804 }, parentGet.literal); 9805 9806 removeWatchCollection.push(removeWatch); 9807 break; 9808 9809 case '&': 9810 // Don't assign Object.prototype method to scope 9811 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 9812 9813 // Don't assign noop to destination if expression is not val
vendor: 11,231 bytes, lines 9813-10161
9813id 9814 if (parentGet === noop && optional) break; 9815 9816 destination[scopeName] = function(locals) { 9817 return parentGet(scope, locals); 9818 }; 9819 break; 9820 } 9821 }); 9822 9823 function recordChanges(key, currentValue, previousValue) { 9824 if (isFunction(destination.$onChanges) && currentValue !== previousValue) { 9825 // If we have not already scheduled the top level onChangesQueue handler then do so now 9826 if (!onChangesQueue) { 9827 scope.$$postDigest(flushOnChangesQueue); 9828 onChangesQueue = []; 9829 } 9830 // If we have not already queued a trigger of onChanges for this controller then do so now 9831 if (!changes) { 9832 changes = {}; 9833 onChangesQueue.push(triggerOnChangesHook); 9834 } 9835 // If the has been a change on this property already then we need to reuse the previous value 9836 if (changes[key]) { 9837 previousValue = changes[key].previousValue; 9838 } 9839 // Store this change 9840 changes[key] = new SimpleChange(previousValue, currentValue); 9841 } 9842 } 9843 9844 function triggerOnChangesHook() { 9845 destination.$onChanges(changes); 9846 // Now clear the changes so that we schedule onChanges when more changes arrive 9847 changes = undefined; 9848 } 9849 9850 return { 9851 initialChanges: initialChanges, 9852 removeWatches: removeWatchCollection.length && function removeWatches() { 9853 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 9854 removeWatchCollection[i](); 9855 } 9856 } 9857 }; 9858 } 9859 }]; 9860} 9861 9862function SimpleChange(previous, current) { 9863 this.previousValue = previous; 9864 this.currentValue = current; 9865} 9866SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; 9867 9868 9869var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 9870/** 9871 * Converts all accepted directives format into proper directive name. 9872 * @param name Name to normalize 9873 */ 9874function directiveNormalize(name) { 9875 return camelCase(name.replace(PREFIX_REGEXP, '')); 9876} 9877 9878/** 9879 * @ngdoc type 9880 * @name $compile.directive.Attributes 9881 * 9882 * @description 9883 * A shared object between directive compile / linking functions which contains normalized DOM 9884 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 9885 * needed since all of these are treated as equivalent in Angular: 9886 * 9887 * ``` 9888 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 9889 * ``` 9890 */ 9891 9892/** 9893 * @ngdoc property 9894 * @name $compile.directive.Attributes#$attr 9895 * 9896 * @description 9897 * A map of DOM element attribute names to the normalized name. This is 9898 * needed to do reverse lookup from normalized name back to actual name. 9899 */ 9900 9901 9902/** 9903 * @ngdoc method 9904 * @name $compile.directive.Attributes#$set 9905 * @kind function 9906 * 9907 * @description 9908 * Set DOM element attribute value. 9909 * 9910 * 9911 * @param {string} name Normalized element attribute name of the property to modify. The name is 9912 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 9913 * property to the original name. 9914 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 9915 */ 9916 9917 9918 9919/** 9920 * Closure compiler type information 9921 */ 9922 9923function nodesetLinkingFn( 9924 /* angular.Scope */ scope, 9925 /* NodeList */ nodeList, 9926 /* Element */ rootElement, 9927 /* function(Function) */ boundTranscludeFn 9928) {} 9929 9930function directiveLinkingFn( 9931 /* nodesetLinkingFn */ nodesetLinkingFn, 9932 /* angular.Scope */ scope, 9933 /* Node */ node, 9934 /* Element */ rootElement, 9935 /* function(Function) */ boundTranscludeFn 9936) {} 9937 9938function tokenDifference(str1, str2) { 9939 var values = '', 9940 tokens1 = str1.split(/\s+/), 9941 tokens2 = str2.split(/\s+/); 9942 9943 outer: 9944 for (var i = 0; i < tokens1.length; i++) { 9945 var token = tokens1[i]; 9946 for (var j = 0; j < tokens2.length; j++) { 9947 if (token == tokens2[j]) continue outer; 9948 } 9949 values += (values.length > 0 ? ' ' : '') + token; 9950 } 9951 return values; 9952} 9953 9954function removeComments(jqNodes) { 9955 jqNodes = jqLite(jqNodes); 9956 var i = jqNodes.length; 9957 9958 if (i <= 1) { 9959 return jqNodes; 9960 } 9961 9962 while (i--) { 9963 var node = jqNodes[i]; 9964 if (node.nodeType === NODE_TYPE_COMMENT) { 9965 splice.call(jqNodes, i, 1); 9966 } 9967 } 9968 return jqNodes; 9969} 9970 9971var $controllerMinErr = minErr('$controller'); 9972 9973 9974var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 9975function identifierForController(controller, ident) { 9976 if (ident && isString(ident)) return ident; 9977 if (isString(controller)) { 9978 var match = CNTRL_REG.exec(controller); 9979 if (match) return match[3]; 9980 } 9981} 9982 9983 9984/** 9985 * @ngdoc provider 9986 * @name $controllerProvider 9987 * @description 9988 * The {@link ng.$controller $controller service} is used by Angular to create new 9989 * controllers. 9990 * 9991 * This provider allows controller registration via the 9992 * {@link ng.$controllerProvider#register register} method. 9993 */ 9994function $ControllerProvider() { 9995 var controllers = {}, 9996 globals = false; 9997 9998 /** 9999 * @ngdoc method 10000 * @name $controllerProvider#has 10001 * @param {string} name Controller name to check. 10002 */ 10003 this.has = function(name) { 10004 return controllers.hasOwnProperty(name); 10005 }; 10006 10007 /** 10008 * @ngdoc method 10009 * @name $controllerProvider#register 10010 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 10011 * the names and the values are the constructors. 10012 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 10013 * annotations in the array notation). 10014 */ 10015 this.register = function(name, constructor) { 10016 assertNotHasOwnProperty(name, 'controller'); 10017 if (isObject(name)) { 10018 extend(controllers, name); 10019 } else { 10020 controllers[name] = constructor; 10021 } 10022 }; 10023 10024 /** 10025 * @ngdoc method 10026 * @name $controllerProvider#allowGlobals 10027 * @description If called, allows `$controller` to find controller constructors on `window` 10028 */ 10029 this.allowGlobals = function() { 10030 globals = true; 10031 }; 10032 10033 10034 this.$get = ['$injector', '$window', function($injector, $window) { 10035 10036 /** 10037 * @ngdoc service 10038 * @name $controller 10039 * @requires $injector 10040 * 10041 * @param {Function|string} constructor If called with a function then it's considered to be the 10042 * controller constructor function. Otherwise it's considered to be a string which is used 10043 * to retrieve the controller constructor using the following steps: 10044 * 10045 * * check if a controller with given name is registered via `$controllerProvider` 10046 * * check if evaluating the string on the current scope returns a constructor 10047 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 10048 * `window` object (not recommended) 10049 * 10050 * The string can use the `controller as property` syntax, where the controller instance is published 10051 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 10052 * to work correctly. 10053 * 10054 * @param {Object} locals Injection locals for Controller. 10055 * @return {Object} Instance of given controller. 10056 * 10057 * @description 10058 * `$controller` service is responsible for instantiating controllers. 10059 * 10060 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 10061 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 10062 */ 10063 return function $controller(expression, locals, later, ident) { 10064 // PRIVATE API: 10065 // param `later` --- indicates that the controller's constructor is invoked at a later time. 10066 // If true, $controller will allocate the object with the correct 10067 // prototype chain, but will not invoke the controller until a returned 10068 // callback is invoked. 10069 // param `ident` --- An optional label which overrides the label parsed from the controller 10070 // expression, if any. 10071 var instance, match, constructor, identifier; 10072 later = later === true; 10073 if (ident && isString(ident)) { 10074 identifier = ident; 10075 } 10076 10077 if (isString(expression)) { 10078 match = expression.match(CNTRL_REG); 10079 if (!match) { 10080 throw $controllerMinErr('ctrlfmt', 10081 "Badly formed controller string '{0}'. " + 10082 "Must match `__name__ as __id__` or `__name__`.", expression); 10083 } 10084 constructor = match[1], 10085 identifier = identifier || match[3]; 10086 expression = controllers.hasOwnProperty(constructor) 10087 ? controllers[constructor] 10088 : getter(locals.$scope, constructor, true) || 10089 (globals ? getter($window, constructor, true) : undefined); 10090 10091 assertArgFn(expression, constructor, true); 10092 } 10093 10094 if (later) { 10095 // Instantiate controller later: 10096 // This machinery is used to create an instance of the object before calling the 10097 // controller's constructor itself. 10098 // 10099 // This allows properties to be added to the controller before the constructor is 10100 // invoked. Primarily, this is used for isolate scope bindings in $compile. 10101 // 10102 // This feature is not intended for use by applications, and is thus not documented 10103 // publicly. 10104 // Object creation: http://jsperf.com/create-constructor/2 10105 var controllerPrototype = (isArray(expression) ? 10106 expression[expression.length - 1] : expression).prototype; 10107 instance = Object.create(controllerPrototype || null); 10108 10109 if (identifier) { 10110 addIdentifier(locals, identifier, instance, constructor || expression.name); 10111 } 10112 10113 var instantiate; 10114 return instantiate = extend(function $controllerInit() { 10115 var result = $injector.invoke(expression, instance, locals, constructor); 10116 if (result !== instance && (isObject(result) || isFunction(result))) { 10117 instance = result; 10118 if (identifier) { 10119 // If result changed, re-assign controllerAs value to scope. 10120 addIdentifier(locals, identifier, instance, constructor || expression.name); 10121 } 10122 } 10123 return instance; 10124 }, { 10125 instance: instance, 10126 identifier: identifier 10127 }); 10128 } 10129 10130 instance = $injector.instantiate(expression, locals, constructor); 10131 10132 if (identifier) { 10133 addIdentifier(locals, identifier, instance, constructor || expression.name); 10134 } 10135 10136 return instance; 10137 }; 10138 10139 function addIdentifier(locals, identifier, instance, name) { 10140 if (!(locals && isObject(locals.$scope))) { 10141 throw minErr('$controller')('noscp', 10142 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 10143 name, identifier); 10144 } 10145 10146 locals.$scope[identifier] = instance; 10147 } 10148 }]; 10149} 10150 10151/** 10152 * @ngdoc service 10153 * @name $document 10154 * @requires $window 10155 * 10156 * @description 10157 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 10158 * 10159 * @example 10160 <example module="documentExample"> 10161 <file name="index.html">
10162 <div ng-controller="ExampleController"> 10163 <p>$document title: <b ng-bind="title"></b></p> 10164 <p>window.document title: <b ng-bind="windowTitle"></b></p> 10165 </div> 10166 </file> 10167 <file name="script.js"> 10168 angular.module('documentExample', []) 10169 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 10170 $scope.title = $document[0].title; 10171 $scope.windowTitle = angular.element(window.document)[0].title; 10172 }]); 10173 </file> 10174 </example> 10175 */ 10176function $DocumentProvider() { 10177 this.$get = ['$window', function(window) { 10178 return jqLite(window.document); 10179 }]; 10180} 10181 10182/** 10183 * @ngdoc service 10184 * @name $exceptionHandler 10185 * @requires ng.$log 10186 * 10187 * @description 10188 * Any uncaught exception in angular expressions is delegated to this service. 10189 * The default implementation simply delegates to `$log.error` which logs it into 10190 * the browser console. 10191 * 10192 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 10193 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 10194 * 10195 * ## Example: 10196 * 10197 * ```js 10198 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 10199 * return function(exception, cause) { 10200 * exception.message += ' (caused by "' + cause + '")'; 10201 * throw exception; 10202 * }; 10203 * }); 10204 * ``` 10205 * 10206 * This example will override the normal action of `$exceptionHandler`, to make angular 10207 * exceptions fail hard when they happen, instead of just logging to the console. 10208 * 10209 * <hr /> 10210 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 10211 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 10212 * (unless executed during a digest). 10213 * 10214 * If you wish, you can manually delegate exceptions, e.g. 10215 * `try { ... } catch(e) { $exceptionHandler(e); }` 10216 * 10217 * @param {Error} exception Exception associated with the error. 10218 * @param {string=} cause optional information about the context in which 10219 * the error was thrown. 10220 * 10221 */ 10222function $ExceptionHandlerProvider() { 10223 this.$get = ['$log', function($log) { 10224 return function(exception, cause) { 10225 $log.error.apply($log, arguments); 10226 }; 10227 }]; 10228} 10229 10230var $$ForceReflowProvider = function() { 10231 this.$get = ['$document', function($document) { 10232 return function(domNode) { 10233 //the line below will force the browser to perform a repaint so 10234 //that all the animated elements within the animation frame will 10235 //be properly updated and drawn on screen. This is required to 10236 //ensure that the preparation animation is properly flushed so that 10237 //the active state picks up from there. DO NOT REMOVE THIS LINE. 10238 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 10239 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 10240 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 10241 if (domNode) { 10242 if (!domNode.nodeType && domNode instanceof jqLite) { 10243 domNode = domNode[0]; 10244 } 10245 } else { 10246 domNode = $document[0].body; 10247 } 10248 return domNode.offsetWidth + 1; 10249 }; 10250 }]; 10251}; 10252 10253var APPLICATION_JSON = 'application/json'; 10254var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 10255var JSON_START = /^\[|^\{(?!\{)/; 10256var JSON_ENDS = { 10257 '[': /]$/, 10258 '{': /}$/ 10259}; 10260var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 10261var $httpMinErr = minErr('$http'); 10262var $httpMinErrLegacyFn = function(method) { 10263 return function() { 10264 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 10265 }; 10266}; 10267 10268function serializeValue(v) { 10269 if (isObject(v)) { 10270 return isDate(v) ? v.toISOString() : toJson(v); 10271 } 10272 return v; 10273} 10274 10275 10276function $HttpParamSerializerProvider() { 10277 /** 10278 * @ngdoc service 10279 * @name $httpParamSerializer 10280 * @description 10281 * 10282 * Default {@link $http `$http`} params serializer that converts objects to strings 10283 * according to the following rules: 10284 * 10285 * * `{'foo': 'bar'}` results in `foo=bar` 10286 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 10287 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 10288 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 10289 * 10290 * Note that serializer will sort the request parameters alphabetically. 10291 * */ 10292 10293 this.$get = function() { 10294 return function ngParamSerializer(params) { 10295 if (!params) return ''; 10296 var parts = [];
vendor: 3,679 bytes, lines 10297-10423
10297 forEachSorted(params, function(value, key) { 10298 if (value === null || isUndefined(value)) return; 10299 if (isArray(value)) { 10300 forEach(value, function(v) { 10301 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 10302 }); 10303 } else { 10304 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 10305 } 10306 }); 10307 10308 return parts.join('&'); 10309 }; 10310 }; 10311} 10312 10313function $HttpParamSerializerJQLikeProvider() { 10314 /** 10315 * @ngdoc service 10316 * @name $httpParamSerializerJQLike 10317 * @description 10318 * 10319 * Alternative {@link $http `$http`} params serializer that follows 10320 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 10321 * The serializer will also sort the params alphabetically. 10322 * 10323 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 10324 * 10325 * ```js 10326 * $http({ 10327 * url: myUrl, 10328 * method: 'GET', 10329 * params: myParams, 10330 * paramSerializer: '$httpParamSerializerJQLike' 10331 * }); 10332 * ``` 10333 * 10334 * It is also possible to set it as the default `paramSerializer` in the 10335 * {@link $httpProvider#defaults `$httpProvider`}. 10336 * 10337 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 10338 * form data for submission: 10339 * 10340 * ```js 10341 * .controller(function($http, $httpParamSerializerJQLike) { 10342 * //... 10343 * 10344 * $http({ 10345 * url: myUrl, 10346 * method: 'POST', 10347 * data: $httpParamSerializerJQLike(myData), 10348 * headers: { 10349 * 'Content-Type': 'application/x-www-form-urlencoded' 10350 * } 10351 * }); 10352 * 10353 * }); 10354 * ``` 10355 * 10356 * */ 10357 this.$get = function() { 10358 return function jQueryLikeParamSerializer(params) { 10359 if (!params) return ''; 10360 var parts = []; 10361 serialize(params, '', true); 10362 return parts.join('&'); 10363 10364 function serialize(toSerialize, prefix, topLevel) { 10365 if (toSerialize === null || isUndefined(toSerialize)) return; 10366 if (isArray(toSerialize)) { 10367 forEach(toSerialize, function(value, index) { 10368 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 10369 }); 10370 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 10371 forEachSorted(toSerialize, function(value, key) { 10372 serialize(value, prefix + 10373 (topLevel ? '' : '[') + 10374 key + 10375 (topLevel ? '' : ']')); 10376 }); 10377 } else { 10378 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 10379 } 10380 } 10381 }; 10382 }; 10383} 10384 10385function defaultHttpResponseTransform(data, headers) { 10386 if (isString(data)) { 10387 // Strip json vulnerability protection prefix and trim whitespace 10388 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 10389 10390 if (tempData) { 10391 var contentType = headers('Content-Type'); 10392 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 10393 data = fromJson(tempData); 10394 } 10395 } 10396 } 10397 10398 return data; 10399} 10400 10401function isJsonLike(str) { 10402 var jsonStart = str.match(JSON_START); 10403 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 10404} 10405 10406/** 10407 * Parse headers into key value object 10408 * 10409 * @param {string} headers Raw headers as a string 10410 * @returns {Object} Parsed headers as key value object 10411 */ 10412function parseHeaders(headers) { 10413 var parsed = createMap(), i; 10414 10415 function fillInParsed(key, val) { 10416 if (key) { 10417 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 10418 } 10419 } 10420 10421 if (isString(headers)) { 10422 forEach(headers.split('\n'), function(line) { 10423 i = line.indexOf(':');
vendor: 4,132 bytes, lines 10424-10560
10424 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 10425 }); 10426 } else if (isObject(headers)) { 10427 forEach(headers, function(headerVal, headerKey) { 10428 fillInParsed(lowercase(headerKey), trim(headerVal)); 10429 }); 10430 } 10431 10432 return parsed; 10433} 10434 10435 10436/** 10437 * Returns a function that provides access to parsed headers. 10438 * 10439 * Headers are lazy parsed when first requested. 10440 * @see parseHeaders 10441 * 10442 * @param {(string|Object)} headers Headers to provide access to. 10443 * @returns {function(string=)} Returns a getter function which if called with: 10444 * 10445 * - if called with single an argument returns a single header value or null 10446 * - if called with no arguments returns an object containing all headers. 10447 */ 10448function headersGetter(headers) { 10449 var headersObj; 10450 10451 return function(name) { 10452 if (!headersObj) headersObj = parseHeaders(headers); 10453 10454 if (name) { 10455 var value = headersObj[lowercase(name)]; 10456 if (value === void 0) { 10457 value = null; 10458 } 10459 return value; 10460 } 10461 10462 return headersObj; 10463 }; 10464} 10465 10466 10467/** 10468 * Chain all given functions 10469 * 10470 * This function is used for both request and response transforming 10471 * 10472 * @param {*} data Data to transform. 10473 * @param {function(string=)} headers HTTP headers getter fn. 10474 * @param {number} status HTTP status code of the response. 10475 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 10476 * @returns {*} Transformed data. 10477 */ 10478function transformData(data, headers, status, fns) { 10479 if (isFunction(fns)) { 10480 return fns(data, headers, status); 10481 } 10482 10483 forEach(fns, function(fn) { 10484 data = fn(data, headers, status); 10485 }); 10486 10487 return data; 10488} 10489 10490 10491function isSuccess(status) { 10492 return 200 <= status && status < 300; 10493} 10494 10495 10496/** 10497 * @ngdoc provider 10498 * @name $httpProvider 10499 * @description 10500 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 10501 * */ 10502function $HttpProvider() { 10503 /** 10504 * @ngdoc property 10505 * @name $httpProvider#defaults 10506 * @description 10507 * 10508 * Object containing default values for all {@link ng.$http $http} requests. 10509 * 10510 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with 10511 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses 10512 * by default. See {@link $http#caching $http Caching} for more information. 10513 * 10514 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 10515 * Defaults value is `'XSRF-TOKEN'`. 10516 * 10517 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 10518 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 10519 * 10520 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 10521 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 10522 * setting default headers. 10523 * - **`defaults.headers.common`** 10524 * - **`defaults.headers.post`** 10525 * - **`defaults.headers.put`** 10526 * - **`defaults.headers.patch`** 10527 * 10528 * 10529 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 10530 * used to the prepare string representation of request parameters (specified as an object). 10531 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 10532 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 10533 * 10534 **/ 10535 var defaults = this.defaults = { 10536 // transform incoming response data 10537 transformResponse: [defaultHttpResponseTransform], 10538 10539 // transform outgoing request data 10540 transformRequest: [function(d) { 10541 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 10542 }], 10543 10544 // default headers 10545 headers: { 10546 common: { 10547 'Accept': 'application/json, text/plain, */*' 10548 }, 10549 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 10550 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 10551 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 10552 }, 10553 10554 xsrfCookieName: 'XSRF-TOKEN', 10555 xsrfHeaderName: 'X-XSRF-TOKEN', 10556 10557 paramSerializer: '$httpParamSerializer' 10558 }; 10559 10560 var useApplyAsync = false;
vendor: 4,307 bytes, lines 10561-10671
10561 /** 10562 * @ngdoc method 10563 * @name $httpProvider#useApplyAsync 10564 * @description 10565 * 10566 * Configure $http service to combine processing of multiple http responses received at around 10567 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 10568 * significant performance improvement for bigger applications that make many HTTP requests 10569 * concurrently (common during application bootstrap). 10570 * 10571 * Defaults to false. If no value is specified, returns the current configured value. 10572 * 10573 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 10574 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 10575 * to load and share the same digest cycle. 10576 * 10577 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 10578 * otherwise, returns the current configured value. 10579 **/ 10580 this.useApplyAsync = function(value) { 10581 if (isDefined(value)) { 10582 useApplyAsync = !!value; 10583 return this; 10584 } 10585 return useApplyAsync; 10586 }; 10587 10588 var useLegacyPromise = true; 10589 /** 10590 * @ngdoc method 10591 * @name $httpProvider#useLegacyPromiseExtensions 10592 * @description 10593 * 10594 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 10595 * This should be used to make sure that applications work without these methods. 10596 * 10597 * Defaults to true. If no value is specified, returns the current configured value. 10598 * 10599 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. 10600 * 10601 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 10602 * otherwise, returns the current configured value. 10603 **/ 10604 this.useLegacyPromiseExtensions = function(value) { 10605 if (isDefined(value)) { 10606 useLegacyPromise = !!value; 10607 return this; 10608 } 10609 return useLegacyPromise; 10610 }; 10611 10612 /** 10613 * @ngdoc property 10614 * @name $httpProvider#interceptors 10615 * @description 10616 * 10617 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 10618 * pre-processing of request or postprocessing of responses. 10619 * 10620 * These service factories are ordered by request, i.e. they are applied in the same order as the 10621 * array, on request, but reverse order, on response. 10622 * 10623 * {@link ng.$http#interceptors Interceptors detailed info} 10624 **/ 10625 var interceptorFactories = this.interceptors = []; 10626 10627 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 10628 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 10629 10630 var defaultCache = $cacheFactory('$http'); 10631 10632 /** 10633 * Make sure that default param serializer is exposed as a function 10634 */ 10635 defaults.paramSerializer = isString(defaults.paramSerializer) ? 10636 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 10637 10638 /** 10639 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 10640 * The reversal is needed so that we can build up the interception chain around the 10641 * server request. 10642 */ 10643 var reversedInterceptors = []; 10644 10645 forEach(interceptorFactories, function(interceptorFactory) { 10646 reversedInterceptors.unshift(isString(interceptorFactory) 10647 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 10648 }); 10649 10650 /** 10651 * @ngdoc service 10652 * @kind function 10653 * @name $http 10654 * @requires ng.$httpBackend 10655 * @requires $cacheFactory 10656 * @requires $rootScope 10657 * @requires $q 10658 * @requires $injector 10659 * 10660 * @description 10661 * The `$http` service is a core Angular service that facilitates communication with the remote 10662 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 10663 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 10664 * 10665 * For unit testing applications that use `$http` service, see 10666 * {@link ngMock.$httpBackend $httpBackend mock}. 10667 * 10668 * For a higher level of abstraction, please check out the {@link ngResource.$resource 10669 * $resource} service. 10670 * 10671 * The $http API is based on the {@link ng.$q deferred/promise APIs}
10671 exposed by 10672 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 10673 * it is important to familiarize yourself with these APIs and the guarantees they provide. 10674 * 10675 * 10676 * ## General usage 10677 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} — 10678 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 10679 * 10680 * ```js 10681 * // Simple GET request example: 10682 * $http({ 10683 * method: 'GET', 10684 * url: '/someUrl' 10685 * }).then(function successCallback(response) { 10686 * // this callback will be called asynchronously 10687 * // when the response is available 10688 * }, function errorCallback(response) { 10689 * // called asynchronously if an error occurs 10690 * // or server returns response with an error status. 10691 * }); 10692 * ``` 10693 * 10694 * The response object has these properties: 10695 * 10696 * - **data** – `{string|Object}` – The response body transformed with the transform 10697 * functions. 10698 * - **status** – `{number}` – HTTP status code of the response. 10699 * - **headers** – `{function([headerName])}` – Header getter function. 10700 * - **config** – `{Object}` – The configuration object that was used to generate the request. 10701 * - **statusText** – `{string}` – HTTP status text of the response. 10702 * 10703 * A response status code between 200 and 299 is considered a success status and 10704 * will result in the success callback being called. Note that if the response is a redirect, 10705 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 10706 * called for such responses. 10707 * 10708 * 10709 * ## Shortcut methods 10710 * 10711 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 10712 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 10713 * last argument. 10714 * 10715 * ```js 10716 * $http.get('/someUrl', config).then(successCallback, errorCallback); 10717 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 10718 * ``` 10719 * 10720 * Complete list of shortcut methods: 10721 * 10722 * - {@link ng.$http#get $http.get} 10723 * - {@link ng.$http#head $http.head} 10724 * - {@link ng.$http#post $http.post} 10725 * - {@link ng.$http#put $http.put} 10726 * - {@link ng.$http#delete $http.delete} 10727 * - {@link ng.$http#jsonp $http.jsonp} 10728 * - {@link ng.$http#patch $http.patch} 10729 * 10730 * 10731 * ## Writing Unit Tests that use $http 10732 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 10733 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 10734 * request using trained responses. 10735 * 10736 * ``` 10737 * $httpBackend.expectGET(...); 10738 * $http.get(...); 10739 * $httpBackend.flush(); 10740 * ``` 10741 * 10742 * ## Deprecation Notice 10743 * <div class="alert alert-danger"> 10744 * The `$http` legacy promise methods `success` and `error` have been deprecated. 10745 * Use the standard `then` method instead. 10746 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 10747 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 10748 * </div> 10749 * 10750 * ## Setting HTTP Headers 10751 * 10752 * The $http service will automatically add certain HTTP headers to all requests. These defaults 10753 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 10754 * object, which currently contains this default configuration: 10755 * 10756 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 10757 * - `Accept: application/json, text/plain, * / *` 10758 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 10759 * - `Content-Type: application/json` 10760 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 10761 * - `Content-Type: application/json` 10762 *
10763 * To add or overwrite these defaults, simply add or remove a property from these configuration 10764 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 10765 * with the lowercased HTTP method name as the key, e.g. 10766 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 10767 * 10768 * The defaults can also be set at runtime via the `$http.defaults` object in the same 10769 * fashion. For example: 10770 * 10771 * ``` 10772 * module.run(function($http) { 10773 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 10774 * }); 10775 * ``` 10776 * 10777 * In addition, you can supply a `headers` property in the config object passed when 10778 * calling `$http(config)`, which overrides the defaults without changing them globally. 10779 * 10780 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 10781 * Use the `headers` property, setting the desired header to `undefined`. For example: 10782 * 10783 * ```js 10784 * var req = { 10785 * method: 'POST', 10786 * url: 'http://example.com', 10787 * headers: { 10788 * 'Content-Type': undefined 10789 * }, 10790 * data: { test: 'test' } 10791 * } 10792 * 10793 * $http(req).then(function(){...}, function(){...}); 10794 * ``` 10795 * 10796 * ## Transforming Requests and Responses 10797 * 10798 * Both requests and responses can be transformed using transformation functions: `transformRequest` 10799 * and `transformResponse`. These properties can be a single function that returns 10800 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 10801 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 10802 * 10803 * <div class="alert alert-warning"> 10804 * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. 10805 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). 10806 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest 10807 * function will be reflected on the scope and in any templates where the object is data-bound. 10808 * To prevent this, transform functions should have no side-effects. 10809 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. 10810 * </div> 10811 * 10812 * ### Default Transformations 10813 * 10814 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 10815 * `defaults.transformResponse` properties. If a request does not provide its own transformations 10816 * then these will be applied. 10817 * 10818 * You can augment or replace the default transformations by modifying these properties by adding to or 10819 * replacing the array. 10820 * 10821 * Angular provides the following default transformations: 10822 * 10823 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 10824 * 10825 * - If the `data` property of the request configuration object contains an object, serialize it 10826 * into JSON format. 10827 * 10828 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 10829 * 10830 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 10831 * - If JSON response is detected, deserialize it using a JSON parser. 10832 * 10833 * 10834 * ### Overriding the Default Transformations Per Request 10835 * 10836 * If you wish override the request/response transformations only for a single request then provide 10837 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 10838 * into `$http`. 10839 * 10840 * Note that if you provide these properties on the config object the default transformations will be 10841 * overwritten. If you wish to augment the default transformations then you must include them in your 10842 * local transformation array.
10843 * 10844 * The following code demonstrates adding a new response transformation to be run after the default response 10845 * transformations have been run. 10846 * 10847 * ```js 10848 * function appendTransform(defaults, transform) { 10849 * 10850 * // We can't guarantee that the default transformation is an array 10851 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 10852 * 10853 * // Append the new transformation to the defaults 10854 * return defaults.concat(transform); 10855 * } 10856 * 10857 * $http({ 10858 * url: '...', 10859 * method: 'GET', 10860 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 10861 * return doTransform(value); 10862 * }) 10863 * }); 10864 * ``` 10865 * 10866 * 10867 * ## Caching 10868 * 10869 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must 10870 * set the config.cache value or the default cache value to TRUE or to a cache object (created 10871 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes 10872 * precedence over the default cache value. 10873 * 10874 * In order to: 10875 * * cache all responses - set the default cache value to TRUE or to a cache object 10876 * * cache a specific response - set config.cache value to TRUE or to a cache object 10877 * 10878 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, 10879 * then the default `$cacheFactory($http)` object is used. 10880 * 10881 * The default cache value can be set by updating the 10882 * {@link ng.$http#defaults `$http.defaults.cache`} property or the 10883 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. 10884 * 10885 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using 10886 * the relevant cache object. The next time the same request is made, the response is returned 10887 * from the cache without sending a request to the server. 10888 * 10889 * Take note that: 10890 * 10891 * * Only GET and JSONP requests are cached. 10892 * * The cache key is the request URL including search parameters; headers are not considered. 10893 * * Cached responses are returned asynchronously, in the same way as responses from the server. 10894 * * If multiple identical requests are made using the same cache, which is not yet populated, 10895 * one request will be made to the server and remaining requests will return the same response. 10896 * * A cache-control header on the response does not affect if or how responses are cached. 10897 * 10898 * 10899 * ## Interceptors 10900 * 10901 * Before you start creating interceptors, be sure to understand the 10902 * {@link ng.$q $q and deferred/promise APIs}. 10903 * 10904 * For purposes of global error handling, authentication, or any kind of synchronous or 10905 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 10906 * able to intercept requests before they are handed to the server and 10907 * responses before they are handed over to the application code that 10908 * initiated these requests. The interceptors leverage the {@link ng.$q 10909 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 10910 * 10911 * The interceptors are service factories that are registered with the `$httpProvider` by 10912 * adding them to the `$httpProvider.interceptors` array. The factory is called and 10913 * injected with dependencies (if specified) and returns the interceptor. 10914 * 10915 * There are two kinds of interceptors (and two kinds of rejection interceptors): 10916 * 10917 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 10918 * modify the `config` object or create a new one. The function needs to return the `config` 10919 * object directly, or a promise containing the `config` or a new `config` object. 10920 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 10921 * resolved with a rejection. 10922 * * `response`: interceptors get called with http `response` object. The function is free to 10923 * modify the `response` object or create a new one. The function needs to return the `response` 10924 * object directly, or as a promise containing the `response` or a new `response` object. 10925 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 10926 * resolved with a rejection. 10927 * 10928 * 10929 * ```js 10930 * // register the interceptor as a service 10931 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 10932 * return { 10933 * // optional method 10934 * 'request': function(config) {
10935 * // do something on success 10936 * return config; 10937 * }, 10938 * 10939 * // optional method 10940 * 'requestError': function(rejection) { 10941 * // do something on error 10942 * if (canRecover(rejection)) { 10943 * return responseOrNewPromise 10944 * } 10945 * return $q.reject(rejection); 10946 * }, 10947 * 10948 * 10949 * 10950 * // optional method 10951 * 'response': function(response) { 10952 * // do something on success 10953 * return response; 10954 * }, 10955 * 10956 * // optional method 10957 * 'responseError': function(rejection) { 10958 * // do something on error 10959 * if (canRecover(rejection)) { 10960 * return responseOrNewPromise 10961 * } 10962 * return $q.reject(rejection); 10963 * } 10964 * }; 10965 * }); 10966 * 10967 * $httpProvider.interceptors.push('myHttpInterceptor'); 10968 * 10969 * 10970 * // alternatively, register the interceptor via an anonymous factory 10971 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 10972 * return { 10973 * 'request': function(config) { 10974 * // same as above 10975 * }, 10976 * 10977 * 'response': function(response) { 10978 * // same as above 10979 * } 10980 * }; 10981 * }); 10982 * ``` 10983 * 10984 * ## Security Considerations 10985 * 10986 * When designing web applications, consider security threats from: 10987 * 10988 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10989 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 10990 * 10991 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 10992 * pre-configured with strategies that address these issues, but for this to work backend server 10993 * cooperation is required. 10994 * 10995 * ### JSON Vulnerability Protection 10996 * 10997 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10998 * allows third party website to turn your JSON resource URL into 10999 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 11000 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 11001 * Angular will automatically strip the prefix before processing it as JSON. 11002 * 11003 * For example if your server needs to return: 11004 * ```js 11005 * ['one','two'] 11006 * ``` 11007 * 11008 * which is vulnerable to attack, your server can return: 11009 * ```js 11010 * )]}', 11011 * ['one','two'] 11012 * ``` 11013 * 11014 * Angular will strip the prefix, before processing the JSON. 11015 * 11016 * 11017 * ### Cross Site Request Forgery (XSRF) Protection 11018 * 11019 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by 11020 * which the attacker can trick an authenticated user into unknowingly executing actions on your 11021 * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the 11022 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP 11023 * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the 11024 * cookie, your server can be assured that the XHR came from JavaScript running on your domain. 11025 * The header will not be set for cross-domain requests. 11026 * 11027 * To take advantage of this, your server needs to set a token in a JavaScript readable session 11028 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 11029 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 11030 * that only JavaScript running on your domain could have sent the request. The token must be 11031 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 11032 * making up its own tokens). We recommend that the token is a digest of your site's 11033 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 11034 * for added security. 11035 * 11036 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 11037 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 11038 * or the per-request config object. 11039 * 11040 * In order to prevent collisions in environments where multiple Angular apps share the 11041 * same domain or subdomain, we recommend that each application uses unique cookie name. 11042 * 11043 * @param {object} config Object describing the request to be made and how it should be 11044 * processed. The object has following properties: 11045 * 11046 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) 11047 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. 11048 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized 11049 * with the `paramSerializer` and appended as GET parameters. 11050 * - **data** – `{string|Object}` – Data to be sent as the request message data. 11051 * - **headers** – `{Object}` – Map of strings or functions which return strings representing 11052 * HTTP headers to send to the server. If the return value of a function is null, the 11053 * header will not be sent. Functions accept a config object as an argument. 11054 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. 11055 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. 11056 * The handler will be called in the context of a `$apply` block. 11057 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload 11058 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. 11059 * The handler will be called in the context of a `$apply` block. 11060 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. 11061 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. 11062 * - **transformRequest** – 11063 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
11063}` – 11064 * transform function or an array of such functions. The transform function takes the http 11065 * request body and headers and returns its transformed (typically serialized) version. 11066 * See {@link ng.$http#overriding-the-default-transformations-per-request 11067 * Overriding the Default Transformations} 11068 * - **transformResponse** – 11069 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` – 11070 * transform function or an array of such functions. The transform function takes the http 11071 * response body, headers and status and returns its transformed (typically deserialized) version. 11072 * See {@link ng.$http#overriding-the-default-transformations-per-request 11073 * Overriding the Default Transformations} 11074 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 11075 * prepare the string representation of request parameters (specified as an object). 11076 * If specified as string, it is interpreted as function registered with the 11077 * {@link $injector $injector}, which means you can create your own serializer 11078 * by registering it as a {@link auto.$provide#service service}. 11079 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 11080 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 11081 * - **cache** – `{boolean|Object}` – A boolean value or object created with 11082 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 11083 * See {@link $http#caching $http Caching} for more information. 11084 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} 11085 * that should abort the request when resolved. 11086 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 11087 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 11088 * for more information. 11089 * - **responseType** - `{string}` - see 11090 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 11091 * 11092 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 11093 * when the request succeeds or fails. 11094 * 11095 * 11096 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 11097 * requests. This is primarily meant to be used for debugging purposes. 11098 * 11099 * 11100 * @example 11101<example module="httpExample"> 11102<file name="index.html"> 11103 <div ng-controller="FetchController"> 11104 <select ng-model="method" aria-label="Request method"> 11105 <option>GET</option> 11106 <option>JSONP</option> 11107 </select> 11108 <input type="text" ng-model="url" size="80" aria-label="URL" /> 11109 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 11110 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 11111 <button id="samplejsonpbtn" 11112 ng-click="updateModel('JSONP', 11113 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 11114 Sample JSONP 11115 </button> 11116 <button id="invalidjsonpbtn" 11117 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 11118 Invalid JSONP 11119 </button> 11120 <pre>http status code: {{status}}</pre> 11121 <pre>http response data: {{data}}</pre> 11122 </div> 11123</file> 11124<file name="script.js"> 11125 angular.module('httpExample', []) 11126 .controller('FetchController', ['$scope', '$http', '$templateCache', 11127 function($scope, $http, $templateCache) { 11128 $scope.method = 'GET'; 11129 $scope.url = 'http-hello.html'; 11130 11131 $scope.fetch = function() { 11132 $scope.code = null; 11133 $scope.response = null; 11134 11135 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 11136 then(function(response) { 11137 $scope.status = response.status; 11138 $scope.data = response.data; 11139 }, function(response) { 11140 $scope.data = response.data || "Request failed"; 11141 $scope.status = response.status; 11142 }); 11143 }; 11144 11145 $scope.updateModel = function(method, url) { 11146 $scope.method = method; 11147 $scope.url = url; 11148 }; 11149 }]); 11150</file> 11151<file name="http-hello.html"> 11152 Hello, $http! 11153</file> 11154<file name="protractor.js" type="protractor"> 11155 var status = element(by.binding('status')); 11156 var data = element(by.binding('data')); 11157 var fetchBtn = element(by.id('fetchbtn')); 11158 var sampleGetBtn = element(by.id('samplegetbtn')); 11159 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 11160 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 11161 11162 it('should make an xhr GET request', function() { 11163 sampleGetBtn.click(); 11164 fetchBtn.click(); 11165 expect(status.getText()).toMatch('200'); 11166 expect(data.getText()).toMatch(/Hello, \$http!/); 11167 }); 11168 11169// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 11170// it('should make a JSONP request to angularjs.org', function() { 11171// sampleJsonpBtn.click(); 11172// fetchBtn.click(); 11173// expect(status.getText()).toMatch('200'); 11174// expect(data.getText()).toMatch(/Super Hero!/); 11175// }); 11176
11177 it('should make JSONP request to invalid URL and invoke the error handler', 11178 function() { 11179 invalidJsonpBtn.click(); 11180 fetchBtn.click(); 11181 expect(status.getText()).toMatch('0'); 11182 expect(data.getText()).toMatch('Request failed'); 11183 }); 11184</file> 11185</example> 11186 */ 11187 function $http(requestConfig) { 11188 11189 if (!isObject(requestConfig)) { 11190 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 11191 } 11192 11193 if (!isString(requestConfig.url)) { 11194 throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); 11195 } 11196 11197 var config = extend({ 11198 method: 'get', 11199 transformRequest: defaults.transformRequest, 11200 transformResponse: defaults.transformResponse, 11201 paramSerializer: defaults.paramSerializer 11202 }, requestConfig); 11203 11204 config.headers = mergeHeaders(requestConfig); 11205 config.method = uppercase(config.method); 11206 config.paramSerializer = isString(config.paramSerializer) ? 11207 $injector.get(config.paramSerializer) : config.paramSerializer; 11208 11209 var serverRequest = function(config) { 11210 var headers = config.headers; 11211 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 11212 11213 // strip content-type if data is undefined 11214 if (isUndefined(reqData)) { 11215 forEach(headers, function(value, header) { 11216 if (lowercase(header) === 'content-type') { 11217 delete headers[header]; 11218 } 11219 }); 11220 } 11221 11222 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 11223 config.withCredentials = defaults.withCredentials; 11224 } 11225 11226 // send request 11227 return sendReq(config, reqData).then(transformResponse, transformResponse); 11228 }; 11229 11230 var chain = [serverRequest, undefined]; 11231 var promise = $q.when(config); 11232 11233 // apply interceptors 11234 forEach(reversedInterceptors, function(interceptor) { 11235 if (interceptor.request || interceptor.requestError) { 11236 chain.unshift(interceptor.request, interceptor.requestError); 11237 } 11238 if (interceptor.response || interceptor.responseError) { 11239 chain.push(interceptor.response, interceptor.responseError); 11240 } 11241 }); 11242 11243 while (chain.length) { 11244 var thenFn = chain.shift(); 11245 var rejectFn = chain.shift(); 11246 11247 promise = promise.then(thenFn, rejectFn); 11248 } 11249 11250 if (useLegacyPromise) { 11251 promise.success = function(fn) { 11252 assertArgFn(fn, 'fn'); 11253 11254 promise.then(function(response) { 11255 fn(response.data, response.status, response.headers, config); 11256 }); 11257 return promise; 11258 }; 11259 11260 promise.error = function(fn) { 11261 assertArgFn(fn, 'fn'); 11262 11263 promise.then(null, function(response) { 11264 fn(response.data, response.status, response.headers, config); 11265 }); 11266 return promise; 11267 }; 11268 } else { 11269 promise.success = $httpMinErrLegacyFn('success'); 11270 promise.error = $httpMinErrLegacyFn('error'); 11271 } 11272 11273 return promise; 11274 11275 function transformResponse(response) { 11276 // make a copy since the response must be cacheable 11277 var resp = extend({}, response); 11278 resp.data = transformData(response.data, response.headers, response.status, 11279 config.transformResponse); 11280 return (isSuccess(response.status)) 11281 ? resp 11282 : $q.reject(resp); 11283 } 11284 11285 function executeHeaderFns(headers, config) { 11286 var headerContent, processedHeaders = {}; 11287 11288 forEach(headers, function(headerFn, header) { 11289 if (isFunction(headerFn)) { 11290 headerContent = headerFn(config); 11291 if (headerContent != null) { 11292 processedHeaders[header] = headerContent; 11293 } 11294 } else { 11295 processedHeaders[header] = headerFn; 11296 } 11297 }); 11298 11299 return processedHeaders; 11300 } 11301 11302 function mergeHeaders(config) { 11303 var defHeaders = defaults.headers, 11304 reqHeaders = extend({}, config.headers), 11305 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 11306 11307 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 11308
vendor: 9,890 bytes, lines 11309-11633
11309 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 11310 defaultHeadersIteration: 11311 for (defHeaderName in defHeaders) { 11312 lowercaseDefHeaderName = lowercase(defHeaderName); 11313 11314 for (reqHeaderName in reqHeaders) { 11315 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 11316 continue defaultHeadersIteration; 11317 } 11318 } 11319 11320 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 11321 } 11322 11323 // execute if header value is a function for merged headers 11324 return executeHeaderFns(reqHeaders, shallowCopy(config)); 11325 } 11326 } 11327 11328 $http.pendingRequests = []; 11329 11330 /** 11331 * @ngdoc method 11332 * @name $http#get 11333 * 11334 * @description 11335 * Shortcut method to perform `GET` request. 11336 * 11337 * @param {string} url Relative or absolute URL specifying the destination of the request 11338 * @param {Object=} config Optional configuration object 11339 * @returns {HttpPromise} Future object 11340 */ 11341 11342 /** 11343 * @ngdoc method 11344 * @name $http#delete 11345 * 11346 * @description 11347 * Shortcut method to perform `DELETE` request. 11348 * 11349 * @param {string} url Relative or absolute URL specifying the destination of the request 11350 * @param {Object=} config Optional configuration object 11351 * @returns {HttpPromise} Future object 11352 */ 11353 11354 /** 11355 * @ngdoc method 11356 * @name $http#head 11357 * 11358 * @description 11359 * Shortcut method to perform `HEAD` request. 11360 * 11361 * @param {string} url Relative or absolute URL specifying the destination of the request 11362 * @param {Object=} config Optional configuration object 11363 * @returns {HttpPromise} Future object 11364 */ 11365 11366 /** 11367 * @ngdoc method 11368 * @name $http#jsonp 11369 * 11370 * @description 11371 * Shortcut method to perform `JSONP` request. 11372 * 11373 * @param {string} url Relative or absolute URL specifying the destination of the request. 11374 * The name of the callback should be the string `JSON_CALLBACK`. 11375 * @param {Object=} config Optional configuration object 11376 * @returns {HttpPromise} Future object 11377 */ 11378 createShortMethods('get', 'delete', 'head', 'jsonp'); 11379 11380 /** 11381 * @ngdoc method 11382 * @name $http#post 11383 * 11384 * @description 11385 * Shortcut method to perform `POST` request. 11386 * 11387 * @param {string} url Relative or absolute URL specifying the destination of the request 11388 * @param {*} data Request content 11389 * @param {Object=} config Optional configuration object 11390 * @returns {HttpPromise} Future object 11391 */ 11392 11393 /** 11394 * @ngdoc method 11395 * @name $http#put 11396 * 11397 * @description 11398 * Shortcut method to perform `PUT` request. 11399 * 11400 * @param {string} url Relative or absolute URL specifying the destination of the request 11401 * @param {*} data Request content 11402 * @param {Object=} config Optional configuration object 11403 * @returns {HttpPromise} Future object 11404 */ 11405 11406 /** 11407 * @ngdoc method 11408 * @name $http#patch 11409 * 11410 * @description 11411 * Shortcut method to perform `PATCH` request. 11412 * 11413 * @param {string} url Relative or absolute URL specifying the destination of the request 11414 * @param {*} data Request content 11415 * @param {Object=} config Optional configuration object 11416 * @returns {HttpPromise} Future object 11417 */ 11418 createShortMethodsWithData('post', 'put', 'patch'); 11419 11420 /** 11421 * @ngdoc property 11422 * @name $http#defaults 11423 * 11424 * @description 11425 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 11426 * default headers, withCredentials as well as request and response transformations. 11427 * 11428 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 11429 */ 11430 $http.defaults = defaults; 11431 11432 11433 return $http; 11434 11435 11436 function createShortMethods(names) { 11437 forEach(arguments, function(name) { 11438 $http[name] = function(url, config) { 11439 return $http(extend({}, config || {}, { 11440 method: name, 11441 url: url 11442 })); 11443 }; 11444 }); 11445 } 11446 11447 11448 function createShortMethodsWithData(name) { 11449 forEach(arguments, function(name) { 11450 $http[name] = function(url, data, config) { 11451 return $http(extend({}, config || {}, { 11452 method: name, 11453 url: url, 11454 data: data 11455 })); 11456 }; 11457 }); 11458 } 11459 11460 11461 /** 11462 * Makes the request. 11463 * 11464 * !!! ACCESSES CLOSURE VARS: 11465 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 11466 */ 11467 function sendReq(config, reqData) { 11468 var deferred = $q.defer(), 11469 promise = deferred.promise, 11470 cache, 11471 cachedResp, 11472 reqHeaders = config.headers, 11473 url = buildUrl(config.url, config.paramSerializer(config.params)); 11474 11475 $http.pendingRequests.push(config); 11476 promise.then(removePendingReq, removePendingReq); 11477 11478 11479 if ((config.cache || defaults.cache) && config.cache !== false && 11480 (config.method === 'GET' || config.method === 'JSONP')) { 11481 cache = isObject(config.cache) ? config.cache 11482 : isObject(defaults.cache) ? defaults.cache 11483 : defaultCache; 11484 } 11485 11486 if (cache) { 11487 cachedResp = cache.get(url); 11488 if (isDefined(cachedResp)) { 11489 if (isPromiseLike(cachedResp)) { 11490 // cached request has already been sent, but there is no response yet 11491 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 11492 } else { 11493 // serving from cache 11494 if (isArray(cachedResp)) { 11495 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 11496 } else { 11497 resolvePromise(cachedResp, 200, {}, 'OK'); 11498 } 11499 } 11500 } else { 11501 // put the promise for the non-transformed response into cache as a placeholder 11502 cache.put(url, promise); 11503 } 11504 } 11505 11506 11507 // if we won't have the response in cache, set the xsrf headers and 11508 // send the request to the backend 11509 if (isUndefined(cachedResp)) { 11510 var xsrfValue = urlIsSameOrigin(config.url) 11511 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 11512 : undefined; 11513 if (xsrfValue) { 11514 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 11515 } 11516 11517 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 11518 config.withCredentials, config.responseType, 11519 createApplyHandlers(config.eventHandlers), 11520 createApplyHandlers(config.uploadEventHandlers)); 11521 } 11522 11523 return promise; 11524 11525 function createApplyHandlers(eventHandlers) { 11526 if (eventHandlers) { 11527 var applyHandlers = {}; 11528 forEach(eventHandlers, function(eventHandler, key) { 11529 applyHandlers[key] = function(event) { 11530 if (useApplyAsync) { 11531 $rootScope.$applyAsync(callEventHandler); 11532 } else if ($rootScope.$$phase) { 11533 callEventHandler(); 11534 } else { 11535 $rootScope.$apply(callEventHandler); 11536 } 11537 11538 function callEventHandler() { 11539 eventHandler(event); 11540 } 11541 }; 11542 }); 11543 return applyHandlers; 11544 } 11545 } 11546 11547 11548 /** 11549 * Callback registered to $httpBackend(): 11550 * - caches the response if desired 11551 * - resolves the raw $http promise 11552 * - calls $apply 11553 */ 11554 function done(status, response, headersString, statusText) { 11555 if (cache) { 11556 if (isSuccess(status)) { 11557 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 11558 } else { 11559 // remove promise from the cache 11560 cache.remove(url); 11561 } 11562 } 11563 11564 function resolveHttpPromise() { 11565 resolvePromise(response, status, headersString, statusText); 11566 } 11567 11568 if (useApplyAsync) { 11569 $rootScope.$applyAsync(resolveHttpPromise); 11570 } else { 11571 resolveHttpPromise(); 11572 if (!$rootScope.$$phase) $rootScope.$apply(); 11573 } 11574 } 11575 11576 11577 /** 11578 * Resolves the raw $http promise. 11579 */ 11580 function resolvePromise(response, status, headers, statusText) { 11581 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 11582 status = status >= -1 ? status : 0; 11583 11584 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 11585 data: response, 11586 status: status, 11587 headers: headersGetter(headers), 11588 config: config, 11589 statusText: statusText 11590 }); 11591 } 11592 11593 function resolvePromiseWithResult(result) { 11594 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 11595 } 11596 11597 function removePendingReq() { 11598 var idx = $http.pendingRequests.indexOf(config); 11599 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 11600 } 11601 } 11602 11603 11604 function buildUrl(url, serializedParams) { 11605 if (serializedParams.length > 0) { 11606 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 11607 } 11608 return url; 11609 } 11610 }]; 11611} 11612 11613/** 11614 * @ngdoc service 11615 * @name $xhrFactory 11616 * 11617 * @description 11618 * Factory function used to create XMLHttpRequest objects. 11619 * 11620 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 11621 * 11622 * ``` 11623 * angular.module('myApp', []) 11624 * .factory('$xhrFactory', function() { 11625 * return function createXhr(method, url) { 11626 * return new window.XMLHttpRequest({mozSystem: true}); 11627 * }; 11628 * }); 11629 * ``` 11630 * 11631 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 11632 * @param {string} url URL of the request. 11633 */
11634function $xhrFactoryProvider() { 11635 this.$get = function() { 11636 return function createXhr() { 11637 return new window.XMLHttpRequest(); 11638 }; 11639 }; 11640} 11641 11642/** 11643 * @ngdoc service 11644 * @name $httpBackend 11645 * @requires $window 11646 * @requires $document 11647 * @requires $xhrFactory 11648 * 11649 * @description 11650 * HTTP backend used by the {@link ng.$http service} that delegates to 11651 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 11652 * 11653 * You should never need to use this service directly, instead use the higher-level abstractions: 11654 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 11655 * 11656 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 11657 * $httpBackend} which can be trained with responses. 11658 */ 11659function $HttpBackendProvider() { 11660 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 11661 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 11662 }]; 11663} 11664 11665function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 11666 // TODO(vojta): fix the signature 11667 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { 11668 $browser.$$incOutstandingRequestCount(); 11669 url = url || $browser.url(); 11670 11671 if (lowercase(method) == 'jsonp') { 11672 var callbackId = '_' + (callbacks.counter++).toString(36); 11673 callbacks[callbackId] = function(data) { 11674 callbacks[callbackId].data = data; 11675 callbacks[callbackId].called = true; 11676 }; 11677 11678 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 11679 callbackId, function(status, text) { 11680 completeRequest(callback, status, callbacks[callbackId].data, "", text); 11681 callbacks[callbackId] = noop; 11682 }); 11683 } else { 11684 11685 var xhr = createXhr(method, url); 11686 11687 xhr.open(method, url, true); 11688 forEach(headers, function(value, key) { 11689 if (isDefined(value)) { 11690 xhr.setRequestHeader(key, value); 11691 } 11692 }); 11693 11694 xhr.onload = function requestLoaded() { 11695 var statusText = xhr.statusText || ''; 11696 11697 // responseText is the old-school way of retrieving response (supported by IE9) 11698 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 11699 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 11700 11701 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 11702 var status = xhr.status === 1223 ? 204 : xhr.status; 11703 11704 // fix status code when it is 0 (0 status is undocumented). 11705 // Occurs when accessing file resources or on Android 4.1 stock browser 11706 // while retrieving files from application cache. 11707 if (status === 0) { 11708 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 11709 } 11710 11711 completeRequest(callback, 11712 status, 11713 response, 11714 xhr.getAllResponseHeaders(), 11715 statusText); 11716 }; 11717 11718 var requestError = function() { 11719 // The response is always empty 11720 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 11721 completeRequest(callback, -1, null, null, ''); 11722 }; 11723 11724 xhr.onerror = requestError; 11725 xhr.onabort = requestError; 11726 11727 forEach(eventHandlers, function(value, key) { 11728 xhr.addEventListener(key, value); 11729 }); 11730 11731 forEach(uploadEventHandlers, function(value, key) { 11732 xhr.upload.addEventListener(key, value); 11733 }); 11734 11735 if (withCredentials) { 11736 xhr.withCredentials = true; 11737 } 11738 11739 if (responseType) { 11740 try { 11741 xhr.responseType = responseType; 11742 } catch (e) { 11743 // WebKit added support for the json responseType value on 09/03/2013 11744 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 11745 // known to throw when setting the value "json" as the response type. Other older 11746 // browsers implementing the responseType 11747 // 11748 // The json response type can be ignored if not supported, because JSON payloads are 11749 // parsed on the client-side regardless. 11750 if (responseType !== 'json') { 11751 throw e; 11752 } 11753 } 11754 } 11755
11756 xhr.send(isUndefined(post) ? null : post); 11757 } 11758 11759 if (timeout > 0) { 11760 var timeoutId = $browserDefer(timeoutRequest, timeout); 11761 } else if (isPromiseLike(timeout)) { 11762 timeout.then(timeoutRequest); 11763 } 11764 11765 11766 function timeoutRequest() { 11767 jsonpDone && jsonpDone(); 11768 xhr && xhr.abort(); 11769 } 11770 11771 function completeRequest(callback, status, response, headersString, statusText) { 11772 // cancel timeout and subsequent timeout promise resolution 11773 if (isDefined(timeoutId)) { 11774 $browserDefer.cancel(timeoutId); 11775 } 11776 jsonpDone = xhr = null; 11777 11778 callback(status, response, headersString, statusText); 11779 $browser.$$completeOutstandingRequest(noop); 11780 } 11781 }; 11782 11783 function jsonpReq(url, callbackId, done) { 11784 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 11785 // - fetches local scripts via XHR and evals them 11786 // - adds and immediately removes script elements from the document 11787 var script = rawDocument.createElement('script'), callback = null; 11788 script.type = "text/javascript"; 11789 script.src = url; 11790 script.async = true; 11791 11792 callback = function(event) { 11793 removeEventListenerFn(script, "load", callback); 11794 removeEventListenerFn(script, "error", callback); 11795 rawDocument.body.removeChild(script); 11796 script = null; 11797 var status = -1; 11798 var text = "unknown"; 11799 11800 if (event) { 11801 if (event.type === "load" && !callbacks[callbackId].called) { 11802 event = { type: "error" }; 11803 } 11804 text = event.type; 11805 status = event.type === "error" ? 404 : 200; 11806 } 11807 11808 if (done) { 11809 done(status, text); 11810 } 11811 }; 11812 11813 addEventListenerFn(script, "load", callback); 11814 addEventListenerFn(script, "error", callback); 11815 rawDocument.body.appendChild(script); 11816 return callback; 11817 } 11818} 11819 11820var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 11821$interpolateMinErr.throwNoconcat = function(text) { 11822 throw $interpolateMinErr('noconcat', 11823 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 11824 "interpolations that concatenate multiple expressions when a trusted value is " + 11825 "required. See http://docs.angularjs.org/api/ng.$sce", text); 11826}; 11827 11828$interpolateMinErr.interr = function(text, err) { 11829 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 11830}; 11831 11832/** 11833 * @ngdoc provider 11834 * @name $interpolateProvider 11835 * 11836 * @description 11837 *
11838 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 11839 * 11840 * <div class="alert alert-danger"> 11841 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular 11842 * template within a Python Jinja template (or any other template language). Mixing templating 11843 * languages is **very dangerous**. The embedding template language will not safely escape Angular 11844 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 11845 * security bugs! 11846 * </div> 11847 * 11848 * @example 11849<example name="custom-interpolation-markup" module="customInterpolationApp"> 11850<file name="index.html"> 11851<script> 11852 var customInterpolationApp = angular.module('customInterpolationApp', []); 11853 11854 customInterpolationApp.config(function($interpolateProvider) { 11855 $interpolateProvider.startSymbol('//'); 11856 $interpolateProvider.endSymbol('//'); 11857 }); 11858 11859 11860 customInterpolationApp.controller('DemoController', function() { 11861 this.label = "This binding is brought you by // interpolation symbols."; 11862 }); 11863</script> 11864<div ng-controller="DemoController as demo"> 11865 //demo.label// 11866</div> 11867</file> 11868<file name="protractor.js" type="protractor"> 11869 it('should interpolate binding with custom symbols', function() { 11870 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 11871 }); 11872</file> 11873</example> 11874 */ 11875function $InterpolateProvider() { 11876 var startSymbol = '{{'; 11877 var endSymbol = '}}'; 11878 11879 /** 11880 * @ngdoc method 11881 * @name $interpolateProvider#startSymbol 11882 * @description 11883 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 11884 * 11885 * @param {string=} value new value to set the starting symbol to. 11886 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11887 */ 11888 this.startSymbol = function(value) { 11889 if (value) { 11890 startSymbol = value; 11891 return this; 11892 } else { 11893 return startSymbol; 11894 } 11895 }; 11896 11897 /** 11898 * @ngdoc method 11899 * @name $interpolateProvider#endSymbol 11900 * @description 11901 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11902 * 11903 * @param {string=} value new value to set the ending symbol to. 11904 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11905 */ 11906 this.endSymbol = function(value) { 11907 if (value) { 11908 endSymbol = value; 11909 return this; 11910 } else { 11911 return endSymbol; 11912 } 11913 }; 11914 11915 11916 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 11917 var startSymbolLength = startSymbol.length, 11918 endSymbolLength = endSymbol.length, 11919 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 11920 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 11921 11922 function escape(ch) { 11923 return '\\\\\\' + ch; 11924 } 11925 11926 function unescapeText(text) { 11927 return text.replace(escapedStartRegexp, startSymbol). 11928 replace(escapedEndRegexp, endSymbol); 11929 } 11930 11931 function stringify(value) { 11932 if (value == null) { // null || undefined 11933 return ''; 11934 } 11935 switch (typeof value) { 11936 case 'string': 11937 break; 11938 case 'number': 11939 value = '' + value; 11940 break; 11941 default: 11942 value = toJson(value); 11943 } 11944 11945 return value; 11946 } 11947 11948 //TODO: this is the same as the constantWatchDelegate in parse.js 11949 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 11950 var unwatch; 11951 return unwatch = scope.$watch(function constantInterpolateWatch(scope) { 11952 unwatch(); 11953 return constantInterp(scope); 11954 }, listener, objectEquality); 11955 } 11956 11957 /** 11958 * @ngdoc service 11959 * @name $interpolate 11960 * @kind function 11961 * 11962 * @requires $parse 11963 * @requires $sce 11964 * 11965 * @description 11966 * 11967 * Compiles a string with markup into an interpolation function. This service is used by the 11968 * HTML {@link ng.$compile $compile} service for data binding. See 11969 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 11970 * interpolation markup. 11971 * 11972 * 11973 * ```js 11974 * var $interpolate = ...; // injected 11975 * var exp = $interpolate('Hello {{name | uppercase}}!'); 11976 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 11977 * ``` 11978 * 11979 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 11980 * `true`, the interpolation function will return `undefined` unless all embedded expressions 11981 * evaluate to a value other than `undefined`. 11982 * 11983 * ```js 11984 * var $interpolate = ...; // injected 11985 * var context = {greeting: 'Hello', name: undefined }; 11986 * 11987 * // default "forgiving" mode 11988 * var exp = $interpolate('{{greeting}} {{name}}!'); 11989 * expect(exp(context)).toEqual('Hello !'); 11990 * 11991 * // "allOrNothing" mode 11992 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 11993 * expect(exp(context)).toBeUndefined(); 11994 * context.name = 'Angular'; 11995 * expect(exp(context)).toEqual('Hello Angular!'); 11996 * ``` 11997 * 11998 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 11999 * 12000 * ####Escaped Interpolation 12001 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 12002 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 12003 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 12004 * or binding. 12005 * 12006 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 12007 * degree, while also enabling code examples to work without relying on the 12008 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 12009 * 12010 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 12011 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 12012 * interpolation start/end markers with their escaped counterparts.** 12013 * 12014 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 12015 * output when the $interpolate service processes the text. So, for HTML elements interpolated 12016 * by {@link ng.$compile $compile}
12016, or otherwise interpolated with the `mustHaveExpression` parameter 12017 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 12018 * this is typically useful only when user-data is used in rendering a template from the server, or 12019 * when otherwise untrusted data is used by a directive. 12020 * 12021 * <example> 12022 * <file name="index.html"> 12023 * <div ng-init="username='A user'"> 12024 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 12025 * </p> 12026 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 12027 * application, but fails to accomplish their task, because the server has correctly 12028 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 12029 * characters.</p> 12030 * <p>Instead, the result of the attempted script injection is visible, and can be removed 12031 * from the database by an administrator.</p> 12032 * </div> 12033 * </file> 12034 * </example> 12035 * 12036 * @param {string} text The text with markup to interpolate. 12037 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 12038 * embedded expression in order to return an interpolation function. Strings with no 12039 * embedded expression will return null for the interpolation function. 12040 * @param {string=} trustedContext when provided, the returned function passes the interpolated 12041 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 12042 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 12043 * provides Strict Contextual Escaping for details. 12044 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 12045 * unless all embedded expressions evaluate to a value other than `undefined`. 12046 * @returns {function(context)} an interpolation function which is used to compute the 12047 * interpolated string. The function has these parameters: 12048 * 12049 * - `context`: evaluation context for all expressions embedded in the interpolated text 12050 */ 12051 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 12052 // Provide a quick exit and simplified result function for text with no interpolation 12053 if (!text.length || text.indexOf(startSymbol) === -1) { 12054 var constantInterp; 12055 if (!mustHaveExpression) { 12056 var unescapedText = unescapeText(text); 12057 constantInterp = valueFn(unescapedText); 12058 constantInterp.exp = text; 12059 constantInterp.expressions = []; 12060 constantInterp.$$watchDelegate = constantWatchDelegate; 12061 } 12062 return constantInterp; 12063 } 12064 12065 allOrNothing = !!allOrNothing; 12066 var startIndex, 12067 endIndex, 12068 index = 0, 12069 expressions = [], 12070 parseFns = [], 12071 textLength = text.length, 12072 exp, 12073 concat = [], 12074 expressionPositions = []; 12075 12076 while (index < textLength) { 12077 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 12078 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 12079 if (index !== startIndex) { 12080 concat.push(unescapeText(text.substring(index, startIndex))); 12081 } 12082 exp = text.substring(startIndex + startSymbolLength, endIndex); 12083 expressions.push(exp); 12084 parseFns.push($parse(exp, parseStringifyInterceptor)); 12085 index = endIndex + endSymbolLength; 12086 expressionPositions.push(concat.length); 12087 concat.push(''); 12088 } else { 12089 // we did not find an interpolation, so we have to add the remainder to the separators array 12090 if (index !== textLength) { 12091 concat.push(unescapeText(text.substring(index))); 12092 } 12093 break; 12094 } 12095 } 12096 12097 // Concatenating expressions makes it hard to reason about whether some combination of 12098 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 12099 // single expression be used for iframe[src], object[src], etc., we ensure that the value 12100 // that's used is assigned or constructed by some JS code somewhere that is more testable or 12101 // make it obvious that you bound the value to some user controlled value. This helps reduce 12102 // the load when auditing for XSS issues. 12103 if (trustedContext && concat.length > 1) { 12104 $interpolateMinErr.throwNoconcat(text); 12105 } 12106 12107 if (!mustHaveExpression || expressions.length) { 12108 var compute = function(values) { 12109 for (var i = 0, ii = expressions.length; i < ii; i++) { 12110 if (allOrNothing && isUndefined(values[i])) return; 12111 concat[expressionPositions[i]] = values[i]; 12112 } 12113 return concat.join(''); 12114 }; 12115 12116 var getValue = function(value) { 12117 return trustedContext ? 12118 $sce.getTrusted(trustedContext, value) : 12119 $sce.valueOf(value); 12120 }; 12121 12122 return extend(function interpolationFn(context) { 12123 var i = 0; 12124 var ii = expressions.length; 12125 var values = new Array(ii); 12126 12127 try { 12128 for (; i < ii; i++) { 12129 values[i] = parseFns[i](context); 12130 } 12131 12132 return compute(values); 12133 } catch (err) { 12134 $exceptionHandler($interpolateMinErr.interr(text, err)); 12135 } 12136 12137 }, { 12138 // all of these properties are undocumented for now 12139 exp: text, //just for compatibility with regular watchers created via $watch 12140 expressions: expressions, 12141 $$watchDelegate: function(scope, listener) { 12142 var lastValue; 12143 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 12144 var currValue = compute(values); 12145 if (isFunction(listener)) { 12146 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 12147 }
12148 lastValue = currValue; 12149 }); 12150 } 12151 }); 12152 } 12153 12154 function parseStringifyInterceptor(value) { 12155 try { 12156 value = getValue(value); 12157 return allOrNothing && !isDefined(value) ? value : stringify(value); 12158 } catch (err) { 12159 $exceptionHandler($interpolateMinErr.interr(text, err)); 12160 } 12161 } 12162 } 12163 12164 12165 /** 12166 * @ngdoc method 12167 * @name $interpolate#startSymbol 12168 * @description 12169 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 12170 * 12171 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 12172 * the symbol. 12173 * 12174 * @returns {string} start symbol. 12175 */ 12176 $interpolate.startSymbol = function() { 12177 return startSymbol; 12178 }; 12179 12180 12181 /** 12182 * @ngdoc method 12183 * @name $interpolate#endSymbol 12184 * @description 12185 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 12186 * 12187 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 12188 * the symbol. 12189 * 12190 * @returns {string} end symbol. 12191 */ 12192 $interpolate.endSymbol = function() { 12193 return endSymbol; 12194 }; 12195 12196 return $interpolate; 12197 }]; 12198} 12199 12200function $IntervalProvider() { 12201 this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', 12202 function($rootScope, $window, $q, $$q, $browser) { 12203 var intervals = {}; 12204 12205 12206 /** 12207 * @ngdoc service 12208 * @name $interval 12209 * 12210 * @description 12211 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 12212 * milliseconds. 12213 * 12214 * The return value of registering an interval function is a promise. This promise will be 12215 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 12216 * run indefinitely if `count` is not defined. The value of the notification will be the 12217 * number of iterations that have run. 12218 * To cancel an interval, call `$interval.cancel(promise)`. 12219 * 12220 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 12221 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 12222 * time. 12223 * 12224 * <div class="alert alert-warning"> 12225 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 12226 * with them. In particular they are not automatically destroyed when a controller's scope or a 12227 * directive's element are destroyed. 12228 * You should take this into consideration and make sure to always cancel the interval at the 12229 * appropriate moment. See the example below for more details on how and when to do this. 12230 * </div> 12231 * 12232 * @param {function()} fn A function that should be called repeatedly. 12233 * @param {number} delay Number of milliseconds between each function call. 12234 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 12235 * indefinitely. 12236 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 12237 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 12238 * @param {...*=} Pass additional parameters to the executed function. 12239 * @returns {promise} A promise which will be notified on each iteration. 12240 * 12241 * @example 12242 * <example module="intervalExample"> 12243 * <file name="index.html"> 12244 * <script> 12245 * angular.module('intervalExample', []) 12246 * .controller('ExampleController', ['$scope', '$interval', 12247 * function($scope, $interval) { 12248 * $scope.format = 'M/d/yy h:mm:ss a'; 12249 * $scope.blood_1 = 100; 12250 * $scope.blood_2 = 120; 12251 * 12252 * var stop; 12253 * $scope.fight = function() { 12254 * // Don't start a new fight if we are already fighting 12255 * if ( angular.isDefined(stop) ) return; 12256 * 12257 * stop = $interval(function() { 12258 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 12259 * $scope.blood_1 = $scope.blood_1 - 3; 12260 * $scope.blood_2 = $scope.blood_2 - 4; 12261 * } else { 12262 * $scope.stopFight(); 12263 * } 12264 * }, 100); 12265 * }; 12266 * 12267 * $scope.stopFight = function() { 12268 * if (angular.isDefined(stop)) { 12269 * $interval.cancel(stop); 12270 * stop = undefined; 12271 * } 12272 * }; 12273 * 12274 * $scope.resetFight = function() { 12275 * $scope.blood_1 = 100; 12276 * $scope.blood_2 = 120; 12277 * }; 12278 * 12279 * $scope.$on('$destroy', function() { 12280 * // Make sure that the interval is destroyed too 12281 * $scope.stopFight(); 12282 * }); 12283 * }]) 12284 * // Register the 'myCurrentTime' directive factory method. 12285 * // We inject $interval and dateFilter service since the factory method is DI. 12286 * .directive('myCurrentTime', ['$interval', 'dateFilter', 12287 * function($interval, dateFilter) { 12288 * // return the directive link function. (compile function not needed) 12289 * return function(scope, element, attrs) { 12290 * var format, // date format 12291 * stopTime; // so that we can cancel the time updates 12292 * 12293 * // used to update the UI 12294 * function updateTime() { 12295 * element.text(dateFilter(new Date(), format)); 12296 * } 12297 * 12298 * // watch the expression, and update the UI on change. 12299 * scope.$watch(attrs.myCurrentTime, function(value) { 12300 * format = value; 12301 * updateTime(); 12302 * }); 12303 * 12304 * stopTime = $interval(updateTime, 1000); 12305 * 12306 * // listen on DOM destroy (removal) event, and cancel the next UI update 12307 * // to prevent updating time after the DOM element was removed. 12308 * element.on('$destroy', function() { 12309 * $interval.cancel(stopTime); 12310 * }); 12311 * } 12312 * }]); 12313 * </script> 12314 * 12315 * <div>
12316 * <div ng-controller="ExampleController"> 12317 * <label>Date format: <input ng-model="format"></label> <hr/> 12318 * Current time is: <span my-current-time="format"></span> 12319 * <hr/> 12320 * Blood 1 : <font color='red'>{{blood_1}}</font> 12321 * Blood 2 : <font color='red'>{{blood_2}}</font> 12322 * <button type="button" data-ng-click="fight()">Fight</button> 12323 * <button type="button" data-ng-click="stopFight()">StopFight</button> 12324 * <button type="button" data-ng-click="resetFight()">resetFight</button> 12325 * </div> 12326 * </div> 12327 * 12328 * </file> 12329 * </example> 12330 */ 12331 function interval(fn, delay, count, invokeApply) { 12332 var hasParams = arguments.length > 4, 12333 args = hasParams ? sliceArgs(arguments, 4) : [], 12334 setInterval = $window.setInterval, 12335 clearInterval = $window.clearInterval, 12336 iteration = 0, 12337 skipApply = (isDefined(invokeApply) && !invokeApply), 12338 deferred = (skipApply ? $$q : $q).defer(), 12339 promise = deferred.promise; 12340 12341 count = isDefined(count) ? count : 0; 12342 12343 promise.$$intervalId = setInterval(function tick() { 12344 if (skipApply) { 12345 $browser.defer(callback); 12346 } else { 12347 $rootScope.$evalAsync(callback); 12348 } 12349 deferred.notify(iteration++); 12350 12351 if (count > 0 && iteration >= count) { 12352 deferred.resolve(iteration); 12353 clearInterval(promise.$$intervalId); 12354 delete intervals[promise.$$intervalId]; 12355 } 12356 12357 if (!skipApply) $rootScope.$apply(); 12358 12359 }, delay); 12360 12361 intervals[promise.$$intervalId] = deferred; 12362 12363 return promise; 12364 12365 function callback() { 12366 if (!hasParams) { 12367 fn(iteration); 12368 } else { 12369 fn.apply(null, args); 12370 } 12371 } 12372 } 12373 12374 12375 /** 12376 * @ngdoc method 12377 * @name $interval#cancel 12378 * 12379 * @description 12380 * Cancels a task associated with the `promise`. 12381 * 12382 * @param {Promise=} promise returned by the `$interval` function. 12383 * @returns {boolean} Returns `true` if the task was successfully canceled. 12384 */ 12385 interval.cancel = function(promise) { 12386 if (promise && promise.$$intervalId in intervals) { 12387 intervals[promise.$$intervalId].reject('canceled'); 12388 $window.clearInterval(promise.$$intervalId); 12389 delete intervals[promise.$$intervalId]; 12390 return true; 12391 } 12392 return false; 12393 }; 12394 12395 return interval; 12396 }]; 12397} 12398 12399/** 12400 * @ngdoc service 12401 * @name $locale 12402 * 12403 * @description 12404 * $locale service provides localization rules for various Angular components. As of right now the 12405 * only public api is: 12406 * 12407 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) 12408 */ 12409 12410var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 12411 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 12412var $locationMinErr = minErr('$location'); 12413 12414 12415/** 12416 * Encode path using encodeUriSegment, ignoring forward slashes 12417 * 12418 * @param {string} path Path to encode 12419 * @returns {string} 12420 */ 12421function encodePath(path) { 12422 var segments = path.split('/'), 12423 i = segments.length; 12424 12425 while (i--) { 12426 segments[i] = encodeUriSegment(segments[i]); 12427 } 12428 12429 return segments.join('/'); 12430} 12431 12432function parseAbsoluteUrl(absoluteUrl, locationObj) { 12433 var parsedUrl = urlResolve(absoluteUrl); 12434 12435 locationObj.$$protocol = parsedUrl.protocol; 12436 locationObj.$$host = parsedUrl.hostname; 12437 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 12438} 12439 12440 12441function parseAppUrl(relativeUrl, locationObj) { 12442 var prefixed = (relativeUrl.charAt(0) !== '/'); 12443 if (prefixed) { 12444 relativeUrl = '/' + relativeUrl; 12445 } 12446 var match = urlResolve(relativeUrl); 12447 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 12448 match.pathname.substring(1) : match.pathname); 12449 locationObj.$$search = parseKeyValue(match.search); 12450 locationObj.$$hash = decodeURIComponent(match.hash); 12451 12452 // make sure path starts with '/'; 12453 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 12454 locationObj.$$path = '/' + locationObj.$$path; 12455 } 12456} 12457 12458 12459/** 12460 * 12461 * @param {string} begin 12462 * @param {string} whole 12463 * @returns {string} returns text from whole after begin or undefined if it does not begin with 12464 * expected string. 12465 */ 12466function beginsWith(begin, whole) { 12467 if (whole.indexOf(begin) === 0) { 12468 return whole.substr(begin.length); 12469 } 12470} 12471 12472 12473function stripHash(url) { 12474 var index = url.indexOf('#'); 12475 return index == -1 ? url : url.substr(0, index); 12476} 12477 12478function trimEmptyHash(url) { 12479 return url.replace(/(#.+)|#$/, '$1'); 12480} 12481 12482 12483function stripFile(url) { 12484 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 12485} 12486 12487/* return the server only (scheme://host:port) */ 12488function serverBase(url) { 12489 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 12490} 12491 12492 12493/** 12494 * LocationHtml5Url represents an url 12495 * This object is exposed as $location service when HTML5 mode is enabled and supported 12496 * 12497 * @constructor 12498 * @param {string} appBase application base URL 12499 * @param {string} appBaseNoFile application base URL stripped of any filename 12500 * @param {string} basePrefix url path prefix 12501 */ 12502function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 12503 this.$$html5 = true; 12504 basePrefix = basePrefix || ''; 12505 parseAbsoluteUrl(appBase, this); 12506 12507 12508 /** 12509 * Parse given html5 (regular) url string into properties 12510 * @param {string} url HTML5 url 12511 * @private 12512 */ 12513 this.$$parse = function(url) { 12514 var pathUrl = beginsWith(appBaseNoFile, url); 12515 if (!isString(pathUrl)) { 12516 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 12517 appBaseNoFile); 12518 } 12519 12520 parseAppUrl(pathUrl, this); 12521 12522 if (!this.$$path) { 12523 this.$$path = '/'; 12524 } 12525 12526 this.$$compose(); 12527 }; 12528 12529 /** 12530 * Compose url and update `absUrl` property 12531 * @private 12532 */ 12533 this.$$compose = function() {
vendor: 4,177 bytes, lines 12534-12665
12534 var search = toKeyValue(this.$$search), 12535 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 12536 12537 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 12538 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 12539 }; 12540 12541 this.$$parseLinkUrl = function(url, relHref) { 12542 if (relHref && relHref[0] === '#') { 12543 // special case for links to hash fragments: 12544 // keep the old url and only replace the hash fragment 12545 this.hash(relHref.slice(1)); 12546 return true; 12547 } 12548 var appUrl, prevAppUrl; 12549 var rewrittenUrl; 12550 12551 if (isDefined(appUrl = beginsWith(appBase, url))) { 12552 prevAppUrl = appUrl; 12553 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 12554 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 12555 } else { 12556 rewrittenUrl = appBase + prevAppUrl; 12557 } 12558 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 12559 rewrittenUrl = appBaseNoFile + appUrl; 12560 } else if (appBaseNoFile == url + '/') { 12561 rewrittenUrl = appBaseNoFile; 12562 } 12563 if (rewrittenUrl) { 12564 this.$$parse(rewrittenUrl); 12565 } 12566 return !!rewrittenUrl; 12567 }; 12568} 12569 12570 12571/** 12572 * LocationHashbangUrl represents url 12573 * This object is exposed as $location service when developer doesn't opt into html5 mode. 12574 * It also serves as the base class for html5 mode fallback on legacy browsers. 12575 * 12576 * @constructor 12577 * @param {string} appBase application base URL 12578 * @param {string} appBaseNoFile application base URL stripped of any filename 12579 * @param {string} hashPrefix hashbang prefix 12580 */ 12581function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 12582 12583 parseAbsoluteUrl(appBase, this); 12584 12585 12586 /** 12587 * Parse given hashbang url into properties 12588 * @param {string} url Hashbang url 12589 * @private 12590 */ 12591 this.$$parse = function(url) { 12592 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 12593 var withoutHashUrl; 12594 12595 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 12596 12597 // The rest of the url starts with a hash so we have 12598 // got either a hashbang path or a plain hash fragment 12599 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 12600 if (isUndefined(withoutHashUrl)) { 12601 // There was no hashbang prefix so we just have a hash fragment 12602 withoutHashUrl = withoutBaseUrl; 12603 } 12604 12605 } else { 12606 // There was no hashbang path nor hash fragment: 12607 // If we are in HTML5 mode we use what is left as the path; 12608 // Otherwise we ignore what is left 12609 if (this.$$html5) { 12610 withoutHashUrl = withoutBaseUrl; 12611 } else { 12612 withoutHashUrl = ''; 12613 if (isUndefined(withoutBaseUrl)) { 12614 appBase = url; 12615 this.replace(); 12616 } 12617 } 12618 } 12619 12620 parseAppUrl(withoutHashUrl, this); 12621 12622 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 12623 12624 this.$$compose(); 12625 12626 /* 12627 * In Windows, on an anchor node on documents loaded from 12628 * the filesystem, the browser will return a pathname 12629 * prefixed with the drive name ('/C:/path') when a 12630 * pathname without a drive is set: 12631 * * a.setAttribute('href', '/foo') 12632 * * a.pathname === '/C:/foo' //true 12633 * 12634 * Inside of Angular, we're always using pathnames that 12635 * do not include drive names for routing. 12636 */ 12637 function removeWindowsDriveName(path, url, base) { 12638 /* 12639 Matches paths for file protocol on windows, 12640 such as /C:/foo/bar, and captures only /foo/bar. 12641 */ 12642 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 12643 12644 var firstPathSegmentMatch; 12645 12646 //Get the relative path from the input URL. 12647 if (url.indexOf(base) === 0) { 12648 url = url.replace(base, ''); 12649 } 12650 12651 // The input URL intentionally contains a first path segment that ends with a colon. 12652 if (windowsFilePathExp.exec(url)) { 12653 return path; 12654 } 12655 12656 firstPathSegmentMatch = windowsFilePathExp.exec(path); 12657 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 12658 } 12659 }; 12660 12661 /** 12662 * Compose hashbang url and update `absUrl` property 12663 * @private 12664 */ 12665 this.$$compose = function() {
12666 var search = toKeyValue(this.$$search), 12667 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 12668 12669 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 12670 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 12671 }; 12672 12673 this.$$parseLinkUrl = function(url, relHref) { 12674 if (stripHash(appBase) == stripHash(url)) { 12675 this.$$parse(url); 12676 return true; 12677 } 12678 return false; 12679 }; 12680} 12681 12682 12683/** 12684 * LocationHashbangUrl represents url 12685 * This object is exposed as $location service when html5 history api is enabled but the browser 12686 * does not support it. 12687 * 12688 * @constructor 12689 * @param {string} appBase application base URL 12690 * @param {string} appBaseNoFile application base URL stripped of any filename 12691 * @param {string} hashPrefix hashbang prefix 12692 */ 12693function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 12694 this.$$html5 = true; 12695 LocationHashbangUrl.apply(this, arguments); 12696 12697 this.$$parseLinkUrl = function(url, relHref) { 12698 if (relHref && relHref[0] === '#') { 12699 // special case for links to hash fragments: 12700 // keep the old url and only replace the hash fragment 12701 this.hash(relHref.slice(1)); 12702 return true; 12703 } 12704 12705 var rewrittenUrl; 12706 var appUrl; 12707 12708 if (appBase == stripHash(url)) { 12709 rewrittenUrl = url; 12710 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 12711 rewrittenUrl = appBase + hashPrefix + appUrl; 12712 } else if (appBaseNoFile === url + '/') { 12713 rewrittenUrl = appBaseNoFile; 12714 } 12715 if (rewrittenUrl) { 12716 this.$$parse(rewrittenUrl); 12717 } 12718 return !!rewrittenUrl; 12719 }; 12720 12721 this.$$compose = function() { 12722 var search = toKeyValue(this.$$search), 12723 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 12724 12725 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 12726 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 12727 this.$$absUrl = appBase + hashPrefix + this.$$url; 12728 }; 12729 12730} 12731 12732 12733var locationPrototype = { 12734 12735 /** 12736 * Are we in html5 mode? 12737 * @private 12738 */ 12739 $$html5: false, 12740 12741 /** 12742 * Has any change been replacing? 12743 * @private 12744 */ 12745 $$replace: false, 12746 12747 /** 12748 * @ngdoc method 12749 * @name $location#absUrl 12750 * 12751 * @description 12752 * This method is getter only. 12753 * 12754 * Return full url representation with all segments encoded according to rules specified in 12755 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 12756 * 12757 * 12758 * ```js 12759 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12760 * var absUrl = $location.absUrl(); 12761 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 12762 * ``` 12763 * 12764 * @return {string} full url 12765 */ 12766 absUrl: locationGetter('$$absUrl'), 12767 12768 /** 12769 * @ngdoc method 12770 * @name $location#url 12771 * 12772 * @description 12773 * This method is getter / setter. 12774 * 12775 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 12776 * 12777 * Change path, search and hash, when called with parameter and return `$location`. 12778 * 12779 * 12780 * ```js 12781 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12782 * var url = $location.url(); 12783 * // => "/some/path?foo=bar&baz=xoxo" 12784 * ``` 12785 * 12786 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 12787 * @return {string} url 12788 */ 12789 url: function(url) { 12790 if (isUndefined(url)) { 12791 return this.$$url; 12792 } 12793 12794 var match = PATH_MATCH.exec(url); 12795 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 12796 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 12797 this.hash(match[5] || ''); 12798 12799 return this; 12800 }, 12801 12802 /** 12803 * @ngdoc method 12804 * @name $location#protocol 12805 * 12806 * @description 12807 * This method is getter only. 12808 * 12809 * Return protocol of current url. 12810 * 12811 * 12812 * ```js 12813 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12814 * var protocol = $location.protocol(); 12815 * // => "http" 12816 * ``` 12817 * 12818 * @return {string} protocol of current url 12819 */ 12820 protocol: locationGetter('$$protocol'), 12821 12822 /** 12823 * @ngdoc method 12824 * @name $location#host 12825 * 12826 * @description 12827 * This method is getter only. 12828 * 12829 * Return host of current url. 12830 * 12831 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 12832 * 12833 * 12834 * ```js
12835 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12836 * var host = $location.host(); 12837 * // => "example.com" 12838 * 12839 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 12840 * host = $location.host(); 12841 * // => "example.com" 12842 * host = location.host; 12843 * // => "example.com:8080" 12844 * ``` 12845 * 12846 * @return {string} host of current url. 12847 */ 12848 host: locationGetter('$$host'), 12849 12850 /** 12851 * @ngdoc method 12852 * @name $location#port 12853 * 12854 * @description 12855 * This method is getter only. 12856 * 12857 * Return port of current url. 12858 * 12859 * 12860 * ```js 12861 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12862 * var port = $location.port(); 12863 * // => 80 12864 * ``` 12865 * 12866 * @return {Number} port 12867 */ 12868 port: locationGetter('$$port'), 12869 12870 /** 12871 * @ngdoc method 12872 * @name $location#path 12873 * 12874 * @description 12875 * This method is getter / setter. 12876 * 12877 * Return path of current url when called without any parameter. 12878 * 12879 * Change path when called with parameter and return `$location`. 12880 * 12881 * Note: Path should always begin with forward slash (/), this method will add the forward slash 12882 * if it is missing. 12883 * 12884 * 12885 * ```js 12886 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12887 * var path = $location.path(); 12888 * // => "/some/path" 12889 * ``` 12890 * 12891 * @param {(string|number)=} path New path 12892 * @return {string} path 12893 */ 12894 path: locationGetterSetter('$$path', function(path) { 12895 path = path !== null ? path.toString() : ''; 12896 return path.charAt(0) == '/' ? path : '/' + path; 12897 }), 12898 12899 /** 12900 * @ngdoc method 12901 * @name $location#search 12902 * 12903 * @description 12904 * This method is getter / setter. 12905 * 12906 * Return search part (as object) of current url when called without any parameter. 12907 * 12908 * Change search part when called with parameter and return `$location`. 12909 * 12910 * 12911 * ```js 12912 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12913 * var searchObject = $location.search(); 12914 * // => {foo: 'bar', baz: 'xoxo'} 12915 * 12916 * // set foo to 'yipee' 12917 * $location.search('foo', 'yipee'); 12918 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 12919 * ``` 12920 * 12921 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 12922 * hash object. 12923 * 12924 * When called with a single argument the method acts as a setter, setting the `search` component 12925 * of `$location` to the specified value. 12926 * 12927 * If the argument is a hash object containing an array of values, these values will be encoded 12928 * as duplicate search parameters in the url. 12929 * 12930 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 12931 * will override only a single search property. 12932 * 12933 * If `paramValue` is an array, it will override the property of the `search` component of 12934 * `$location` specified via the first argument. 12935 * 12936 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 12937 * 12938 * If `paramValue` is `true`, the property specified via the first argument will be added with no 12939 * value nor trailing equal sign. 12940 * 12941 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 12942 * one or more arguments returns `$location` object itself. 12943 */ 12944 search: function(search, paramValue) { 12945 switch (arguments.length) { 12946 case 0: 12947 return this.$$search; 12948 case 1: 12949 if (isString(search) || isNumber(search)) { 12950 search = search.toString(); 12951 this.$$search = parseKeyValue(search); 12952 } else if (isObject(search)) { 12953 search = copy(search, {}); 12954 // remove object undefined or null properties 12955 forEach(search, function(value, key) { 12956 if (value == null) delete search[key]; 12957 }); 12958 12959 this.$$search = search; 12960 } else { 12961 throw $locationMinErr('isrcharg', 12962 'The first argument of the `$location#search()` call must be a string or an object.'); 12963 } 12964 break; 12965 default: 12966 if (isUndefined(paramValue) || paramValue === null) { 12967 delete this.$$search[search]; 12968 } else { 12969 this.$$search[search] = paramValue; 12970 } 12971 } 12972 12973 this.$$compose(); 12974 return this; 12975 }, 12976 12977 /** 12978 * @ngdoc method 12979 * @name $location#hash 12980 * 12981 * @description 12982 * This method is getter / setter. 12983 * 12984 * Returns the hash fragment when called without any parameters. 12985 * 12986 * Changes the hash fragment when called with a parameter and returns `$location`. 12987 * 12988 * 12989 * ```js
12990 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 12991 * var hash = $location.hash(); 12992 * // => "hashValue" 12993 * ``` 12994 * 12995 * @param {(string|number)=} hash New hash fragment 12996 * @return {string} hash 12997 */ 12998 hash: locationGetterSetter('$$hash', function(hash) { 12999 return hash !== null ? hash.toString() : ''; 13000 }), 13001 13002 /** 13003 * @ngdoc method 13004 * @name $location#replace 13005 * 13006 * @description 13007 * If called, all changes to $location during the current `$digest` will replace the current history 13008 * record, instead of adding a new one. 13009 */ 13010 replace: function() { 13011 this.$$replace = true; 13012 return this; 13013 } 13014}; 13015 13016forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 13017 Location.prototype = Object.create(locationPrototype); 13018 13019 /** 13020 * @ngdoc method 13021 * @name $location#state 13022 * 13023 * @description 13024 * This method is getter / setter. 13025 * 13026 * Return the history state object when called without any parameter. 13027 * 13028 * Change the history state object when called with one parameter and return `$location`. 13029 * The state object is later passed to `pushState` or `replaceState`. 13030 * 13031 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 13032 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 13033 * older browsers (like IE9 or Android < 4.0), don't use this method. 13034 * 13035 * @param {object=} state State object for pushState or replaceState 13036 * @return {object} state 13037 */ 13038 Location.prototype.state = function(state) { 13039 if (!arguments.length) { 13040 return this.$$state; 13041 } 13042 13043 if (Location !== LocationHtml5Url || !this.$$html5) { 13044 throw $locationMinErr('nostate', 'History API state support is available only ' + 13045 'in HTML5 mode and only in browsers supporting HTML5 History API'); 13046 } 13047 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 13048 // but we're changing the $$state reference to $browser.state() during the $digest 13049 // so the modification window is narrow. 13050 this.$$state = isUndefined(state) ? null : state; 13051 13052 return this; 13053 }; 13054}); 13055 13056 13057function locationGetter(property) { 13058 return function() { 13059 return this[property]; 13060 }; 13061} 13062 13063 13064function locationGetterSetter(property, preprocess) { 13065 return function(value) { 13066 if (isUndefined(value)) { 13067 return this[property]; 13068 } 13069 13070 this[property] = preprocess(value); 13071 this.$$compose(); 13072 13073 return this; 13074 }; 13075} 13076 13077 13078/** 13079 * @ngdoc service 13080 * @name $location 13081 * 13082 * @requires $rootElement 13083 * 13084 * @description 13085 * The $location service parses the URL in the browser address bar (based on the 13086 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 13087 * available to your application. Changes to the URL in the address bar are reflected into 13088 * $location service and changes to $location are reflected into the browser address bar. 13089 * 13090 * **The $location service:** 13091 * 13092 * - Exposes the current URL in the browser address bar, so you can 13093 * - Watch and observe the URL. 13094 * - Change the URL. 13095 * - Synchronizes the URL with the browser when the user 13096 * - Changes the address bar. 13097 * - Clicks the back or forward button (or clicks a History link). 13098 * - Clicks on a link. 13099 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 13100 * 13101 * For more information see {@link guide/$location Developer Guide: Using $location} 13102 */ 13103 13104/** 13105 * @ngdoc provider 13106 * @name $locationProvider 13107 * @description 13108 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 13109 */ 13110function $LocationProvider() { 13111 var hashPrefix = '', 13112 html5Mode = { 13113 enabled: false, 13114 requireBase: true, 13115 rewriteLinks: true 13116 }; 13117 13118 /** 13119 * @ngdoc method 13120 * @name $locationProvider#hashPrefix 13121 * @description 13122 * @param {string=} prefix Prefix for hash part (containing path and search) 13123 * @returns {*} current value if used as getter or itself (chaining) if used as setter 13124 */ 13125 this.hashPrefix = function(prefix) { 13126 if (isDefined(prefix)) { 13127 hashPrefix = prefix; 13128 return this; 13129 } else { 13130 return hashPrefix; 13131 } 13132 }; 13133 13134 /** 13135 * @ngdoc method 13136 * @name $locationProvider#html5Mode 13137 * @description 13138 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 13139 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 13140 * properties: 13141 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to 13142 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 13143 * support `pushState`. 13144 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 13145 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 13146 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 13147 * See the {@link guide/$location $location guide for more information} 13148 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 13149 * enables/disables url rewriting for relative links. 13150 * 13151 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 13152 */ 13153 this.html5Mode = function(mode) { 13154 if (isBoolean(mode)) { 13155 html5Mode.enabled = mode; 13156 return this; 13157 } else if (isObject(mode)) { 13158 13159 if (isBoolean(mode.enabled)) { 13160 html5Mode.enabled = mode.enabled; 13161 } 13162 13163 if (isBoolean(mode.requireBase)) { 13164 html5Mode.requireBase = mode.requireBase; 13165 } 13166 13167 if (isBoolean(mode.rewriteLinks)) { 13168 html5Mode.rewriteLinks = mode.rewriteLinks; 13169 } 13170 13171 return this; 13172 } else { 13173 return html5Mode; 13174 } 13175 }; 13176 13177 /** 13178 * @ngdoc event 13179 * @name $location#$locationChangeStart 13180 * @eventType broadcast on root scope 13181 * @description 13182 * Broadcasted before a URL will change. 13183 * 13184 * This change can be prevented by calling 13185 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 13186 * details about event object. Upon successful change 13187 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 13188 * 13189 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 13190 * the browser supports the HTML5 History API. 13191 * 13192 * @param {Object} angularEvent Synthetic event object. 13193 * @param {string} newUrl New URL 13194 * @param {string=} oldUrl URL that was before it was changed. 13195 * @param {string=} newState New history state object 13196 * @param {string=} oldState History state object that was before it was changed. 13197 */ 13198 13199 /** 13200 * @ngdoc event 13201 * @name $location#$locationChangeSuccess 13202 * @eventType broadcast on root scope 13203 * @description 13204 * Broadcasted after a URL was changed. 13205 * 13206 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 13207 * the browser supports the HTML5 History API. 13208 * 13209 * @param {Object} angularEvent Synthetic event object. 13210 * @param {string} newUrl New URL 13211 * @param {string=} oldUrl URL that was before it was changed. 13212 * @param {string=} newState New history state object 13213 * @param {string=} oldState History state object that was before it was changed. 13214 */ 13215 13216 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 13217 function($rootScope, $browser, $sniffer, $rootElement, $window) { 13218 var $location, 13219 LocationMode, 13220 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 13221 initialUrl = $browser.url(), 13222 appBase; 13223 13224 if (html5Mode.enabled) { 13225 if (!baseHref && html5Mode.requireBase) { 13226 throw $locationMinErr('nobase', 13227 "$location in HTML5 mode requires a <base> tag to be present!"); 13228 } 13229 appBase = serverBase(initialUrl) + (baseHref || '/'); 13230 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 13231 } else { 13232 appBase = stripHash(initialUrl); 13233 LocationMode = LocationHashbangUrl; 13234 } 13235 var appBaseNoFile = stripFile(appBase); 13236
13237 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 13238 $location.$$parseLinkUrl(initialUrl, initialUrl); 13239 13240 $location.$$state = $browser.state(); 13241 13242 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 13243 13244 function setBrowserUrlWithFallback(url, replace, state) { 13245 var oldUrl = $location.url(); 13246 var oldState = $location.$$state; 13247 try { 13248 $browser.url(url, replace, state); 13249 13250 // Make sure $location.state() returns referentially identical (not just deeply equal) 13251 // state object; this makes possible quick checking if the state changed in the digest 13252 // loop. Checking deep equality would be too expensive. 13253 $location.$$state = $browser.state(); 13254 } catch (e) { 13255 // Restore old values if pushState fails 13256 $location.url(oldUrl); 13257 $location.$$state = oldState; 13258 13259 throw e; 13260 } 13261 } 13262 13263 $rootElement.on('click', function(event) { 13264 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 13265 // currently we open nice url link and redirect then 13266 13267 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 13268 13269 var elm = jqLite(event.target); 13270 13271 // traverse the DOM up to find first A tag 13272 while (nodeName_(elm[0]) !== 'a') { 13273 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 13274 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 13275 } 13276 13277 var absHref = elm.prop('href'); 13278 // get the actual href attribute - see 13279 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 13280 var relHref = elm.attr('href') || elm.attr('xlink:href'); 13281 13282 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 13283 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 13284 // an animation. 13285 absHref = urlResolve(absHref.animVal).href; 13286 } 13287 13288 // Ignore when url is started with javascript: or mailto: 13289 if (IGNORE_URI_REGEXP.test(absHref)) return; 13290 13291 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 13292 if ($location.$$parseLinkUrl(absHref, relHref)) { 13293 // We do a preventDefault for all urls that are part of the angular application, 13294 // in html5mode and also without, so that we are able to abort navigation without 13295 // getting double entries in the location history. 13296 event.preventDefault(); 13297 // update location manually 13298 if ($location.absUrl() != $browser.url()) { 13299 $rootScope.$apply(); 13300 // hack to work around FF6 bug 684208 when scenario runner clicks on links 13301 $window.angular['ff-684208-preventDefault'] = true; 13302 } 13303 } 13304 } 13305 }); 13306 13307 13308 // rewrite hashbang url <> html5 url 13309 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 13310 $browser.url($location.absUrl(), true); 13311 } 13312 13313 var initializing = true; 13314 13315 // update $location when $browser url changes 13316 $browser.onUrlChange(function(newUrl, newState) { 13317 13318 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 13319 // If we are navigating outside of the app then force a reload 13320 $window.location.href = newUrl; 13321 return; 13322 } 13323 13324 $rootScope.$evalAsync(function() { 13325 var oldUrl = $location.absUrl(); 13326 var oldState = $location.$$state; 13327 var defaultPrevented; 13328 newUrl = trimEmptyHash(newUrl); 13329 $location.$$parse(newUrl); 13330 $location.$$state = newState; 13331 13332 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 13333 newState, oldState).defaultPrevented; 13334 13335 // if the location was changed by a `$locationChangeStart` handler then stop 13336 // processing this location change 13337 if ($location.absUrl() !== newUrl) return; 13338 13339 if (defaultPrevented) { 13340 $location.$$parse(oldUrl); 13341 $location.$$state = oldState; 13342 setBrowserUrlWithFallback(oldUrl, false, oldState); 13343 } else { 13344 initializing = false;
vendor: 4,210 bytes, lines 13345-13487
13345 afterLocationChange(oldUrl, oldState); 13346 } 13347 }); 13348 if (!$rootScope.$$phase) $rootScope.$digest(); 13349 }); 13350 13351 // update browser 13352 $rootScope.$watch(function $locationWatch() { 13353 var oldUrl = trimEmptyHash($browser.url()); 13354 var newUrl = trimEmptyHash($location.absUrl()); 13355 var oldState = $browser.state(); 13356 var currentReplace = $location.$$replace; 13357 var urlOrStateChanged = oldUrl !== newUrl || 13358 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 13359 13360 if (initializing || urlOrStateChanged) { 13361 initializing = false; 13362 13363 $rootScope.$evalAsync(function() { 13364 var newUrl = $location.absUrl(); 13365 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 13366 $location.$$state, oldState).defaultPrevented; 13367 13368 // if the location was changed by a `$locationChangeStart` handler then stop 13369 // processing this location change 13370 if ($location.absUrl() !== newUrl) return; 13371 13372 if (defaultPrevented) { 13373 $location.$$parse(oldUrl); 13374 $location.$$state = oldState; 13375 } else { 13376 if (urlOrStateChanged) { 13377 setBrowserUrlWithFallback(newUrl, currentReplace, 13378 oldState === $location.$$state ? null : $location.$$state); 13379 } 13380 afterLocationChange(oldUrl, oldState); 13381 } 13382 }); 13383 } 13384 13385 $location.$$replace = false; 13386 13387 // we don't need to return anything because $evalAsync will make the digest loop dirty when 13388 // there is a change 13389 }); 13390 13391 return $location; 13392 13393 function afterLocationChange(oldUrl, oldState) { 13394 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 13395 $location.$$state, oldState); 13396 } 13397}]; 13398} 13399 13400/** 13401 * @ngdoc service 13402 * @name $log 13403 * @requires $window 13404 * 13405 * @description 13406 * Simple service for logging. Default implementation safely writes the message 13407 * into the browser's console (if present). 13408 * 13409 * The main purpose of this service is to simplify debugging and troubleshooting. 13410 * 13411 * The default is to log `debug` messages. You can use 13412 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 13413 * 13414 * @example 13415 <example module="logExample"> 13416 <file name="script.js"> 13417 angular.module('logExample', []) 13418 .controller('LogController', ['$scope', '$log', function($scope, $log) { 13419 $scope.$log = $log; 13420 $scope.message = 'Hello World!'; 13421 }]); 13422 </file> 13423 <file name="index.html"> 13424 <div ng-controller="LogController"> 13425 <p>Reload this page with open console, enter text and hit the log button...</p> 13426 <label>Message: 13427 <input type="text" ng-model="message" /></label> 13428 <button ng-click="$log.log(message)">log</button> 13429 <button ng-click="$log.warn(message)">warn</button> 13430 <button ng-click="$log.info(message)">info</button> 13431 <button ng-click="$log.error(message)">error</button> 13432 <button ng-click="$log.debug(message)">debug</button> 13433 </div> 13434 </file> 13435 </example> 13436 */ 13437 13438/** 13439 * @ngdoc provider 13440 * @name $logProvider 13441 * @description 13442 * Use the `$logProvider` to configure how the application logs messages 13443 */ 13444function $LogProvider() { 13445 var debug = true, 13446 self = this; 13447 13448 /** 13449 * @ngdoc method 13450 * @name $logProvider#debugEnabled 13451 * @description 13452 * @param {boolean=} flag enable or disable debug level messages 13453 * @returns {*} current value if used as getter or itself (chaining) if used as setter 13454 */ 13455 this.debugEnabled = function(flag) { 13456 if (isDefined(flag)) { 13457 debug = flag; 13458 return this; 13459 } else { 13460 return debug; 13461 } 13462 }; 13463 13464 this.$get = ['$window', function($window) { 13465 return { 13466 /** 13467 * @ngdoc method 13468 * @name $log#log 13469 * 13470 * @description 13471 * Write a log message 13472 */ 13473 log: consoleLog('log'), 13474 13475 /** 13476 * @ngdoc method 13477 * @name $log#info 13478 * 13479 * @description 13480 * Write an information message 13481 */ 13482 info: consoleLog('info'), 13483 13484 /** 13485 * @ngdoc method 13486 * @name $log#warn 13487 *
13488 * @description 13489 * Write a warning message 13490 */ 13491 warn: consoleLog('warn'), 13492 13493 /** 13494 * @ngdoc method 13495 * @name $log#error 13496 * 13497 * @description 13498 * Write an error message 13499 */ 13500 error: consoleLog('error'), 13501 13502 /** 13503 * @ngdoc method 13504 * @name $log#debug 13505 * 13506 * @description 13507 * Write a debug message 13508 */ 13509 debug: (function() { 13510 var fn = consoleLog('debug'); 13511 13512 return function() { 13513 if (debug) { 13514 fn.apply(self, arguments); 13515 } 13516 }; 13517 }()) 13518 }; 13519 13520 function formatError(arg) { 13521 if (arg instanceof Error) { 13522 if (arg.stack) { 13523 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 13524 ? 'Error: ' + arg.message + '\n' + arg.stack 13525 : arg.stack; 13526 } else if (arg.sourceURL) { 13527 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 13528 } 13529 } 13530 return arg; 13531 } 13532 13533 function consoleLog(type) { 13534 var console = $window.console || {}, 13535 logFn = console[type] || console.log || noop, 13536 hasApply = false; 13537 13538 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 13539 // The reason behind this is that console.log has type "object" in IE8... 13540 try { 13541 hasApply = !!logFn.apply; 13542 } catch (e) {} 13543 13544 if (hasApply) { 13545 return function() { 13546 var args = []; 13547 forEach(arguments, function(arg) { 13548 args.push(formatError(arg)); 13549 }); 13550 return logFn.apply(console, args); 13551 }; 13552 } 13553 13554 // we are IE which either doesn't have window.console => this is noop and we do nothing, 13555 // or we are IE where console.log doesn't have apply so we log at least first 2 args 13556 return function(arg1, arg2) { 13557 logFn(arg1, arg2 == null ? '' : arg2); 13558 }; 13559 } 13560 }]; 13561} 13562 13563/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 13564 * Any commits to this file should be reviewed with security in mind. * 13565 * Changes to this file can potentially create security vulnerabilities. * 13566 * An approval from 2 Core members with history of modifying * 13567 * this file is required. * 13568 * * 13569 * Does the change somehow allow for arbitrary javascript to be executed? * 13570 * Or allows for someone to change the prototype of built-in objects? * 13571 * Or gives undesired access to variables likes document or window? * 13572 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 13573 13574var $parseMinErr = minErr('$parse'); 13575 13576// Sandboxing Angular Expressions 13577// ------------------------------ 13578// Angular expressions are generally considered safe because these expressions only have direct 13579// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 13580// obtaining a reference to native JS functions such as the Function constructor. 13581// 13582// As an example, consider the following Angular expression: 13583// 13584// {}.toString.constructor('alert("evil JS code")') 13585// 13586// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 13587// against the expression language, but not to prevent exploits that were enabled by exposing 13588// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 13589// practice and therefore we are not even trying to protect against interaction with an object 13590// explicitly exposed in this way. 13591// 13592// In general, it is not possible to access a Window object from an angular expression unless a 13593// window or some DOM object that has a reference to window is published onto a Scope. 13594// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 13595// native objects. 13596// 13597// See https://docs.angularjs.org/guide/security 13598 13599 13600function ensureSafeMemberName(name, fullExpression) { 13601 if (name === "__defineGetter__" || name === "__defineSetter__" 13602 || name === "__lookupGetter__" || name === "__lookupSetter__" 13603 || name === "__proto__") { 13604 throw $parseMinErr('isecfld', 13605 'Attempting to access a disallowed field in Angular expressions! ' 13606 + 'Expression: {0}', fullExpression); 13607 } 13608 return name; 13609} 13610 13611function getStringValue(name) { 13612 // Property names must be strings. This means that non-string objects cannot be used 13613 // as keys in an object. Any non-string object, including a number, is typecasted 13614 // into a string via the toString method. 13615 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 13616 // 13617 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 13618 // to a string. It's not always possible. If `name` is an object and its `toString` method is 13619 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 13620 // 13621 // TypeError: Cannot convert object to primitive value 13622 // 13623 // For performance reasons, we don't catch this error here and allow it to propagate up the call 13624 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 13625 // such a 'broken' object as a key. 13626 return name + ''; 13627} 13628 13629function ensureSafeObject(obj, fullExpression) { 13630 // nifty check if obj is Function that is fast and works across iframes and other contexts 13631 if (obj) { 13632 if (obj.constructor === obj) { 13633 throw $parseMinErr('isecfn', 13634 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 13635 fullExpression); 13636 } else if (// isWindow(obj) 13637 obj.window === obj) { 13638 throw $parseMinErr('isecwindow', 13639 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 13640 fullExpression); 13641 } else if (// isElement(obj) 13642 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 13643 throw $parseMinErr('isecdom', 13644 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 13645 fullExpression); 13646 } else if (// block Object so that we can't get hold of dangerous Object.* methods 13647 obj === Object) { 13648 throw $parseMinErr('isecobj', 13649 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 13650 fullExpression); 13651 } 13652 } 13653 return obj; 13654} 13655 13656var CALL = Function.prototype.call; 13657var APPLY = Function.prototype.apply; 13658var BIND = Function.prototype.bind; 13659 13660function ensureSafeFunction(obj, fullExpression) { 13661 if (obj) { 13662 if (obj.constructor === obj) { 13663 throw $parseMinErr('isecfn', 13664 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 13665 fullExpression); 13666 } else if (obj === CALL || obj === APPLY || obj === BIND) { 13667 throw $parseMinErr('isecff', 13668 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 13669 fullExpression); 13670 } 13671 } 13672} 13673 13674function ensureSafeAssignContext(obj, fullExpression) { 13675 if (obj) { 13676 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 13677 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 13678 throw $parseMinErr('isecaf', 13679 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 13680 } 13681 } 13682} 13683 13684var OPERATORS = createMap(); 13685forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 13686var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 13687 13688
vendor: 49,739 bytes, lines 13689-15256
13689///////////////////////////////////////// 13690 13691 13692/** 13693 * @constructor 13694 */ 13695var Lexer = function(options) { 13696 this.options = options; 13697}; 13698 13699Lexer.prototype = { 13700 constructor: Lexer, 13701 13702 lex: function(text) { 13703 this.text = text; 13704 this.index = 0; 13705 this.tokens = []; 13706 13707 while (this.index < this.text.length) { 13708 var ch = this.text.charAt(this.index); 13709 if (ch === '"' || ch === "'") { 13710 this.readString(ch); 13711 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 13712 this.readNumber(); 13713 } else if (this.isIdentifierStart(this.peekMultichar())) { 13714 this.readIdent(); 13715 } else if (this.is(ch, '(){}[].,;:?')) { 13716 this.tokens.push({index: this.index, text: ch}); 13717 this.index++; 13718 } else if (this.isWhitespace(ch)) { 13719 this.index++; 13720 } else { 13721 var ch2 = ch + this.peek(); 13722 var ch3 = ch2 + this.peek(2); 13723 var op1 = OPERATORS[ch]; 13724 var op2 = OPERATORS[ch2]; 13725 var op3 = OPERATORS[ch3]; 13726 if (op1 || op2 || op3) { 13727 var token = op3 ? ch3 : (op2 ? ch2 : ch); 13728 this.tokens.push({index: this.index, text: token, operator: true}); 13729 this.index += token.length; 13730 } else { 13731 this.throwError('Unexpected next character ', this.index, this.index + 1); 13732 } 13733 } 13734 } 13735 return this.tokens; 13736 }, 13737 13738 is: function(ch, chars) { 13739 return chars.indexOf(ch) !== -1; 13740 }, 13741 13742 peek: function(i) { 13743 var num = i || 1; 13744 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 13745 }, 13746 13747 isNumber: function(ch) { 13748 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 13749 }, 13750 13751 isWhitespace: function(ch) { 13752 // IE treats non-breaking space as \u00A0 13753 return (ch === ' ' || ch === '\r' || ch === '\t' || 13754 ch === '\n' || ch === '\v' || ch === '\u00A0'); 13755 }, 13756 13757 isIdentifierStart: function(ch) { 13758 return this.options.isIdentifierStart ? 13759 this.options.isIdentifierStart(ch, this.codePointAt(ch)) : 13760 this.isValidIdentifierStart(ch); 13761 }, 13762 13763 isValidIdentifierStart: function(ch) { 13764 return ('a' <= ch && ch <= 'z' || 13765 'A' <= ch && ch <= 'Z' || 13766 '_' === ch || ch === '$'); 13767 }, 13768 13769 isIdentifierContinue: function(ch) { 13770 return this.options.isIdentifierContinue ? 13771 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : 13772 this.isValidIdentifierContinue(ch); 13773 }, 13774 13775 isValidIdentifierContinue: function(ch, cp) { 13776 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); 13777 }, 13778 13779 codePointAt: function(ch) { 13780 if (ch.length === 1) return ch.charCodeAt(0); 13781 /*jshint bitwise: false*/ 13782 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; 13783 /*jshint bitwise: true*/ 13784 }, 13785 13786 peekMultichar: function() { 13787 var ch = this.text.charAt(this.index); 13788 var peek = this.peek(); 13789 if (!peek) { 13790 return ch; 13791 } 13792 var cp1 = ch.charCodeAt(0); 13793 var cp2 = peek.charCodeAt(0); 13794 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { 13795 return ch + peek; 13796 } 13797 return ch; 13798 }, 13799 13800 isExpOperator: function(ch) { 13801 return (ch === '-' || ch === '+' || this.isNumber(ch)); 13802 }, 13803 13804 throwError: function(error, start, end) { 13805 end = end || this.index; 13806 var colStr = (isDefined(start) 13807 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 13808 : ' ' + end); 13809 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 13810 error, colStr, this.text); 13811 }, 13812 13813 readNumber: function() { 13814 var number = ''; 13815 var start = this.index; 13816 while (this.index < this.text.length) { 13817 var ch = lowercase(this.text.charAt(this.index)); 13818 if (ch == '.' || this.isNumber(ch)) { 13819 number += ch; 13820 } else { 13821 var peekCh = this.peek(); 13822 if (ch == 'e' && this.isExpOperator(peekCh)) { 13823 number += ch; 13824 } else if (this.isExpOperator(ch) && 13825 peekCh && this.isNumber(peekCh) && 13826 number.charAt(number.length - 1) == 'e') { 13827 number += ch; 13828 } else if (this.isExpOperator(ch) && 13829 (!peekCh || !this.isNumber(peekCh)) && 13830 number.charAt(number.length - 1) == 'e') { 13831 this.throwError('Invalid exponent'); 13832 } else { 13833 break; 13834 } 13835 } 13836 this.index++; 13837 } 13838 this.tokens.push({ 13839 index: start, 13840 text: number, 13841 constant: true, 13842 value: Number(number) 13843 }); 13844 }, 13845 13846 readIdent: function() { 13847 var start = this.index; 13848 this.index += this.peekMultichar().length; 13849 while (this.index < this.text.length) { 13850 var ch = this.peekMultichar(); 13851 if (!this.isIdentifierContinue(ch)) { 13852 break; 13853 } 13854 this.index += ch.length; 13855 } 13856 this.tokens.push({ 13857 index: start, 13858 text: this.text.slice(start, this.index), 13859 identifier: true 13860 }); 13861 }, 13862 13863 readString: function(quote) { 13864 var start = this.index; 13865 this.index++; 13866 var string = ''; 13867 var rawString = quote; 13868 var escape = false; 13869 while (this.index < this.text.length) { 13870 var ch = this.text.charAt(this.index); 13871 rawString += ch; 13872 if (escape) { 13873 if (ch === 'u') { 13874 var hex = this.text.substring(this.index + 1, this.index + 5); 13875 if (!hex.match(/[\da-f]{4}/i)) { 13876 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 13877 } 13878 this.index += 4; 13879 string += String.fromCharCode(parseInt(hex, 16)); 13880 } else { 13881 var rep = ESCAPE[ch]; 13882 string = string + (rep || ch); 13883 } 13884 escape = false; 13885 } else if (ch === '\\') { 13886 escape = true; 13887 } else if (ch === quote) { 13888 this.index++; 13889 this.tokens.push({ 13890 index: start, 13891 text: rawString, 13892 constant: true, 13893 value: string 13894 }); 13895 return; 13896 } else { 13897 string += ch; 13898 } 13899 this.index++; 13900 } 13901 this.throwError('Unterminated quote', start); 13902 } 13903}; 13904 13905var AST = function(lexer, options) { 13906 this.lexer = lexer; 13907 this.options = options; 13908}; 13909 13910AST.Program = 'Program'; 13911AST.ExpressionStatement = 'ExpressionStatement'; 13912AST.AssignmentExpression = 'AssignmentExpression'; 13913AST.ConditionalExpression = 'ConditionalExpression'; 13914AST.LogicalExpression = 'LogicalExpression'; 13915AST.BinaryExpression = 'BinaryExpression'; 13916AST.UnaryExpression = 'UnaryExpression'; 13917AST.CallExpression = 'CallExpression'; 13918AST.MemberExpression = 'MemberExpression'; 13919AST.Identifier = 'Identifier'; 13920AST.Literal = 'Literal'; 13921AST.ArrayExpression = 'ArrayExpression'; 13922AST.Property = 'Property'; 13923AST.ObjectExpression = 'ObjectExpression'; 13924AST.ThisExpression = 'ThisExpression'; 13925AST.LocalsExpression = 'LocalsExpression'; 13926 13927// Internal use only 13928AST.NGValueParameter = 'NGValueParameter'; 13929 13930AST.prototype = { 13931 ast: function(text) { 13932 this.text = text; 13933 this.tokens = this.lexer.lex(text); 13934 13935 var value = this.program(); 13936 13937 if (this.tokens.length !== 0) { 13938 this.throwError('is an unexpected token', this.tokens[0]); 13939 } 13940 13941 return value; 13942 }, 13943 13944 program: function() { 13945 var body = []; 13946 while (true) { 13947 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 13948 body.push(this.expressionStatement()); 13949 if (!this.expect(';')) { 13950 return { type: AST.Program, body: body}; 13951 } 13952 } 13953 }, 13954 13955 expressionStatement: function() { 13956 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 13957 }, 13958 13959 filterChain: function() { 13960 var left = this.expression(); 13961 var token; 13962 while ((token = this.expect('|'))) { 13963 left = this.filter(left); 13964 } 13965 return left; 13966 }, 13967 13968 expression: function() { 13969 return this.assignment(); 13970 }, 13971 13972 assignment: function() { 13973 var result = this.ternary(); 13974 if (this.expect('=')) { 13975 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 13976 } 13977 return result; 13978 }, 13979 13980 ternary: function() { 13981 var test = this.logicalOR(); 13982 var alternate; 13983 var consequent; 13984 if (this.expect('?')) { 13985 alternate = this.expression(); 13986 if (this.consume(':')) { 13987 consequent = this.expression(); 13988 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 13989 } 13990 } 13991 return test; 13992 }, 13993 13994 logicalOR: function() { 13995 var left = this.logicalAND(); 13996 while (this.expect('||')) { 13997 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 13998 } 13999 return left; 14000 }, 14001 14002 logicalAND: function() { 14003 var left = this.equality(); 14004 while (this.expect('&&')) { 14005 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 14006 } 14007 return left; 14008 }, 14009 14010 equality: function() { 14011 var left = this.relational(); 14012 var token; 14013 while ((token = this.expect('==','!=','===','!=='))) { 14014 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 14015 } 14016 return left; 14017 }, 14018 14019 relational: function() { 14020 var left = this.additive(); 14021 var token; 14022 while ((token = this.expect('<', '>', '<=', '>='))) { 14023 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 14024 } 14025 return left; 14026 }, 14027 14028 additive: function() { 14029 var left = this.multiplicative(); 14030 var token; 14031 while ((token = this.expect('+','-'))) { 14032 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 14033 } 14034 return left; 14035 }, 14036 14037 multiplicative: function() { 14038 var left = this.unary(); 14039 var token; 14040 while ((token = this.expect('*','/','%'))) { 14041 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 14042 } 14043 return left; 14044 }, 14045 14046 unary: function() { 14047 var token; 14048 if ((token = this.expect('+', '-', '!'))) { 14049 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 14050 } else { 14051 return this.primary(); 14052 } 14053 }, 14054 14055 primary: function() { 14056 var primary; 14057 if (this.expect('(')) { 14058 primary = this.filterChain(); 14059 this.consume(')'); 14060 } else if (this.expect('[')) { 14061 primary = this.arrayDeclaration(); 14062 } else if (this.expect('{')) { 14063 primary = this.object(); 14064 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { 14065 primary = copy(this.selfReferential[this.consume().text]); 14066 } else if (this.options.literals.hasOwnProperty(this.peek().text)) { 14067 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; 14068 } else if (this.peek().identifier) { 14069 primary = this.identifier(); 14070 } else if (this.peek().constant) { 14071 primary = this.constant(); 14072 } else { 14073 this.throwError('not a primary expression', this.peek()); 14074 } 14075 14076 var next; 14077 while ((next = this.expect('(', '[', '.'))) { 14078 if (next.text === '(') { 14079 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 14080 this.consume(')'); 14081 } else if (next.text === '[') { 14082 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 14083 this.consume(']'); 14084 } else if (next.text === '.') { 14085 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 14086 } else { 14087 this.throwError('IMPOSSIBLE'); 14088 } 14089 } 14090 return primary; 14091 }, 14092 14093 filter: function(baseExpression) { 14094 var args = [baseExpression]; 14095 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 14096 14097 while (this.expect(':')) { 14098 args.push(this.expression()); 14099 } 14100 14101 return result; 14102 }, 14103 14104 parseArguments: function() { 14105 var args = []; 14106 if (this.peekToken().text !== ')') { 14107 do { 14108 args.push(this.expression()); 14109 } while (this.expect(',')); 14110 } 14111 return args; 14112 }, 14113 14114 identifier: function() { 14115 var token = this.consume(); 14116 if (!token.identifier) { 14117 this.throwError('is not a valid identifier', token); 14118 } 14119 return { type: AST.Identifier, name: token.text }; 14120 }, 14121 14122 constant: function() { 14123 // TODO check that it is a constant 14124 return { type: AST.Literal, value: this.consume().value }; 14125 }, 14126 14127 arrayDeclaration: function() { 14128 var elements = []; 14129 if (this.peekToken().text !== ']') { 14130 do { 14131 if (this.peek(']')) { 14132 // Support trailing commas per ES5.1. 14133 break; 14134 } 14135 elements.push(this.expression()); 14136 } while (this.expect(',')); 14137 } 14138 this.consume(']'); 14139 14140 return { type: AST.ArrayExpression, elements: elements }; 14141 }, 14142 14143 object: function() { 14144 var properties = [], property; 14145 if (this.peekToken().text !== '}') { 14146 do { 14147 if (this.peek('}')) { 14148 // Support trailing commas per ES5.1. 14149 break; 14150 } 14151 property = {type: AST.Property, kind: 'init'}; 14152 if (this.peek().constant) { 14153 property.key = this.constant(); 14154 } else if (this.peek().identifier) { 14155 property.key = this.identifier(); 14156 } else { 14157 this.throwError("invalid key", this.peek()); 14158 } 14159 this.consume(':'); 14160 property.value = this.expression(); 14161 properties.push(property); 14162 } while (this.expect(',')); 14163 } 14164 this.consume('}'); 14165 14166 return {type: AST.ObjectExpression, properties: properties }; 14167 }, 14168 14169 throwError: function(msg, token) { 14170 throw $parseMinErr('syntax', 14171 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 14172 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 14173 }, 14174 14175 consume: function(e1) { 14176 if (this.tokens.length === 0) { 14177 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 14178 } 14179 14180 var token = this.expect(e1); 14181 if (!token) { 14182 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 14183 } 14184 return token; 14185 }, 14186 14187 peekToken: function() { 14188 if (this.tokens.length === 0) { 14189 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 14190 } 14191 return this.tokens[0]; 14192 }, 14193 14194 peek: function(e1, e2, e3, e4) { 14195 return this.peekAhead(0, e1, e2, e3, e4); 14196 }, 14197 14198 peekAhead: function(i, e1, e2, e3, e4) { 14199 if (this.tokens.length > i) { 14200 var token = this.tokens[i]; 14201 var t = token.text; 14202 if (t === e1 || t === e2 || t === e3 || t === e4 || 14203 (!e1 && !e2 && !e3 && !e4)) { 14204 return token; 14205 } 14206 } 14207 return false; 14208 }, 14209 14210 expect: function(e1, e2, e3, e4) { 14211 var token = this.peek(e1, e2, e3, e4); 14212 if (token) { 14213 this.tokens.shift(); 14214 return token; 14215 } 14216 return false; 14217 }, 14218 14219 selfReferential: { 14220 'this': {type: AST.ThisExpression }, 14221 '$locals': {type: AST.LocalsExpression } 14222 } 14223}; 14224 14225function ifDefined(v, d) { 14226 return typeof v !== 'undefined' ? v : d; 14227} 14228 14229function plusFn(l, r) { 14230 if (typeof l === 'undefined') return r; 14231 if (typeof r === 'undefined') return l; 14232 return l + r; 14233} 14234 14235function isStateless($filter, filterName) { 14236 var fn = $filter(filterName); 14237 return !fn.$stateful; 14238} 14239 14240function findConstantAndWatchExpressions(ast, $filter) { 14241 var allConstants; 14242 var argsToWatch; 14243 switch (ast.type) { 14244 case AST.Program: 14245 allConstants = true; 14246 forEach(ast.body, function(expr) { 14247 findConstantAndWatchExpressions(expr.expression, $filter); 14248 allConstants = allConstants && expr.expression.constant; 14249 }); 14250 ast.constant = allConstants; 14251 break; 14252 case AST.Literal: 14253 ast.constant = true; 14254 ast.toWatch = []; 14255 break; 14256 case AST.UnaryExpression: 14257 findConstantAndWatchExpressions(ast.argument, $filter); 14258 ast.constant = ast.argument.constant; 14259 ast.toWatch = ast.argument.toWatch; 14260 break; 14261 case AST.BinaryExpression: 14262 findConstantAndWatchExpressions(ast.left, $filter); 14263 findConstantAndWatchExpressions(ast.right, $filter); 14264 ast.constant = ast.left.constant && ast.right.constant; 14265 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 14266 break; 14267 case AST.LogicalExpression: 14268 findConstantAndWatchExpressions(ast.left, $filter); 14269 findConstantAndWatchExpressions(ast.right, $filter); 14270 ast.constant = ast.left.constant && ast.right.constant; 14271 ast.toWatch = ast.constant ? [] : [ast]; 14272 break; 14273 case AST.ConditionalExpression: 14274 findConstantAndWatchExpressions(ast.test, $filter); 14275 findConstantAndWatchExpressions(ast.alternate, $filter); 14276 findConstantAndWatchExpressions(ast.consequent, $filter); 14277 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 14278 ast.toWatch = ast.constant ? [] : [ast]; 14279 break; 14280 case AST.Identifier: 14281 ast.constant = false; 14282 ast.toWatch = [ast]; 14283 break; 14284 case AST.MemberExpression: 14285 findConstantAndWatchExpressions(ast.object, $filter); 14286 if (ast.computed) { 14287 findConstantAndWatchExpressions(ast.property, $filter); 14288 } 14289 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 14290 ast.toWatch = [ast]; 14291 break; 14292 case AST.CallExpression: 14293 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 14294 argsToWatch = []; 14295 forEach(ast.arguments, function(expr) { 14296 findConstantAndWatchExpressions(expr, $filter); 14297 allConstants = allConstants && expr.constant; 14298 if (!expr.constant) { 14299 argsToWatch.push.apply(argsToWatch, expr.toWatch); 14300 } 14301 }); 14302 ast.constant = allConstants; 14303 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 14304 break; 14305 case AST.AssignmentExpression: 14306 findConstantAndWatchExpressions(ast.left, $filter); 14307 findConstantAndWatchExpressions(ast.right, $filter); 14308 ast.constant = ast.left.constant && ast.right.constant; 14309 ast.toWatch = [ast]; 14310 break; 14311 case AST.ArrayExpression: 14312 allConstants = true; 14313 argsToWatch = []; 14314 forEach(ast.elements, function(expr) { 14315 findConstantAndWatchExpressions(expr, $filter); 14316 allConstants = allConstants && expr.constant; 14317 if (!expr.constant) { 14318 argsToWatch.push.apply(argsToWatch, expr.toWatch); 14319 } 14320 }); 14321 ast.constant = allConstants; 14322 ast.toWatch = argsToWatch; 14323 break; 14324 case AST.ObjectExpression: 14325 allConstants = true; 14326 argsToWatch = []; 14327 forEach(ast.properties, function(property) { 14328 findConstantAndWatchExpressions(property.value, $filter); 14329 allConstants = allConstants && property.value.constant; 14330 if (!property.value.constant) { 14331 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 14332 } 14333 }); 14334 ast.constant = allConstants; 14335 ast.toWatch = argsToWatch; 14336 break; 14337 case AST.ThisExpression: 14338 ast.constant = false; 14339 ast.toWatch = []; 14340 break; 14341 case AST.LocalsExpression: 14342 ast.constant = false; 14343 ast.toWatch = []; 14344 break; 14345 } 14346} 14347 14348function getInputs(body) { 14349 if (body.length != 1) return; 14350 var lastExpression = body[0].expression; 14351 var candidate = lastExpression.toWatch; 14352 if (candidate.length !== 1) return candidate; 14353 return candidate[0] !== lastExpression ? candidate : undefined; 14354} 14355 14356function isAssignable(ast) { 14357 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 14358} 14359 14360function assignableAST(ast) { 14361 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 14362 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 14363 } 14364} 14365 14366function isLiteral(ast) { 14367 return ast.body.length === 0 || 14368 ast.body.length === 1 && ( 14369 ast.body[0].expression.type === AST.Literal || 14370 ast.body[0].expression.type === AST.ArrayExpression || 14371 ast.body[0].expression.type === AST.ObjectExpression); 14372} 14373 14374function isConstant(ast) { 14375 return ast.constant; 14376} 14377 14378function ASTCompiler(astBuilder, $filter) { 14379 this.astBuilder = astBuilder; 14380 this.$filter = $filter; 14381} 14382 14383ASTCompiler.prototype = { 14384 compile: function(expression, expensiveChecks) { 14385 var self = this; 14386 var ast = this.astBuilder.ast(expression); 14387 this.state = { 14388 nextId: 0, 14389 filters: {}, 14390 expensiveChecks: expensiveChecks, 14391 fn: {vars: [], body: [], own: {}}, 14392 assign: {vars: [], body: [], own: {}}, 14393 inputs: [] 14394 }; 14395 findConstantAndWatchExpressions(ast, self.$filter); 14396 var extra = ''; 14397 var assignable; 14398 this.stage = 'assign'; 14399 if ((assignable = assignableAST(ast))) { 14400 this.state.computing = 'assign'; 14401 var result = this.nextId(); 14402 this.recurse(assignable, result); 14403 this.return_(result); 14404 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 14405 } 14406 var toWatch = getInputs(ast.body); 14407 self.stage = 'inputs'; 14408 forEach(toWatch, function(watch, key) { 14409 var fnKey = 'fn' + key; 14410 self.state[fnKey] = {vars: [], body: [], own: {}}; 14411 self.state.computing = fnKey; 14412 var intoId = self.nextId(); 14413 self.recurse(watch, intoId); 14414 self.return_(intoId); 14415 self.state.inputs.push(fnKey); 14416 watch.watchId = key; 14417 }); 14418 this.state.computing = 'fn'; 14419 this.stage = 'main'; 14420 this.recurse(ast); 14421 var fnString = 14422 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 14423 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 14424 '"' + this.USE + ' ' + this.STRICT + '";\n' + 14425 this.filterPrefix() + 14426 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 14427 extra + 14428 this.watchFns() + 14429 'return fn;'; 14430 14431 /* jshint -W054 */ 14432 var fn = (new Function('$filter', 14433 'ensureSafeMemberName', 14434 'ensureSafeObject', 14435 'ensureSafeFunction', 14436 'getStringValue', 14437 'ensureSafeAssignContext', 14438 'ifDefined', 14439 'plus', 14440 'text', 14441 fnString))( 14442 this.$filter, 14443 ensureSafeMemberName, 14444 ensureSafeObject, 14445 ensureSafeFunction, 14446 getStringValue, 14447 ensureSafeAssignContext, 14448 ifDefined, 14449 plusFn, 14450 expression); 14451 /* jshint +W054 */ 14452 this.state = this.stage = undefined; 14453 fn.literal = isLiteral(ast); 14454 fn.constant = isConstant(ast); 14455 return fn; 14456 }, 14457 14458 USE: 'use', 14459 14460 STRICT: 'strict', 14461 14462 watchFns: function() { 14463 var result = []; 14464 var fns = this.state.inputs; 14465 var self = this; 14466 forEach(fns, function(name) { 14467 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 14468 }); 14469 if (fns.length) { 14470 result.push('fn.inputs=[' + fns.join(',') + '];'); 14471 } 14472 return result.join(''); 14473 }, 14474 14475 generateFunction: function(name, params) { 14476 return 'function(' + params + '){' + 14477 this.varsPrefix(name) + 14478 this.body(name) + 14479 '};'; 14480 }, 14481 14482 filterPrefix: function() { 14483 var parts = []; 14484 var self = this; 14485 forEach(this.state.filters, function(id, filter) { 14486 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 14487 }); 14488 if (parts.length) return 'var ' + parts.join(',') + ';'; 14489 return ''; 14490 }, 14491 14492 varsPrefix: function(section) { 14493 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 14494 }, 14495 14496 body: function(section) { 14497 return this.state[section].body.join(''); 14498 }, 14499 14500 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 14501 var left, right, self = this, args, expression; 14502 recursionFn = recursionFn || noop; 14503 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 14504 intoId = intoId || this.nextId(); 14505 this.if_('i', 14506 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 14507 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 14508 ); 14509 return; 14510 } 14511 switch (ast.type) { 14512 case AST.Program: 14513 forEach(ast.body, function(expression, pos) { 14514 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 14515 if (pos !== ast.body.length - 1) { 14516 self.current().body.push(right, ';'); 14517 } else { 14518 self.return_(right); 14519 } 14520 }); 14521 break; 14522 case AST.Literal: 14523 expression = this.escape(ast.value); 14524 this.assign(intoId, expression); 14525 recursionFn(expression); 14526 break; 14527 case AST.UnaryExpression: 14528 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 14529 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 14530 this.assign(intoId, expression); 14531 recursionFn(expression); 14532 break; 14533 case AST.BinaryExpression: 14534 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 14535 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 14536 if (ast.operator === '+') { 14537 expression = this.plus(left, right); 14538 } else if (ast.operator === '-') { 14539 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 14540 } else { 14541 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 14542 } 14543 this.assign(intoId, expression); 14544 recursionFn(expression); 14545 break; 14546 case AST.LogicalExpression: 14547 intoId = intoId || this.nextId(); 14548 self.recurse(ast.left, intoId); 14549 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 14550 recursionFn(intoId); 14551 break; 14552 case AST.ConditionalExpression: 14553 intoId = intoId || this.nextId(); 14554 self.recurse(ast.test, intoId); 14555 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 14556 recursionFn(intoId); 14557 break; 14558 case AST.Identifier: 14559 intoId = intoId || this.nextId(); 14560 if (nameId) { 14561 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 14562 nameId.computed = false; 14563 nameId.name = ast.name; 14564 } 14565 ensureSafeMemberName(ast.name); 14566 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 14567 function() { 14568 self.if_(self.stage === 'inputs' || 's', function() { 14569 if (create && create !== 1) { 14570 self.if_( 14571 self.not(self.nonComputedMember('s', ast.name)), 14572 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 14573 } 14574 self.assign(intoId, self.nonComputedMember('s', ast.name)); 14575 }); 14576 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 14577 ); 14578 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 14579 self.addEnsureSafeObject(intoId); 14580 } 14581 recursionFn(intoId); 14582 break; 14583 case AST.MemberExpression: 14584 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 14585 intoId = intoId || this.nextId(); 14586 self.recurse(ast.object, left, undefined, function() { 14587 self.if_(self.notNull(left), function() { 14588 if (create && create !== 1) { 14589 self.addEnsureSafeAssignContext(left); 14590 } 14591 if (ast.computed) { 14592 right = self.nextId(); 14593 self.recurse(ast.property, right); 14594 self.getStringValue(right); 14595 self.addEnsureSafeMemberName(right); 14596 if (create && create !== 1) { 14597 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 14598 } 14599 expression = self.ensureSafeObject(self.computedMember(left, right)); 14600 self.assign(intoId, expression); 14601 if (nameId) { 14602 nameId.computed = true; 14603 nameId.name = right; 14604 } 14605 } else { 14606 ensureSafeMemberName(ast.property.name); 14607 if (create && create !== 1) { 14608 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 14609 } 14610 expression = self.nonComputedMember(left, ast.property.name); 14611 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 14612 expression = self.ensureSafeObject(expression); 14613 } 14614 self.assign(intoId, expression); 14615 if (nameId) { 14616 nameId.computed = false; 14617 nameId.name = ast.property.name; 14618 } 14619 } 14620 }, function() { 14621 self.assign(intoId, 'undefined'); 14622 }); 14623 recursionFn(intoId); 14624 }, !!create); 14625 break; 14626 case AST.CallExpression: 14627 intoId = intoId || this.nextId(); 14628 if (ast.filter) { 14629 right = self.filter(ast.callee.name); 14630 args = []; 14631 forEach(ast.arguments, function(expr) { 14632 var argument = self.nextId(); 14633 self.recurse(expr, argument); 14634 args.push(argument); 14635 }); 14636 expression = right + '(' + args.join(',') + ')'; 14637 self.assign(intoId, expression); 14638 recursionFn(intoId); 14639 } else { 14640 right = self.nextId(); 14641 left = {}; 14642 args = []; 14643 self.recurse(ast.callee, right, left, function() { 14644 self.if_(self.notNull(right), function() { 14645 self.addEnsureSafeFunction(right); 14646 forEach(ast.arguments, function(expr) { 14647 self.recurse(expr, self.nextId(), undefined, function(argument) { 14648 args.push(self.ensureSafeObject(argument)); 14649 }); 14650 }); 14651 if (left.name) { 14652 if (!self.state.expensiveChecks) { 14653 self.addEnsureSafeObject(left.context); 14654 } 14655 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 14656 } else { 14657 expression = right + '(' + args.join(',') + ')'; 14658 } 14659 expression = self.ensureSafeObject(expression); 14660 self.assign(intoId, expression); 14661 }, function() { 14662 self.assign(intoId, 'undefined'); 14663 }); 14664 recursionFn(intoId); 14665 }); 14666 } 14667 break; 14668 case AST.AssignmentExpression: 14669 right = this.nextId(); 14670 left = {}; 14671 if (!isAssignable(ast.left)) { 14672 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 14673 } 14674 this.recurse(ast.left, undefined, left, function() { 14675 self.if_(self.notNull(left.context), function() { 14676 self.recurse(ast.right, right); 14677 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 14678 self.addEnsureSafeAssignContext(left.context); 14679 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 14680 self.assign(intoId, expression); 14681 recursionFn(intoId || expression); 14682 }); 14683 }, 1); 14684 break; 14685 case AST.ArrayExpression: 14686 args = []; 14687 forEach(ast.elements, function(expr) { 14688 self.recurse(expr, self.nextId(), undefined, function(argument) { 14689 args.push(argument); 14690 }); 14691 }); 14692 expression = '[' + args.join(',') + ']'; 14693 this.assign(intoId, expression); 14694 recursionFn(expression); 14695 break; 14696 case AST.ObjectExpression: 14697 args = []; 14698 forEach(ast.properties, function(property) { 14699 self.recurse(property.value, self.nextId(), undefined, function(expr) { 14700 args.push(self.escape( 14701 property.key.type === AST.Identifier ? property.key.name : 14702 ('' + property.key.value)) + 14703 ':' + expr); 14704 }); 14705 }); 14706 expression = '{' + args.join(',') + '}'; 14707 this.assign(intoId, expression); 14708 recursionFn(expression); 14709 break; 14710 case AST.ThisExpression: 14711 this.assign(intoId, 's'); 14712 recursionFn('s'); 14713 break; 14714 case AST.LocalsExpression: 14715 this.assign(intoId, 'l'); 14716 recursionFn('l'); 14717 break; 14718 case AST.NGValueParameter: 14719 this.assign(intoId, 'v'); 14720 recursionFn('v'); 14721 break; 14722 } 14723 }, 14724 14725 getHasOwnProperty: function(element, property) { 14726 var key = element + '.' + property; 14727 var own = this.current().own; 14728 if (!own.hasOwnProperty(key)) { 14729 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 14730 } 14731 return own[key]; 14732 }, 14733 14734 assign: function(id, value) { 14735 if (!id) return; 14736 this.current().body.push(id, '=', value, ';'); 14737 return id; 14738 }, 14739 14740 filter: function(filterName) { 14741 if (!this.state.filters.hasOwnProperty(filterName)) { 14742 this.state.filters[filterName] = this.nextId(true); 14743 } 14744 return this.state.filters[filterName]; 14745 }, 14746 14747 ifDefined: function(id, defaultValue) { 14748 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 14749 }, 14750 14751 plus: function(left, right) { 14752 return 'plus(' + left + ',' + right + ')'; 14753 }, 14754 14755 return_: function(id) { 14756 this.current().body.push('return ', id, ';'); 14757 }, 14758 14759 if_: function(test, alternate, consequent) { 14760 if (test === true) { 14761 alternate(); 14762 } else { 14763 var body = this.current().body; 14764 body.push('if(', test, '){'); 14765 alternate(); 14766 body.push('}'); 14767 if (consequent) { 14768 body.push('else{'); 14769 consequent(); 14770 body.push('}'); 14771 } 14772 } 14773 }, 14774 14775 not: function(expression) { 14776 return '!(' + expression + ')'; 14777 }, 14778 14779 notNull: function(expression) { 14780 return expression + '!=null'; 14781 }, 14782 14783 nonComputedMember: function(left, right) { 14784 var SAFE_IDENTIFIER = /[$_a-zA-Z][$_a-zA-Z0-9]*/; 14785 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; 14786 if (SAFE_IDENTIFIER.test(right)) { 14787 return left + '.' + right; 14788 } else { 14789 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; 14790 } 14791 }, 14792 14793 computedMember: function(left, right) { 14794 return left + '[' + right + ']'; 14795 }, 14796 14797 member: function(left, right, computed) { 14798 if (computed) return this.computedMember(left, right); 14799 return this.nonComputedMember(left, right); 14800 }, 14801 14802 addEnsureSafeObject: function(item) { 14803 this.current().body.push(this.ensureSafeObject(item), ';'); 14804 }, 14805 14806 addEnsureSafeMemberName: function(item) { 14807 this.current().body.push(this.ensureSafeMemberName(item), ';'); 14808 }, 14809 14810 addEnsureSafeFunction: function(item) { 14811 this.current().body.push(this.ensureSafeFunction(item), ';'); 14812 }, 14813 14814 addEnsureSafeAssignContext: function(item) { 14815 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 14816 }, 14817 14818 ensureSafeObject: function(item) { 14819 return 'ensureSafeObject(' + item + ',text)'; 14820 }, 14821 14822 ensureSafeMemberName: function(item) { 14823 return 'ensureSafeMemberName(' + item + ',text)'; 14824 }, 14825 14826 ensureSafeFunction: function(item) { 14827 return 'ensureSafeFunction(' + item + ',text)'; 14828 }, 14829 14830 getStringValue: function(item) { 14831 this.assign(item, 'getStringValue(' + item + ')'); 14832 }, 14833 14834 ensureSafeAssignContext: function(item) { 14835 return 'ensureSafeAssignContext(' + item + ',text)'; 14836 }, 14837 14838 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 14839 var self = this; 14840 return function() { 14841 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 14842 }; 14843 }, 14844 14845 lazyAssign: function(id, value) { 14846 var self = this; 14847 return function() { 14848 self.assign(id, value); 14849 }; 14850 }, 14851 14852 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 14853 14854 stringEscapeFn: function(c) { 14855 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 14856 }, 14857 14858 escape: function(value) { 14859 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 14860 if (isNumber(value)) return value.toString(); 14861 if (value === true) return 'true'; 14862 if (value === false) return 'false'; 14863 if (value === null) return 'null'; 14864 if (typeof value === 'undefined') return 'undefined'; 14865 14866 throw $parseMinErr('esc', 'IMPOSSIBLE'); 14867 }, 14868 14869 nextId: function(skip, init) { 14870 var id = 'v' + (this.state.nextId++); 14871 if (!skip) { 14872 this.current().vars.push(id + (init ? '=' + init : '')); 14873 } 14874 return id; 14875 }, 14876 14877 current: function() { 14878 return this.state[this.state.computing]; 14879 } 14880}; 14881 14882 14883function ASTInterpreter(astBuilder, $filter) { 14884 this.astBuilder = astBuilder; 14885 this.$filter = $filter; 14886} 14887 14888ASTInterpreter.prototype = { 14889 compile: function(expression, expensiveChecks) { 14890 var self = this; 14891 var ast = this.astBuilder.ast(expression); 14892 this.expression = expression; 14893 this.expensiveChecks = expensiveChecks; 14894 findConstantAndWatchExpressions(ast, self.$filter); 14895 var assignable; 14896 var assign; 14897 if ((assignable = assignableAST(ast))) { 14898 assign = this.recurse(assignable); 14899 } 14900 var toWatch = getInputs(ast.body); 14901 var inputs; 14902 if (toWatch) { 14903 inputs = []; 14904 forEach(toWatch, function(watch, key) { 14905 var input = self.recurse(watch); 14906 watch.input = input; 14907 inputs.push(input); 14908 watch.watchId = key; 14909 }); 14910 } 14911 var expressions = []; 14912 forEach(ast.body, function(expression) { 14913 expressions.push(self.recurse(expression.expression)); 14914 }); 14915 var fn = ast.body.length === 0 ? noop : 14916 ast.body.length === 1 ? expressions[0] : 14917 function(scope, locals) { 14918 var lastValue; 14919 forEach(expressions, function(exp) { 14920 lastValue = exp(scope, locals); 14921 }); 14922 return lastValue; 14923 }; 14924 if (assign) { 14925 fn.assign = function(scope, value, locals) { 14926 return assign(scope, locals, value); 14927 }; 14928 } 14929 if (inputs) { 14930 fn.inputs = inputs; 14931 } 14932 fn.literal = isLiteral(ast); 14933 fn.constant = isConstant(ast); 14934 return fn; 14935 }, 14936 14937 recurse: function(ast, context, create) { 14938 var left, right, self = this, args, expression; 14939 if (ast.input) { 14940 return this.inputs(ast.input, ast.watchId); 14941 } 14942 switch (ast.type) { 14943 case AST.Literal: 14944 return this.value(ast.value, context); 14945 case AST.UnaryExpression: 14946 right = this.recurse(ast.argument); 14947 return this['unary' + ast.operator](right, context); 14948 case AST.BinaryExpression: 14949 left = this.recurse(ast.left); 14950 right = this.recurse(ast.right); 14951 return this['binary' + ast.operator](left, right, context); 14952 case AST.LogicalExpression: 14953 left = this.recurse(ast.left); 14954 right = this.recurse(ast.right); 14955 return this['binary' + ast.operator](left, right, context); 14956 case AST.ConditionalExpression: 14957 return this['ternary?:']( 14958 this.recurse(ast.test), 14959 this.recurse(ast.alternate), 14960 this.recurse(ast.consequent), 14961 context 14962 ); 14963 case AST.Identifier: 14964 ensureSafeMemberName(ast.name, self.expression); 14965 return self.identifier(ast.name, 14966 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 14967 context, create, self.expression); 14968 case AST.MemberExpression: 14969 left = this.recurse(ast.object, false, !!create); 14970 if (!ast.computed) { 14971 ensureSafeMemberName(ast.property.name, self.expression); 14972 right = ast.property.name; 14973 } 14974 if (ast.computed) right = this.recurse(ast.property); 14975 return ast.computed ? 14976 this.computedMember(left, right, context, create, self.expression) : 14977 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 14978 case AST.CallExpression: 14979 args = []; 14980 forEach(ast.arguments, function(expr) { 14981 args.push(self.recurse(expr)); 14982 }); 14983 if (ast.filter) right = this.$filter(ast.callee.name); 14984 if (!ast.filter) right = this.recurse(ast.callee, true); 14985 return ast.filter ? 14986 function(scope, locals, assign, inputs) { 14987 var values = []; 14988 for (var i = 0; i < args.length; ++i) { 14989 values.push(args[i](scope, locals, assign, inputs)); 14990 } 14991 var value = right.apply(undefined, values, inputs); 14992 return context ? {context: undefined, name: undefined, value: value} : value; 14993 } : 14994 function(scope, locals, assign, inputs) { 14995 var rhs = right(scope, locals, assign, inputs); 14996 var value; 14997 if (rhs.value != null) { 14998 ensureSafeObject(rhs.context, self.expression); 14999 ensureSafeFunction(rhs.value, self.expression); 15000 var values = []; 15001 for (var i = 0; i < args.length; ++i) { 15002 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 15003 } 15004 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 15005 } 15006 return context ? {value: value} : value; 15007 }; 15008 case AST.AssignmentExpression: 15009 left = this.recurse(ast.left, true, 1); 15010 right = this.recurse(ast.right); 15011 return function(scope, locals, assign, inputs) { 15012 var lhs = left(scope, locals, assign, inputs); 15013 var rhs = right(scope, locals, assign, inputs); 15014 ensureSafeObject(lhs.value, self.expression); 15015 ensureSafeAssignContext(lhs.context); 15016 lhs.context[lhs.name] = rhs; 15017 return context ? {value: rhs} : rhs; 15018 }; 15019 case AST.ArrayExpression: 15020 args = []; 15021 forEach(ast.elements, function(expr) { 15022 args.push(self.recurse(expr)); 15023 }); 15024 return function(scope, locals, assign, inputs) { 15025 var value = []; 15026 for (var i = 0; i < args.length; ++i) { 15027 value.push(args[i](scope, locals, assign, inputs)); 15028 } 15029 return context ? {value: value} : value; 15030 }; 15031 case AST.ObjectExpression: 15032 args = []; 15033 forEach(ast.properties, function(property) { 15034 args.push({key: property.key.type === AST.Identifier ? 15035 property.key.name : 15036 ('' + property.key.value), 15037 value: self.recurse(property.value) 15038 }); 15039 }); 15040 return function(scope, locals, assign, inputs) { 15041 var value = {}; 15042 for (var i = 0; i < args.length; ++i) { 15043 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 15044 } 15045 return context ? {value: value} : value; 15046 }; 15047 case AST.ThisExpression: 15048 return function(scope) { 15049 return context ? {value: scope} : scope; 15050 }; 15051 case AST.LocalsExpression: 15052 return function(scope, locals) { 15053 return context ? {value: locals} : locals; 15054 }; 15055 case AST.NGValueParameter: 15056 return function(scope, locals, assign) { 15057 return context ? {value: assign} : assign; 15058 }; 15059 } 15060 }, 15061 15062 'unary+': function(argument, context) { 15063 return function(scope, locals, assign, inputs) { 15064 var arg = argument(scope, locals, assign, inputs); 15065 if (isDefined(arg)) { 15066 arg = +arg; 15067 } else { 15068 arg = 0; 15069 } 15070 return context ? {value: arg} : arg; 15071 }; 15072 }, 15073 'unary-': function(argument, context) { 15074 return function(scope, locals, assign, inputs) { 15075 var arg = argument(scope, locals, assign, inputs); 15076 if (isDefined(arg)) { 15077 arg = -arg; 15078 } else { 15079 arg = 0; 15080 } 15081 return context ? {value: arg} : arg; 15082 }; 15083 }, 15084 'unary!': function(argument, context) { 15085 return function(scope, locals, assign, inputs) { 15086 var arg = !argument(scope, locals, assign, inputs); 15087 return context ? {value: arg} : arg; 15088 }; 15089 }, 15090 'binary+': function(left, right, context) { 15091 return function(scope, locals, assign, inputs) { 15092 var lhs = left(scope, locals, assign, inputs); 15093 var rhs = right(scope, locals, assign, inputs); 15094 var arg = plusFn(lhs, rhs); 15095 return context ? {value: arg} : arg; 15096 }; 15097 }, 15098 'binary-': function(left, right, context) { 15099 return function(scope, locals, assign, inputs) { 15100 var lhs = left(scope, locals, assign, inputs); 15101 var rhs = right(scope, locals, assign, inputs); 15102 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 15103 return context ? {value: arg} : arg; 15104 }; 15105 }, 15106 'binary*': function(left, right, context) { 15107 return function(scope, locals, assign, inputs) { 15108 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 15109 return context ? {value: arg} : arg; 15110 }; 15111 }, 15112 'binary/': function(left, right, context) { 15113 return function(scope, locals, assign, inputs) { 15114 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 15115 return context ? {value: arg} : arg; 15116 }; 15117 }, 15118 'binary%': function(left, right, context) { 15119 return function(scope, locals, assign, inputs) { 15120 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 15121 return context ? {value: arg} : arg; 15122 }; 15123 }, 15124 'binary===': function(left, right, context) { 15125 return function(scope, locals, assign, inputs) { 15126 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 15127 return context ? {value: arg} : arg; 15128 }; 15129 }, 15130 'binary!==': function(left, right, context) { 15131 return function(scope, locals, assign, inputs) { 15132 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 15133 return context ? {value: arg} : arg; 15134 }; 15135 }, 15136 'binary==': function(left, right, context) { 15137 return function(scope, locals, assign, inputs) { 15138 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 15139 return context ? {value: arg} : arg; 15140 }; 15141 }, 15142 'binary!=': function(left, right, context) { 15143 return function(scope, locals, assign, inputs) { 15144 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 15145 return context ? {value: arg} : arg; 15146 }; 15147 }, 15148 'binary<': function(left, right, context) { 15149 return function(scope, locals, assign, inputs) { 15150 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 15151 return context ? {value: arg} : arg; 15152 }; 15153 }, 15154 'binary>': function(left, right, context) { 15155 return function(scope, locals, assign, inputs) { 15156 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 15157 return context ? {value: arg} : arg; 15158 }; 15159 }, 15160 'binary<=': function(left, right, context) { 15161 return function(scope, locals, assign, inputs) { 15162 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 15163 return context ? {value: arg} : arg; 15164 }; 15165 }, 15166 'binary>=': function(left, right, context) { 15167 return function(scope, locals, assign, inputs) { 15168 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 15169 return context ? {value: arg} : arg; 15170 }; 15171 }, 15172 'binary&&': function(left, right, context) { 15173 return function(scope, locals, assign, inputs) { 15174 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 15175 return context ? {value: arg} : arg; 15176 }; 15177 }, 15178 'binary||': function(left, right, context) { 15179 return function(scope, locals, assign, inputs) { 15180 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 15181 return context ? {value: arg} : arg; 15182 }; 15183 }, 15184 'ternary?:': function(test, alternate, consequent, context) { 15185 return function(scope, locals, assign, inputs) { 15186 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 15187 return context ? {value: arg} : arg; 15188 }; 15189 }, 15190 value: function(value, context) { 15191 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 15192 }, 15193 identifier: function(name, expensiveChecks, context, create, expression) { 15194 return function(scope, locals, assign, inputs) { 15195 var base = locals && (name in locals) ? locals : scope; 15196 if (create && create !== 1 && base && !(base[name])) { 15197 base[name] = {}; 15198 } 15199 var value = base ? base[name] : undefined; 15200 if (expensiveChecks) { 15201 ensureSafeObject(value, expression); 15202 } 15203 if (context) { 15204 return {context: base, name: name, value: value}; 15205 } else { 15206 return value; 15207 } 15208 }; 15209 }, 15210 computedMember: function(left, right, context, create, expression) { 15211 return function(scope, locals, assign, inputs) { 15212 var lhs = left(scope, locals, assign, inputs); 15213 var rhs; 15214 var value; 15215 if (lhs != null) { 15216 rhs = right(scope, locals, assign, inputs); 15217 rhs = getStringValue(rhs); 15218 ensureSafeMemberName(rhs, expression); 15219 if (create && create !== 1) { 15220 ensureSafeAssignContext(lhs); 15221 if (lhs && !(lhs[rhs])) { 15222 lhs[rhs] = {}; 15223 } 15224 } 15225 value = lhs[rhs]; 15226 ensureSafeObject(value, expression); 15227 } 15228 if (context) { 15229 return {context: lhs, name: rhs, value: value}; 15230 } else { 15231 return value; 15232 } 15233 }; 15234 }, 15235 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 15236 return function(scope, locals, assign, inputs) { 15237 var lhs = left(scope, locals, assign, inputs); 15238 if (create && create !== 1) { 15239 ensureSafeAssignContext(lhs); 15240 if (lhs && !(lhs[right])) { 15241 lhs[right] = {}; 15242 } 15243 } 15244 var value = lhs != null ? lhs[right] : undefined; 15245 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 15246 ensureSafeObject(value, expression); 15247 } 15248 if (context) { 15249 return {context: lhs, name: right, value: value}; 15250 } else { 15251 return value; 15252 } 15253 }; 15254 }, 15255 inputs: function(input, watchId) { 15256 return function(scope, value, locals, inputs) {
15257 if (inputs) return inputs[watchId]; 15258 return input(scope, value, locals); 15259 }; 15260 } 15261}; 15262 15263/** 15264 * @constructor 15265 */ 15266var Parser = function(lexer, $filter, options) { 15267 this.lexer = lexer; 15268 this.$filter = $filter; 15269 this.options = options; 15270 this.ast = new AST(lexer, options); 15271 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 15272 new ASTCompiler(this.ast, $filter); 15273}; 15274 15275Parser.prototype = { 15276 constructor: Parser, 15277 15278 parse: function(text) { 15279 return this.astCompiler.compile(text, this.options.expensiveChecks); 15280 } 15281}; 15282 15283function isPossiblyDangerousMemberName(name) { 15284 return name == 'constructor'; 15285} 15286 15287var objectValueOf = Object.prototype.valueOf; 15288 15289function getValueOf(value) { 15290 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 15291} 15292 15293/////////////////////////////////// 15294 15295/** 15296 * @ngdoc service 15297 * @name $parse 15298 * @kind function 15299 * 15300 * @description 15301 * 15302 * Converts Angular {@link guide/expression expression} into a function. 15303 * 15304 * ```js 15305 * var getter = $parse('user.name'); 15306 * var setter = getter.assign; 15307 * var context = {user:{name:'angular'}}; 15308 * var locals = {user:{name:'local'}}; 15309 * 15310 * expect(getter(context)).toEqual('angular'); 15311 * setter(context, 'newValue'); 15312 * expect(context.user.name).toEqual('newValue'); 15313 * expect(getter(context, locals)).toEqual('local'); 15314 * ``` 15315 * 15316 * 15317 * @param {string} expression String expression to compile. 15318 * @returns {function(context, locals)} a function which represents the compiled expression: 15319 * 15320 * * `context` – `{object}` – an object against which any expressions embedded in the strings 15321 * are evaluated against (typically a scope object). 15322 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 15323 * `context`. 15324 * 15325 * The returned function also has the following properties: 15326 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript 15327 * literal. 15328 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript 15329 * constant literals. 15330 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be 15331 * set to a function to change its value on the given context. 15332 * 15333 */ 15334 15335 15336/** 15337 * @ngdoc provider 15338 * @name $parseProvider 15339 * 15340 * @description 15341 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 15342 * service. 15343 */ 15344function $ParseProvider() { 15345 var cacheDefault = createMap(); 15346 var cacheExpensive = createMap(); 15347 var literals = { 15348 'true': true, 15349 'false': false, 15350 'null': null, 15351 'undefined': undefined 15352 }; 15353 var identStart, identContinue; 15354 15355 /** 15356 * @ngdoc method 15357 * @name $parseProvider#addLiteral 15358 * @description 15359 * 15360 * Configure $parse service to add literal values that will be present as literal at expressions. 15361 * 15362 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. 15363 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. 15364 * 15365 **/ 15366 this.addLiteral = function(literalName, literalValue) { 15367 literals[literalName] = literalValue; 15368 }; 15369 15370 /** 15371 * @ngdoc method 15372 * @name $parseProvider#setIdentifierFns 15373 * @description 15374 * 15375 * Allows defining the set of characters that are allowed in Angular expressions. The function 15376 * `identifierStart` will get called to know if a given character is a valid character to be the 15377 * first character for an identifier. The function `identifierContinue` will get called to know if 15378 * a given character is a valid character to be a follow-up identifier character. The functions 15379 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be 15380 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in 15381 * mind that the `string` parameter can be two characters long depending on the character 15382 * representation. It is expected for the function to return `true` or `false`, whether that 15383 * character is allowed or not. 15384 * 15385 * Since this function will be called extensivelly, keep the implementation of these functions fast, 15386 * as the performance of these functions have a direct impact on the expressions parsing speed. 15387 * 15388 * @param {function=} identifierStart The function that will decide whether the given character is 15389 * a valid identifier start character. 15390 * @param {function=} identifierContinue The function that will decide whether the given character is 15391 * a valid identifier continue character. 15392 */ 15393 this.setIdentifierFns = function(identifierStart, identifierContinue) { 15394 identStart = identifierStart; 15395 identContinue = identifierContinue; 15396 return this; 15397 }; 15398 15399 this.$get = ['$filter', function($filter) { 15400 var noUnsafeEval = csp().noUnsafeEval; 15401 var $parseOptions = { 15402 csp: noUnsafeEval,
15403 expensiveChecks: false, 15404 literals: copy(literals), 15405 isIdentifierStart: isFunction(identStart) && identStart, 15406 isIdentifierContinue: isFunction(identContinue) && identContinue 15407 }, 15408 $parseOptionsExpensive = { 15409 csp: noUnsafeEval, 15410 expensiveChecks: true, 15411 literals: copy(literals), 15412 isIdentifierStart: isFunction(identStart) && identStart, 15413 isIdentifierContinue: isFunction(identContinue) && identContinue 15414 }; 15415 var runningChecksEnabled = false; 15416 15417 $parse.$$runningExpensiveChecks = function() { 15418 return runningChecksEnabled; 15419 }; 15420 15421 return $parse; 15422 15423 function $parse(exp, interceptorFn, expensiveChecks) { 15424 var parsedExpression, oneTime, cacheKey; 15425 15426 expensiveChecks = expensiveChecks || runningChecksEnabled; 15427 15428 switch (typeof exp) { 15429 case 'string': 15430 exp = exp.trim(); 15431 cacheKey = exp; 15432 15433 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 15434 parsedExpression = cache[cacheKey]; 15435 15436 if (!parsedExpression) { 15437 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 15438 oneTime = true; 15439 exp = exp.substring(2); 15440 } 15441 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 15442 var lexer = new Lexer(parseOptions); 15443 var parser = new Parser(lexer, $filter, parseOptions); 15444 parsedExpression = parser.parse(exp); 15445 if (parsedExpression.constant) { 15446 parsedExpression.$$watchDelegate = constantWatchDelegate; 15447 } else if (oneTime) { 15448 parsedExpression.$$watchDelegate = parsedExpression.literal ? 15449 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 15450 } else if (parsedExpression.inputs) { 15451 parsedExpression.$$watchDelegate = inputsWatchDelegate; 15452 } 15453 if (expensiveChecks) { 15454 parsedExpression = expensiveChecksInterceptor(parsedExpression); 15455 } 15456 cache[cacheKey] = parsedExpression; 15457 } 15458 return addInterceptor(parsedExpression, interceptorFn); 15459 15460 case 'function': 15461 return addInterceptor(exp, interceptorFn); 15462 15463 default: 15464 return addInterceptor(noop, interceptorFn); 15465 } 15466 } 15467 15468 function expensiveChecksInterceptor(fn) { 15469 if (!fn) return fn; 15470 expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; 15471 expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); 15472 expensiveCheckFn.constant = fn.constant; 15473 expensiveCheckFn.literal = fn.literal; 15474 for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { 15475 fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); 15476 } 15477 expensiveCheckFn.inputs = fn.inputs; 15478 15479 return expensiveCheckFn; 15480 15481 function expensiveCheckFn(scope, locals, assign, inputs) { 15482 var expensiveCheckOldValue = runningChecksEnabled; 15483 runningChecksEnabled = true; 15484 try { 15485 return fn(scope, locals, assign, inputs); 15486 } finally { 15487 runningChecksEnabled = expensiveCheckOldValue; 15488 } 15489 } 15490 } 15491 15492 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 15493 15494 if (newValue == null || oldValueOfValue == null) { // null/undefined 15495 return newValue === oldValueOfValue; 15496 } 15497 15498 if (typeof newValue === 'object') { 15499 15500 // attempt to convert the value to a primitive type 15501 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 15502 // be cheaply dirty-checked 15503 newValue = getValueOf(newValue); 15504 15505 if (typeof newValue === 'object') { 15506 // objects/arrays are not supported - deep-watching them would be too expensive 15507 return false; 15508 } 15509 15510 // fall-through to the primitive equality check 15511 } 15512 15513 //Primitive or NaN 15514 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 15515 } 15516 15517 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 15518 var inputExpressions = parsedExpression.inputs; 15519 var lastResult; 15520 15521 if (inputExpressions.length === 1) { 15522 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 15523 inputExpressions = inputExpressions[0]; 15524 return scope.$watch(function expressionInputWatch(scope) { 15525 var newInputValue = inputExpressions(scope); 15526 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 15527 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 15528 oldInputValueOf = newInputValue && getValueOf(newInputValue); 15529 } 15530 return lastResult; 15531 }, listener, objectEquality, prettyPrintExpression); 15532 } 15533 15534 var oldInputValueOfValues = []; 15535 var oldInputValues = []; 15536 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 15537 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 15538 oldInputValues[i] = null; 15539 } 15540 15541 return scope.$watch(function expressionInputsWatch(scope) { 15542 var changed = false;
15543 15544 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 15545 var newInputValue = inputExpressions[i](scope); 15546 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 15547 oldInputValues[i] = newInputValue; 15548 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 15549 } 15550 } 15551 15552 if (changed) { 15553 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 15554 } 15555 15556 return lastResult; 15557 }, listener, objectEquality, prettyPrintExpression); 15558 } 15559 15560 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 15561 var unwatch, lastValue; 15562 return unwatch = scope.$watch(function oneTimeWatch(scope) { 15563 return parsedExpression(scope); 15564 }, function oneTimeListener(value, old, scope) { 15565 lastValue = value; 15566 if (isFunction(listener)) { 15567 listener.apply(this, arguments); 15568 } 15569 if (isDefined(value)) { 15570 scope.$$postDigest(function() { 15571 if (isDefined(lastValue)) { 15572 unwatch(); 15573 } 15574 }); 15575 } 15576 }, objectEquality); 15577 } 15578 15579 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 15580 var unwatch, lastValue; 15581 return unwatch = scope.$watch(function oneTimeWatch(scope) { 15582 return parsedExpression(scope); 15583 }, function oneTimeListener(value, old, scope) { 15584 lastValue = value; 15585 if (isFunction(listener)) { 15586 listener.call(this, value, old, scope); 15587 } 15588 if (isAllDefined(value)) { 15589 scope.$$postDigest(function() { 15590 if (isAllDefined(lastValue)) unwatch(); 15591 }); 15592 } 15593 }, objectEquality); 15594 15595 function isAllDefined(value) { 15596 var allDefined = true; 15597 forEach(value, function(val) { 15598 if (!isDefined(val)) allDefined = false; 15599 }); 15600 return allDefined; 15601 } 15602 } 15603 15604 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 15605 var unwatch; 15606 return unwatch = scope.$watch(function constantWatch(scope) { 15607 unwatch(); 15608 return parsedExpression(scope); 15609 }, listener, objectEquality); 15610 } 15611 15612 function addInterceptor(parsedExpression, interceptorFn) { 15613 if (!interceptorFn) return parsedExpression; 15614 var watchDelegate = parsedExpression.$$watchDelegate; 15615 var useInputs = false; 15616 15617 var regularWatch = 15618 watchDelegate !== oneTimeLiteralWatchDelegate && 15619 watchDelegate !== oneTimeWatchDelegate; 15620 15621 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 15622 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 15623 return interceptorFn(value, scope, locals); 15624 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 15625 var value = parsedExpression(scope, locals, assign, inputs); 15626 var result = interceptorFn(value, scope, locals); 15627 // we only return the interceptor's result if the 15628 // initial value is defined (for bind-once) 15629 return isDefined(value) ? result : value; 15630 }; 15631 15632 // Propagate $$watchDelegates other then inputsWatchDelegate 15633 if (parsedExpression.$$watchDelegate && 15634 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 15635 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 15636 } else if (!interceptorFn.$stateful) { 15637 // If there is an interceptor, but no watchDelegate then treat the interceptor like 15638 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 15639 fn.$$watchDelegate = inputsWatchDelegate; 15640 useInputs = !parsedExpression.inputs; 15641 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 15642 } 15643 15644 return fn; 15645 } 15646 }]; 15647} 15648 15649/** 15650 * @ngdoc service 15651 * @name $q 15652 * @requires $rootScope 15653 * 15654 * @description 15655 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 15656 * when they are done processing. 15657 * 15658 * This is an implementation of promises/deferred objects inspired by 15659 * [Kris Kowal's Q](https://github.com/kriskowal/q). 15660 * 15661 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 15662 * implementations, and the other which resembles ES6 (ES2015) promises to
15662some degree. 15663 * 15664 * # $q constructor 15665 * 15666 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 15667 * function as the first argument. This is similar to the native Promise implementation from ES6, 15668 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 15669 * 15670 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are 15671 * available yet. 15672 * 15673 * It can be used like so: 15674 * 15675 * ```js 15676 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 15677 * // are available in the current lexical scope (they could have been injected or passed in). 15678 * 15679 * function asyncGreet(name) { 15680 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 15681 * return $q(function(resolve, reject) { 15682 * setTimeout(function() { 15683 * if (okToGreet(name)) { 15684 * resolve('Hello, ' + name + '!'); 15685 * } else { 15686 * reject('Greeting ' + name + ' is not allowed.'); 15687 * } 15688 * }, 1000); 15689 * }); 15690 * } 15691 * 15692 * var promise = asyncGreet('Robin Hood'); 15693 * promise.then(function(greeting) { 15694 * alert('Success: ' + greeting); 15695 * }, function(reason) { 15696 * alert('Failed: ' + reason); 15697 * }); 15698 * ``` 15699 * 15700 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 15701 * 15702 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 15703 * 15704 * However, the more traditional CommonJS-style usage is still available, and documented below. 15705 * 15706 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 15707 * interface for interacting with an object that represents the result of an action that is 15708 * performed asynchronously, and may or may not be finished at any given point in time. 15709 * 15710 * From the perspective of dealing with error handling, deferred and promise APIs are to 15711 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 15712 * 15713 * ```js 15714 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 15715 * // are available in the current lexical scope (they could have been injected or passed in). 15716 * 15717 * function asyncGreet(name) { 15718 * var deferred = $q.defer(); 15719 * 15720 * setTimeout(function() { 15721 * deferred.notify('About to greet ' + name + '.'); 15722 * 15723 * if (okToGreet(name)) { 15724 * deferred.resolve('Hello, ' + name + '!'); 15725 * } else { 15726 * deferred.reject('Greeting ' + name + ' is not allowed.'); 15727 * } 15728 * }, 1000); 15729 * 15730 * return deferred.promise; 15731 * } 15732 * 15733 * var promise = asyncGreet('Robin Hood'); 15734 * promise.then(function(greeting) { 15735 * alert('Success: ' + greeting); 15736 * }, function(reason) { 15737 * alert('Failed: ' + reason); 15738 * }, function(update) { 15739 * alert('Got notification: ' + update); 15740 * }); 15741 * ``` 15742 * 15743 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 15744 * comes in the way of guarantees that promise and deferred APIs make, see 15745 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 15746 * 15747 * Additionally the promise api allows for composition that is very hard to do with the 15748 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 15749 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 15750 * section on serial or parallel joining of promises. 15751 * 15752 * # The Deferred API 15753 * 15754 * A new instance of deferred is constructed by calling `$q.defer()`. 15755 * 15756 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 15757 * that can be used for signaling the successful or unsuccessful completion, as well as the status 15758 * of the task. 15759 * 15760 * **Methods** 15761 * 15762 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection 15763 * constructed via `$q.reject`, the promise will be rejected instead. 15764 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to 15765 * resolving it with a rejection constructed via `$q.reject`. 15766 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 15767 * multiple times before the promise is either resolved or rejected. 15768 * 15769 * **Properties** 15770 * 15771 * - promise – `{Promise}` – promise object associated with this deferred. 15772 * 15773 * 15774 * # The Promise API 15775 * 15776 * A new promise instance is created when a deferred instance is created and can be retrieved by 15777 * calling `deferred.promise`. 15778 * 15779 * The purpose of the promise object is to allow for interested parties to get access to the result 15780 * of the deferred task when it completes. 15781 * 15782 * **Methods** 15783 * 15784 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or 15785 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 15786 * as soon as the result is available. The callbacks are called with a single argument: the result 15787 * or rejection reason. Additionally, the notify callback may be called zero or more times to 15788 * provide a progress indication, before the promise is resolved or rejected. 15789 * 15790 * This method *returns a new promise* which is resolved or rejected via the return value of the 15791 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
15792 * with the value which is resolved in that promise using 15793 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 15794 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 15795 * resolved or rejected from the notifyCallback method. 15796 * 15797 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` 15798 * 15799 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, 15800 * but to do so without modifying the final value. This is useful to release resources or do some 15801 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 15802 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 15803 * more information. 15804 * 15805 * # Chaining promises 15806 * 15807 * Because calling the `then` method of a promise returns a new derived promise, it is easily 15808 * possible to create a chain of promises: 15809 * 15810 * ```js 15811 * promiseB = promiseA.then(function(result) { 15812 * return result + 1; 15813 * }); 15814 * 15815 * // promiseB will be resolved immediately after promiseA is resolved and its value 15816 * // will be the result of promiseA incremented by 1 15817 * ``` 15818 * 15819 * It is possible to create chains of any length and since a promise can be resolved with another 15820 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 15821 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 15822 * $http's response interceptors. 15823 * 15824 * 15825 * # Differences between Kris Kowal's Q and $q 15826 * 15827 * There are two main differences: 15828 * 15829 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 15830 * mechanism in angular, which means faster propagation of resolution or rejection into your 15831 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 15832 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
15833 * all the important functionality needed for common async tasks. 15834 * 15835 * # Testing 15836 * 15837 * ```js 15838 * it('should simulate promise', inject(function($q, $rootScope) { 15839 * var deferred = $q.defer(); 15840 * var promise = deferred.promise; 15841 * var resolvedValue; 15842 * 15843 * promise.then(function(value) { resolvedValue = value; }); 15844 * expect(resolvedValue).toBeUndefined(); 15845 * 15846 * // Simulate resolving of promise 15847 * deferred.resolve(123); 15848 * // Note that the 'then' function does not get called synchronously. 15849 * // This is because we want the promise API to always be async, whether or not 15850 * // it got called synchronously or asynchronously. 15851 * expect(resolvedValue).toBeUndefined(); 15852 * 15853 * // Propagate promise resolution to 'then' functions using $apply(). 15854 * $rootScope.$apply(); 15855 * expect(resolvedValue).toEqual(123); 15856 * })); 15857 * ``` 15858 * 15859 * @param {function(function, function)} resolver Function which is responsible for resolving or 15860 * rejecting the newly created promise. The first parameter is a function which resolves the 15861 * promise, the second parameter is a function which rejects the promise. 15862 * 15863 * @returns {Promise} The newly created promise. 15864 */ 15865function $QProvider() { 15866 15867 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 15868 return qFactory(function(callback) { 15869 $rootScope.$evalAsync(callback); 15870 }, $exceptionHandler); 15871 }]; 15872} 15873 15874function $$QProvider() { 15875 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 15876 return qFactory(function(callback) { 15877 $browser.defer(callback); 15878 }, $exceptionHandler); 15879 }]; 15880} 15881 15882/** 15883 * Constructs a promise manager. 15884 * 15885 * @param {function(function)} nextTick Function for executing functions in the next turn. 15886 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 15887 * debugging purposes. 15888 * @returns {object} Promise manager. 15889 */ 15890function qFactory(nextTick, exceptionHandler) { 15891 var $qMinErr = minErr('$q', TypeError); 15892 15893 /** 15894 * @ngdoc method 15895 * @name ng.$q#defer 15896 * @kind function 15897 * 15898 * @description 15899 * Creates a `Deferred` object which represents a task which will finish in the future. 15900 * 15901 * @returns {Deferred} Returns a new instance of deferred. 15902 */ 15903 var defer = function() { 15904 var d = new Deferred(); 15905 //Necessary to support unbound execution :/ 15906 d.resolve = simpleBind(d, d.resolve); 15907 d.reject = simpleBind(d, d.reject); 15908 d.notify = simpleBind(d, d.notify); 15909 return d; 15910 }; 15911 15912 function Promise() { 15913 this.$$state = { status: 0 }; 15914 } 15915 15916 extend(Promise.prototype, { 15917 then: function(onFulfilled, onRejected, progressBack) { 15918 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 15919 return this; 15920 } 15921 var result = new Deferred(); 15922 15923 this.$$state.pending = this.$$state.pending || []; 15924 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 15925 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 15926 15927 return result.promise; 15928 }, 15929 15930 "catch": function(callback) { 15931 return this.then(null, callback); 15932 }, 15933 15934 "finally": function(callback, progressBack) { 15935 return this.then(function(value) { 15936 return handleCallback(value, true, callback); 15937 }, function(error) { 15938 return handleCallback(error, false, callback); 15939 }, progressBack); 15940 } 15941 }); 15942 15943 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 15944 function simpleBind(context, fn) { 15945 return function(value) { 15946 fn.call(context, value); 15947 }; 15948 } 15949 15950 function processQueue(state) { 15951 var fn, deferred, pending; 15952 15953 pending = state.pending; 15954 state.processScheduled = false; 15955 state.pending = undefined; 15956 for (var i = 0, ii = pending.length; i < ii; ++i) { 15957 deferred = pending[i][0]; 15958 fn = pending[i][state.status]; 15959 try { 15960 if (isFunction(fn)) { 15961 deferred.resolve(fn(state.value)); 15962 } else if (state.status === 1) { 15963 deferred.resolve(state.value); 15964 } else { 15965 deferred.reject(state.value); 15966 } 15967 } catch (e) { 15968 deferred.reject(e); 15969 exceptionHandler(e); 15970 } 15971 } 15972 } 15973 15974 function scheduleProcessQueue(state) { 15975 if (state.processScheduled || !state.pending) return; 15976 state.processScheduled = true; 15977 nextTick(function() { processQueue(state); }); 15978 } 15979 15980 function Deferred() { 15981 this.promise = new Promise(); 15982 } 15983 15984 extend(Deferred.prototype, { 15985 resolve: function(val) { 15986 if (this.promise.$$state.status) return; 15987 if (val === this.promise) { 15988 this.$$reject($qMinErr( 15989 'qcycle', 15990 "Expected promise to be resolved with value other than
15990itself '{0}'", 15991 val)); 15992 } else { 15993 this.$$resolve(val); 15994 } 15995 15996 }, 15997 15998 $$resolve: function(val) { 15999 var then; 16000 var that = this; 16001 var done = false; 16002 try { 16003 if ((isObject(val) || isFunction(val))) then = val && val.then; 16004 if (isFunction(then)) { 16005 this.promise.$$state.status = -1; 16006 then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); 16007 } else { 16008 this.promise.$$state.value = val; 16009 this.promise.$$state.status = 1; 16010 scheduleProcessQueue(this.promise.$$state); 16011 } 16012 } catch (e) { 16013 rejectPromise(e); 16014 exceptionHandler(e); 16015 } 16016 16017 function resolvePromise(val) { 16018 if (done) return; 16019 done = true; 16020 that.$$resolve(val); 16021 } 16022 function rejectPromise(val) { 16023 if (done) return; 16024 done = true; 16025 that.$$reject(val); 16026 } 16027 }, 16028 16029 reject: function(reason) { 16030 if (this.promise.$$state.status) return; 16031 this.$$reject(reason); 16032 }, 16033 16034 $$reject: function(reason) { 16035 this.promise.$$state.value = reason; 16036 this.promise.$$state.status = 2; 16037 scheduleProcessQueue(this.promise.$$state); 16038 }, 16039 16040 notify: function(progress) { 16041 var callbacks = this.promise.$$state.pending; 16042 16043 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 16044 nextTick(function() { 16045 var callback, result; 16046 for (var i = 0, ii = callbacks.length; i < ii; i++) { 16047 result = callbacks[i][0]; 16048 callback = callbacks[i][3]; 16049 try { 16050 result.notify(isFunction(callback) ? callback(progress) : progress); 16051 } catch (e) { 16052 exceptionHandler(e); 16053 } 16054 } 16055 }); 16056 } 16057 } 16058 }); 16059 16060 /** 16061 * @ngdoc method 16062 * @name $q#reject 16063 * @kind function 16064 * 16065 * @description 16066 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 16067 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 16068 * a promise chain, you don't need to worry about it. 16069 * 16070 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 16071 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 16072 * a promise error callback and you want to forward the error to the promise derived from the 16073 * current promise, you have to "rethrow" the error by returning a rejection constructed via 16074 * `reject`. 16075 * 16076 * ```js 16077 * promiseB = promiseA.then(function(result) { 16078 * // success: do something and resolve promiseB 16079 * // with the old or a new result 16080 * return result; 16081 * }, function(reason) { 16082 * // error: handle the error if possible and 16083 * // resolve promiseB with newPromiseOrValue, 16084 * // otherwise forward the rejection to promiseB 16085 * if (canHandle(reason)) { 16086 * // handle the error and recover 16087 * return newPromiseOrValue; 16088 * } 16089 * return $q.reject(reason); 16090 * }); 16091 * ``` 16092 * 16093 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 16094 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 16095 */ 16096 var reject = function(reason) { 16097 var result = new Deferred(); 16098 result.reject(reason); 16099 return result.promise; 16100 }; 16101 16102 var makePromise = function makePromise(value, resolved) { 16103 var result = new Deferred(); 16104 if (resolved) { 16105 result.resolve(value); 16106 } else { 16107 result.reject(value); 16108 } 16109 return result.promise; 16110 }; 16111 16112 var handleCallback = function handleCallback(value, isResolved, callback) { 16113 var callbackOutput = null; 16114 try { 16115 if (isFunction(callback)) callbackOutput = callback(); 16116 } catch (e) { 16117 return makePromise(e, false); 16118 } 16119 if (isPromiseLike(callbackOutput)) { 16120 return callbackOutput.then(function() { 16121 return makePromise(value, isResolved); 16122 }, function(error) { 16123 return makePromise(error, false); 16124 }); 16125 } else { 16126 return makePromise(value, isResolved); 16127 } 16128 }; 16129 16130 /** 16131 * @ngdoc method 16132 * @name $q#when 16133 * @kind function 16134 * 16135 * @description 16136 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 16137 * This is useful when you are dealing with an object that might or might not be a promise, or if 16138 * the promise comes from a source that can't be trusted. 16139 * 16140 * @param {*} value Value or a promise 16141 * @param {Function=} successCallback 16142 * @param {Function=} errorCallback 16143 * @param {Function=} progressCallback 16144 * @returns {Promise} Returns a promise of the passed value or promise 16145 */ 16146 16147 16148 var when = function(value, callback, errback, progressBack) { 16149 var result = new Deferred(); 16150 result.resolve(value); 16151 return result.promise.then(callback, errback, progressBack); 16152 }; 16153 16154 /** 16155 * @ngdoc method 16156 * @name $q#resolve 16157 * @kind function 16158 * 16159 * @description 16160 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 16161 * 16162 * @param {*} value Value or a promise 16163 * @param {Function=} successCallback 16164 * @param {Function=} errorCallback 16165 * @param {Function=} progressCallback 16166 * @returns {Promise} Returns a promise of the passed value or promise 16167 */ 16168 var resolve = when; 16169 16170 /** 16171 * @ngdoc method 16172 * @name $q#all 16173 * @kind function 16174 * 16175 * @description 16176 * Combines multiple promises into a single promise that is resolved when all of the input 16177 * promises are resolved. 16178 * 16179 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 16180 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 16181 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 16182 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 16183 * with the same rejection value. 16184 */ 16185 16186 function all(promises) { 16187 var deferred = new Deferred(), 16188 counter = 0, 16189 results = isArray(promises) ? [] : {}; 16190
16191 forEach(promises, function(promise, key) { 16192 counter++; 16193 when(promise).then(function(value) { 16194 if (results.hasOwnProperty(key)) return; 16195 results[key] = value; 16196 if (!(--counter)) deferred.resolve(results); 16197 }, function(reason) { 16198 if (results.hasOwnProperty(key)) return; 16199 deferred.reject(reason); 16200 }); 16201 }); 16202 16203 if (counter === 0) { 16204 deferred.resolve(results); 16205 } 16206 16207 return deferred.promise; 16208 } 16209 16210 var $Q = function Q(resolver) { 16211 if (!isFunction(resolver)) { 16212 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 16213 } 16214 16215 var deferred = new Deferred(); 16216 16217 function resolveFn(value) { 16218 deferred.resolve(value); 16219 } 16220 16221 function rejectFn(reason) { 16222 deferred.reject(reason); 16223 } 16224 16225 resolver(resolveFn, rejectFn); 16226 16227 return deferred.promise; 16228 }; 16229 16230 // Let's make the instanceof operator work for promises, so that 16231 // `new $q(fn) instanceof $q` would evaluate to true. 16232 $Q.prototype = Promise.prototype; 16233 16234 $Q.defer = defer; 16235 $Q.reject = reject; 16236 $Q.when = when; 16237 $Q.resolve = resolve; 16238 $Q.all = all; 16239 16240 return $Q; 16241} 16242 16243function $$RAFProvider() { //rAF 16244 this.$get = ['$window', '$timeout', function($window, $timeout) { 16245 var requestAnimationFrame = $window.requestAnimationFrame || 16246 $window.webkitRequestAnimationFrame; 16247 16248 var cancelAnimationFrame = $window.cancelAnimationFrame || 16249 $window.webkitCancelAnimationFrame || 16250 $window.webkitCancelRequestAnimationFrame; 16251 16252 var rafSupported = !!requestAnimationFrame; 16253 var raf = rafSupported 16254 ? function(fn) { 16255 var id = requestAnimationFrame(fn); 16256 return function() { 16257 cancelAnimationFrame(id); 16258 }; 16259 } 16260 : function(fn) { 16261 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 16262 return function() { 16263 $timeout.cancel(timer); 16264 }; 16265 }; 16266 16267 raf.supported = rafSupported; 16268 16269 return raf; 16270 }]; 16271} 16272 16273/** 16274 * DESIGN NOTES 16275 * 16276 * The design decisions behind the scope are heavily favored for speed and memory consumption. 16277 * 16278 * The typical use of scope is to watch the expressions, which most of the time return the same 16279 * value as last time so we optimize the operation. 16280 * 16281 * Closures construction is expensive in terms of speed as well as memory: 16282 * - No closures, instead use prototypical inheritance for API 16283 * - Internal state needs to be stored on scope directly, which means that private state is 16284 * exposed as $$____ properties 16285 * 16286 * Loop operations are optimized by using while(count--) { ... } 16287 * - This means that in order to keep the same order of execution as addition we have to add 16288 * items to the array at the beginning (unshift) instead of at the end (push) 16289 * 16290 * Child scopes are created and removed often 16291 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 16292 * 16293 * There are fewer watches than observers. This is why you don't want the observer to be implemented 16294 * in the same way as watch. Watch requires return of the initialization function which is expensive 16295 * to construct. 16296 */ 16297 16298 16299/** 16300 * @ngdoc provider 16301 * @name $rootScopeProvider 16302 * @description 16303 * 16304 * Provider for the $rootScope service. 16305 */ 16306 16307/** 16308 * @ngdoc method 16309 * @name $rootScopeProvider#digestTtl 16310 * @description 16311 * 16312 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 16313 * assuming that the model is unstable. 16314 * 16315 * The current default is 10 iterations. 16316 * 16317 * In complex applications it's possible that the dependencies between `$watch`s will result in 16318 * several digest iterations. However if an application needs more than the default 10 digest 16319 * iterations for its model to stabilize then you should investigate what is causing the model to 16320 * continuously change during the digest. 16321 * 16322 * Increasing the TTL could have performance implications, so you should not change it without 16323 * proper justification. 16324 * 16325 * @param {number} limit The number of digest iterations. 16326 */ 16327 16328 16329/** 16330 * @ngdoc service 16331 * @name $rootScope 16332 * @description 16333 * 16334 * Every application has a single root {@link ng.$rootScope.Scope scope}. 16335 * All other scopes are descendant scopes of the root scope. Scopes provide separation 16336 * between the model and the view, via a mechanism for watching the model for changes. 16337 * They also provide event emission/broadcast and subscription facility. See the 16338 * {@link guide/scope developer guide on scopes}. 16339 */ 16340function $RootScopeProvider() { 16341 var TTL = 10; 16342 var $rootScopeMinErr = minErr('$rootScope'); 16343 var lastDirtyWatch = null; 16344 var applyAsyncId = null; 16345 16346 this.digestTtl = function(value) { 16347 if (arguments.length) { 16348 TTL = value; 16349 } 16350 return TTL; 16351 }; 16352 16353 function createChildScopeClass(parent) { 16354 function ChildScope() {
16355 this.$$watchers = this.$$nextSibling = 16356 this.$$childHead = this.$$childTail = null; 16357 this.$$listeners = {}; 16358 this.$$listenerCount = {}; 16359 this.$$watchersCount = 0; 16360 this.$id = nextUid(); 16361 this.$$ChildScope = null; 16362 } 16363 ChildScope.prototype = parent; 16364 return ChildScope; 16365 } 16366 16367 this.$get = ['$exceptionHandler', '$parse', '$browser', 16368 function($exceptionHandler, $parse, $browser) { 16369 16370 function destroyChildScope($event) { 16371 $event.currentScope.$$destroyed = true; 16372 } 16373 16374 function cleanUpScope($scope) { 16375 16376 if (msie === 9) { 16377 // There is a memory leak in IE9 if all child scopes are not disconnected 16378 // completely when a scope is destroyed. So this code will recurse up through 16379 // all this scopes children 16380 // 16381 // See issue https://github.com/angular/angular.js/issues/10706 16382 $scope.$$childHead && cleanUpScope($scope.$$childHead); 16383 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling); 16384 } 16385 16386 // The code below works around IE9 and V8's memory leaks 16387 // 16388 // See: 16389 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 16390 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 16391 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 16392 16393 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 16394 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 16395 } 16396 16397 /** 16398 * @ngdoc type 16399 * @name $rootScope.Scope 16400 * 16401 * @description 16402 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 16403 * {@link auto.$injector $injector}. Child scopes are created using the 16404 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 16405 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 16406 * an in-depth introduction and usage examples. 16407 * 16408 * 16409 * # Inheritance 16410 * A scope can inherit from a parent scope, as in this example: 16411 * ```js 16412 var parent = $rootScope; 16413 var child = parent.$new(); 16414 16415 parent.salutation = "Hello"; 16416 expect(child.salutation).toEqual('Hello'); 16417 16418 child.salutation = "Welcome"; 16419 expect(child.salutation).toEqual('Welcome'); 16420 expect(parent.salutation).toEqual('Hello'); 16421 * ``` 16422 * 16423 * When interacting with `Scope` in tests, additional helper methods are available on the 16424 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 16425 * details. 16426 * 16427 * 16428 * @param {Object.<string, function()>=} providers Map of service factory which need to be 16429 * provided for the current scope. Defaults to {@link ng}. 16430 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 16431 * append/override services provided by `providers`. This is handy 16432 * when unit-testing and having the need to override a default 16433 * service. 16434 * @returns {Object} Newly created scope. 16435 * 16436 */ 16437 function Scope() { 16438 this.$id = nextUid(); 16439 this.$$phase = this.$parent = this.$$watchers = 16440 this.$$nextSibling = this.$$prevSibling = 16441 this.$$childHead = this.$$childTail = null; 16442 this.$root = this; 16443 this.$$destroyed = false; 16444 this.$$listeners = {}; 16445 this.$$listenerCount = {}; 16446 this.$$watchersCount = 0; 16447 this.$$isolateBindings = null; 16448 } 16449 16450 /** 16451 * @ngdoc property 16452 * @name $rootScope.Scope#$id 16453 * 16454 * @description 16455 * Unique scope ID (monotonically increasing) useful for debugging. 16456 */ 16457 16458 /** 16459 * @ngdoc property 16460 * @name $rootScope.Scope#$parent 16461 * 16462 * @description 16463 * Reference to the parent scope. 16464 */ 16465 16466 /** 16467 * @ngdoc property 16468 * @name $rootScope.Scope#$root 16469 *
vendor: 6,672 bytes, lines 16470-16608
16470 * @description 16471 * Reference to the root scope. 16472 */ 16473 16474 Scope.prototype = { 16475 constructor: Scope, 16476 /** 16477 * @ngdoc method 16478 * @name $rootScope.Scope#$new 16479 * @kind function 16480 * 16481 * @description 16482 * Creates a new child {@link ng.$rootScope.Scope scope}. 16483 * 16484 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 16485 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 16486 * 16487 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 16488 * desired for the scope and its child scopes to be permanently detached from the parent and 16489 * thus stop participating in model change detection and listener notification by invoking. 16490 * 16491 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 16492 * parent scope. The scope is isolated, as it can not see parent scope properties. 16493 * When creating widgets, it is useful for the widget to not accidentally read parent 16494 * state. 16495 * 16496 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 16497 * of the newly created scope. Defaults to `this` scope if not provided. 16498 * This is used when creating a transclude scope to correctly place it 16499 * in the scope hierarchy while maintaining the correct prototypical 16500 * inheritance. 16501 * 16502 * @returns {Object} The newly created child scope. 16503 * 16504 */ 16505 $new: function(isolate, parent) { 16506 var child; 16507 16508 parent = parent || this; 16509 16510 if (isolate) { 16511 child = new Scope(); 16512 child.$root = this.$root; 16513 } else { 16514 // Only create a child scope class if somebody asks for one, 16515 // but cache it to allow the VM to optimize lookups. 16516 if (!this.$$ChildScope) { 16517 this.$$ChildScope = createChildScopeClass(this); 16518 } 16519 child = new this.$$ChildScope(); 16520 } 16521 child.$parent = parent; 16522 child.$$prevSibling = parent.$$childTail; 16523 if (parent.$$childHead) { 16524 parent.$$childTail.$$nextSibling = child; 16525 parent.$$childTail = child; 16526 } else { 16527 parent.$$childHead = parent.$$childTail = child; 16528 } 16529 16530 // When the new scope is not isolated or we inherit from `this`, and 16531 // the parent scope is destroyed, the property `$$destroyed` is inherited 16532 // prototypically. In all other cases, this property needs to be set 16533 // when the parent scope is destroyed. 16534 // The listener needs to be added after the parent is set 16535 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 16536 16537 return child; 16538 }, 16539 16540 /** 16541 * @ngdoc method 16542 * @name $rootScope.Scope#$watch 16543 * @kind function 16544 * 16545 * @description 16546 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 16547 * 16548 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 16549 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 16550 * its value when executed multiple times with the same input because it may be executed multiple 16551 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 16552 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 16553 * - The `listener` is called only when the value from the current `watchExpression` and the 16554 * previous call to `watchExpression` are not equal (with the exception of the initial run, 16555 * see below). Inequality is determined according to reference inequality, 16556 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 16557 * via the `!==` Javascript operator, unless `objectEquality == true` 16558 * (see next point) 16559 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 16560 * according to the {@link angular.equals} function. To save the value of the object for 16561 * later comparison, the {@link angular.copy} function is used. This therefore means that 16562 * watching complex objects will have adverse memory and performance implications. 16563 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 16564 * This is achieved by rerunning the watchers until no changes are detected. The rerun 16565 * iteration limit is 10 to prevent an infinite loop deadlock. 16566 * 16567 * 16568 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 16569 * you can register a `watchExpression` function with no `listener`. (Be prepared for 16570 * multiple calls to your `watchExpression` because it will execute multiple times in a 16571 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 16572 * 16573 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 16574 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 16575 * watcher. In rare cases, this is undesirable because the listener is called when the result 16576 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 16577 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 16578 * listener was called due to initialization. 16579 * 16580 * 16581 * 16582 * # Example 16583 * ```js 16584 // let's assume that scope was dependency injected as the $rootScope 16585 var scope = $rootScope; 16586 scope.name = 'misko'; 16587 scope.counter = 0; 16588 16589 expect(scope.counter).toEqual(0); 16590 scope.$watch('name', function(newValue, oldValue) { 16591 scope.counter = scope.counter + 1; 16592 }); 16593 expect(scope.counter).toEqual(0); 16594 16595 scope.$digest(); 16596 // the listener is always called during the first $digest loop after it was registered 16597 expect(scope.counter).toEqual(1); 16598 16599 scope.$digest(); 16600 // but now it will not be called unless the value changes 16601 expect(scope.counter).toEqual(1); 16602 16603 scope.name = 'adam'; 16604 scope.$digest(); 16605 expect(scope.counter).toEqual(2); 16606 16607 16608
16609 // Using a function as a watchExpression 16610 var food; 16611 scope.foodCounter = 0; 16612 expect(scope.foodCounter).toEqual(0); 16613 scope.$watch( 16614 // This function returns the value being watched. It is called for each turn of the $digest loop 16615 function() { return food; }, 16616 // This is the change listener, called when the value returned from the above function changes 16617 function(newValue, oldValue) { 16618 if ( newValue !== oldValue ) { 16619 // Only increment the counter if the value changed 16620 scope.foodCounter = scope.foodCounter + 1; 16621 } 16622 } 16623 ); 16624 // No digest has been run so the counter will be zero 16625 expect(scope.foodCounter).toEqual(0); 16626 16627 // Run the digest but since food has not changed count will still be zero 16628 scope.$digest(); 16629 expect(scope.foodCounter).toEqual(0); 16630 16631 // Update food and run digest. Now the counter will increment 16632 food = 'cheeseburger'; 16633 scope.$digest(); 16634 expect(scope.foodCounter).toEqual(1); 16635 16636 * ``` 16637 * 16638 * 16639 * 16640 * @param {(function()|string)} watchExpression Expression that is evaluated on each 16641 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 16642 * a call to the `listener`. 16643 * 16644 * - `string`: Evaluated as {@link guide/expression expression} 16645 * - `function(scope)`: called with current `scope` as a parameter. 16646 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 16647 * of `watchExpression` changes. 16648 * 16649 * - `newVal` contains the current value of the `watchExpression` 16650 * - `oldVal` contains the previous value of the `watchExpression` 16651 * - `scope` refers to the current scope 16652 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 16653 * comparing for reference equality. 16654 * @returns {function()} Returns a deregistration function for this listener. 16655 */ 16656 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 16657 var get = $parse(watchExp); 16658 16659 if (get.$$watchDelegate) { 16660 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 16661 } 16662 var scope = this, 16663 array = scope.$$watchers, 16664 watcher = { 16665 fn: listener, 16666 last: initWatchVal, 16667 get: get, 16668 exp: prettyPrintExpression || watchExp, 16669 eq: !!objectEquality 16670 }; 16671 16672 lastDirtyWatch = null; 16673 16674 if (!isFunction(listener)) { 16675 watcher.fn = noop; 16676 } 16677 16678 if (!array) { 16679 array = scope.$$watchers = []; 16680 } 16681 // we use unshift since we use a while loop in $digest for speed. 16682 // the while loop reads in reverse order. 16683 array.unshift(watcher); 16684 incrementWatchersCount(this, 1); 16685 16686 return function deregisterWatch() { 16687 if (arrayRemove(array, watcher) >= 0) { 16688 incrementWatchersCount(scope, -1); 16689 } 16690 lastDirtyWatch = null; 16691 }; 16692 }, 16693 16694 /** 16695 * @ngdoc method 16696 * @name $rootScope.Scope#$watchGroup 16697 * @kind function 16698 * 16699 * @description 16700 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 16701 * If any one expression in the collection changes the `listener` is executed. 16702 * 16703 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 16704 * call to $digest() to see if any items changes. 16705 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 16706 * 16707 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 16708 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 16709 * 16710 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 16711 * expression in `watchExpressions` changes 16712 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 16713 * those of `watchExpression` 16714 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 16715 * those of `watchExpression` 16716 * The `scope` refers to the current scope. 16717 * @returns {function()} Returns a de-registration function for all listeners. 16718 */ 16719 $watchGroup: function(watchExpressions, listener) { 16720 var oldValues = new Array(watchExpressions.length); 16721 var newValues = new Array(watchExpressions.length); 16722 var deregisterFns = []; 16723 var self = this; 16724 var changeReactionScheduled = false;
vendor: 10,192 bytes, lines 16725-16987
16725 var firstRun = true; 16726 16727 if (!watchExpressions.length) { 16728 // No expressions means we call the listener ASAP 16729 var shouldCall = true; 16730 self.$evalAsync(function() { 16731 if (shouldCall) listener(newValues, newValues, self); 16732 }); 16733 return function deregisterWatchGroup() { 16734 shouldCall = false; 16735 }; 16736 } 16737 16738 if (watchExpressions.length === 1) { 16739 // Special case size of one 16740 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 16741 newValues[0] = value; 16742 oldValues[0] = oldValue; 16743 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 16744 }); 16745 } 16746 16747 forEach(watchExpressions, function(expr, i) { 16748 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 16749 newValues[i] = value; 16750 oldValues[i] = oldValue; 16751 if (!changeReactionScheduled) { 16752 changeReactionScheduled = true; 16753 self.$evalAsync(watchGroupAction); 16754 } 16755 }); 16756 deregisterFns.push(unwatchFn); 16757 }); 16758 16759 function watchGroupAction() { 16760 changeReactionScheduled = false; 16761 16762 if (firstRun) { 16763 firstRun = false; 16764 listener(newValues, newValues, self); 16765 } else { 16766 listener(newValues, oldValues, self); 16767 } 16768 } 16769 16770 return function deregisterWatchGroup() { 16771 while (deregisterFns.length) { 16772 deregisterFns.shift()(); 16773 } 16774 }; 16775 }, 16776 16777 16778 /** 16779 * @ngdoc method 16780 * @name $rootScope.Scope#$watchCollection 16781 * @kind function 16782 * 16783 * @description 16784 * Shallow watches the properties of an object and fires whenever any of the properties change 16785 * (for arrays, this implies watching the array items; for object maps, this implies watching 16786 * the properties). If a change is detected, the `listener` callback is fired. 16787 * 16788 * - The `obj` collection is observed via standard $watch operation and is examined on every 16789 * call to $digest() to see if any items have been added, removed, or moved. 16790 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 16791 * adding, removing, and moving items belonging to an object or array. 16792 * 16793 * 16794 * # Example 16795 * ```js 16796 $scope.names = ['igor', 'matias', 'misko', 'james']; 16797 $scope.dataCount = 4; 16798 16799 $scope.$watchCollection('names', function(newNames, oldNames) { 16800 $scope.dataCount = newNames.length; 16801 }); 16802 16803 expect($scope.dataCount).toEqual(4); 16804 $scope.$digest(); 16805 16806 //still at 4 ... no changes 16807 expect($scope.dataCount).toEqual(4); 16808 16809 $scope.names.pop(); 16810 $scope.$digest(); 16811 16812 //now there's been a change 16813 expect($scope.dataCount).toEqual(3); 16814 * ``` 16815 * 16816 * 16817 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 16818 * expression value should evaluate to an object or an array which is observed on each 16819 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 16820 * collection will trigger a call to the `listener`. 16821 * 16822 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 16823 * when a change is detected. 16824 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 16825 * - The `oldCollection` object is a copy of the former collection data. 16826 * Due to performance considerations, the`oldCollection` value is computed only if the 16827 * `listener` function declares two or more arguments. 16828 * - The `scope` argument refers to the current scope. 16829 * 16830 * @returns {function()} Returns a de-registration function for this listener. When the 16831 * de-registration function is executed, the internal watch operation is terminated. 16832 */ 16833 $watchCollection: function(obj, listener) { 16834 $watchCollectionInterceptor.$stateful = true; 16835 16836 var self = this; 16837 // the current value, updated on each dirty-check run 16838 var newValue; 16839 // a shallow copy of the newValue from the last dirty-check run, 16840 // updated to match newValue during dirty-check run 16841 var oldValue; 16842 // a shallow copy of the newValue from when the last change happened 16843 var veryOldValue; 16844 // only track veryOldValue if the listener is asking for it 16845 var trackVeryOldValue = (listener.length > 1); 16846 var changeDetected = 0; 16847 var changeDetector = $parse(obj, $watchCollectionInterceptor); 16848 var internalArray = []; 16849 var internalObject = {}; 16850 var initRun = true; 16851 var oldLength = 0; 16852 16853 function $watchCollectionInterceptor(_value) { 16854 newValue = _value; 16855 var newLength, key, bothNaN, newItem, oldItem; 16856 16857 // If the new value is undefined, then return undefined as the watch may be a one-time watch 16858 if (isUndefined(newValue)) return; 16859 16860 if (!isObject(newValue)) { // if primitive 16861 if (oldValue !== newValue) { 16862 oldValue = newValue; 16863 changeDetected++; 16864 } 16865 } else if (isArrayLike(newValue)) { 16866 if (oldValue !== internalArray) { 16867 // we are transitioning from something which was not an array into array. 16868 oldValue = internalArray; 16869 oldLength = oldValue.length = 0; 16870 changeDetected++; 16871 } 16872 16873 newLength = newValue.length; 16874 16875 if (oldLength !== newLength) { 16876 // if lengths do not match we need to trigger change notification 16877 changeDetected++; 16878 oldValue.length = oldLength = newLength; 16879 } 16880 // copy the items to oldValue and look for changes. 16881 for (var i = 0; i < newLength; i++) { 16882 oldItem = oldValue[i]; 16883 newItem = newValue[i]; 16884 16885 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 16886 if (!bothNaN && (oldItem !== newItem)) { 16887 changeDetected++; 16888 oldValue[i] = newItem; 16889 } 16890 } 16891 } else { 16892 if (oldValue !== internalObject) { 16893 // we are transitioning from something which was not an object into object. 16894 oldValue = internalObject = {}; 16895 oldLength = 0; 16896 changeDetected++; 16897 } 16898 // copy the items to oldValue and look for changes. 16899 newLength = 0; 16900 for (key in newValue) { 16901 if (hasOwnProperty.call(newValue, key)) { 16902 newLength++; 16903 newItem = newValue[key]; 16904 oldItem = oldValue[key]; 16905 16906 if (key in oldValue) { 16907 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 16908 if (!bothNaN && (oldItem !== newItem)) { 16909 changeDetected++; 16910 oldValue[key] = newItem; 16911 } 16912 } else { 16913 oldLength++; 16914 oldValue[key] = newItem; 16915 changeDetected++; 16916 } 16917 } 16918 } 16919 if (oldLength > newLength) { 16920 // we used to have more keys, need to find them and destroy them. 16921 changeDetected++; 16922 for (key in oldValue) { 16923 if (!hasOwnProperty.call(newValue, key)) { 16924 oldLength--; 16925 delete oldValue[key]; 16926 } 16927 } 16928 } 16929 } 16930 return changeDetected; 16931 } 16932 16933 function $watchCollectionAction() { 16934 if (initRun) { 16935 initRun = false; 16936 listener(newValue, newValue, self); 16937 } else { 16938 listener(newValue, veryOldValue, self); 16939 } 16940 16941 // make a copy for the next time a collection is changed 16942 if (trackVeryOldValue) { 16943 if (!isObject(newValue)) { 16944 //primitive 16945 veryOldValue = newValue; 16946 } else if (isArrayLike(newValue)) { 16947 veryOldValue = new Array(newValue.length); 16948 for (var i = 0; i < newValue.length; i++) { 16949 veryOldValue[i] = newValue[i]; 16950 } 16951 } else { // if object 16952 veryOldValue = {}; 16953 for (var key in newValue) { 16954 if (hasOwnProperty.call(newValue, key)) { 16955 veryOldValue[key] = newValue[key]; 16956 } 16957 } 16958 } 16959 } 16960 } 16961 16962 return this.$watch(changeDetector, $watchCollectionAction); 16963 }, 16964 16965 /** 16966 * @ngdoc method 16967 * @name $rootScope.Scope#$digest 16968 * @kind function 16969 * 16970 * @description 16971 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 16972 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 16973 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 16974 * until no more listeners are firing. This means that it is possible to get into an infinite 16975 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 16976 * iterations exceeds 10. 16977 * 16978 * Usually, you don't call `$digest()` directly in 16979 * {@link ng.directive:ngController controllers} or in 16980 * {@link ng.$compileProvider#directive directives}. 16981 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 16982 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 16983 * 16984 * If you want to be notified whenever `$digest()` is called, 16985 * you can register a `watchExpression` function with 16986 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 16987 *
16988 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 16989 * 16990 * # Example 16991 * ```js 16992 var scope = ...; 16993 scope.name = 'misko'; 16994 scope.counter = 0; 16995 16996 expect(scope.counter).toEqual(0); 16997 scope.$watch('name', function(newValue, oldValue) { 16998 scope.counter = scope.counter + 1; 16999 }); 17000 expect(scope.counter).toEqual(0); 17001 17002 scope.$digest(); 17003 // the listener is always called during the first $digest loop after it was registered 17004 expect(scope.counter).toEqual(1); 17005 17006 scope.$digest(); 17007 // but now it will not be called unless the value changes 17008 expect(scope.counter).toEqual(1); 17009 17010 scope.name = 'adam'; 17011 scope.$digest(); 17012 expect(scope.counter).toEqual(2); 17013 * ``` 17014 * 17015 */ 17016 $digest: function() { 17017 var watch, value, last, fn, get, 17018 watchers, 17019 length, 17020 dirty, ttl = TTL, 17021 next, current, target = this, 17022 watchLog = [], 17023 logIdx, asyncTask; 17024 17025 beginPhase('$digest'); 17026 // Check for changes to browser url that happened in sync before the call to $digest 17027 $browser.$$checkUrlChange(); 17028 17029 if (this === $rootScope && applyAsyncId !== null) { 17030 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 17031 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 17032 $browser.defer.cancel(applyAsyncId); 17033 flushApplyAsync(); 17034 } 17035 17036 lastDirtyWatch = null; 17037 17038 do { // "while dirty" loop 17039 dirty = false; 17040 current = target; 17041 17042 while (asyncQueue.length) { 17043 try { 17044 asyncTask = asyncQueue.shift(); 17045 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 17046 } catch (e) { 17047 $exceptionHandler(e); 17048 } 17049 lastDirtyWatch = null; 17050 } 17051 17052 traverseScopesLoop: 17053 do { // "traverse the scopes" loop 17054 if ((watchers = current.$$watchers)) { 17055 // process our watches 17056 length = watchers.length; 17057 while (length--) { 17058 try { 17059 watch = watchers[length]; 17060 // Most common watches are on primitives, in which case we can short 17061 // circuit it with === operator, only when === fails do we use .equals 17062 if (watch) { 17063 get = watch.get; 17064 if ((value = get(current)) !== (last = watch.last) && 17065 !(watch.eq 17066 ? equals(value, last) 17067 : (typeof value === 'number' && typeof last === 'number' 17068 && isNaN(value) && isNaN(last)))) { 17069 dirty = true; 17070 lastDirtyWatch = watch; 17071 watch.last = watch.eq ? copy(value, null) : value; 17072 fn = watch.fn; 17073 fn(value, ((last === initWatchVal) ? value : last), current); 17074 if (ttl < 5) { 17075 logIdx = 4 - ttl; 17076 if (!watchLog[logIdx]) watchLog[logIdx] = []; 17077 watchLog[logIdx].push({ 17078 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 17079 newVal: value, 17080 oldVal: last 17081 }); 17082 } 17083 } else if (watch === lastDirtyWatch) { 17084 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 17085 // have already been tested. 17086 dirty = false; 17087 break traverseScopesLoop; 17088 } 17089 } 17090 } catch (e) { 17091 $exceptionHandler(e); 17092 } 17093 } 17094 } 17095 17096 // Insanity Warning: scope depth-first traversal 17097 // yes, this code is a bit crazy, but it works and we have tests to prove it! 17098 // this piece should be kept in sync with the traversal in $broadcast 17099 if (!(next = ((current.$$watchersCount && current.$$childHead) || 17100 (current !== target && current.$$nextSibling)))) { 17101 while (current !== target && !(next = current.$$nextSibling)) { 17102 current = current.$parent; 17103 } 17104 } 17105 } while ((current = next)); 17106 17107 // `break traverseScopesLoop;` takes us to here 17108 17109 if ((dirty || asyncQueue.length) && !(ttl--)) { 17110 clearPhase(); 17111 throw $rootScopeMinErr('infdig', 17112 '{0} $digest() iterations reached. Aborting!\n' + 17113 'Watchers fired in the last 5 iterations: {1}', 17114 TTL, watchLog); 17115 } 17116 17117 }
vendor: 4,632 bytes, lines 17117-17236
17117 while (dirty || asyncQueue.length); 17118 17119 clearPhase(); 17120 17121 while (postDigestQueue.length) { 17122 try { 17123 postDigestQueue.shift()(); 17124 } catch (e) { 17125 $exceptionHandler(e); 17126 } 17127 } 17128 }, 17129 17130 17131 /** 17132 * @ngdoc event 17133 * @name $rootScope.Scope#$destroy 17134 * @eventType broadcast on scope being destroyed 17135 * 17136 * @description 17137 * Broadcasted when a scope and its children are being destroyed. 17138 * 17139 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 17140 * clean up DOM bindings before an element is removed from the DOM. 17141 */ 17142 17143 /** 17144 * @ngdoc method 17145 * @name $rootScope.Scope#$destroy 17146 * @kind function 17147 * 17148 * @description 17149 * Removes the current scope (and all of its children) from the parent scope. Removal implies 17150 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 17151 * propagate to the current scope and its children. Removal also implies that the current 17152 * scope is eligible for garbage collection. 17153 * 17154 * The `$destroy()` is usually used by directives such as 17155 * {@link ng.directive:ngRepeat ngRepeat} for managing the 17156 * unrolling of the loop. 17157 * 17158 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 17159 * Application code can register a `$destroy` event handler that will give it a chance to 17160 * perform any necessary cleanup. 17161 * 17162 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 17163 * clean up DOM bindings before an element is removed from the DOM. 17164 */ 17165 $destroy: function() { 17166 // We can't destroy a scope that has been already destroyed. 17167 if (this.$$destroyed) return; 17168 var parent = this.$parent; 17169 17170 this.$broadcast('$destroy'); 17171 this.$$destroyed = true; 17172 17173 if (this === $rootScope) { 17174 //Remove handlers attached to window when $rootScope is removed 17175 $browser.$$applicationDestroyed(); 17176 } 17177 17178 incrementWatchersCount(this, -this.$$watchersCount); 17179 for (var eventName in this.$$listenerCount) { 17180 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 17181 } 17182 17183 // sever all the references to parent scopes (after this cleanup, the current scope should 17184 // not be retained by any of our references and should be eligible for garbage collection) 17185 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 17186 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 17187 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 17188 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 17189 17190 // Disable listeners, watchers and apply/digest methods 17191 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 17192 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 17193 this.$$listeners = {}; 17194 17195 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 17196 this.$$nextSibling = null; 17197 cleanUpScope(this); 17198 }, 17199 17200 /** 17201 * @ngdoc method 17202 * @name $rootScope.Scope#$eval 17203 * @kind function 17204 * 17205 * @description 17206 * Executes the `expression` on the current scope and returns the result. Any exceptions in 17207 * the expression are propagated (uncaught). This is useful when evaluating Angular 17208 * expressions. 17209 * 17210 * # Example 17211 * ```js 17212 var scope = ng.$rootScope.Scope(); 17213 scope.a = 1; 17214 scope.b = 2; 17215 17216 expect(scope.$eval('a+b')).toEqual(3); 17217 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 17218 * ``` 17219 * 17220 * @param {(string|function())=} expression An angular expression to be executed. 17221 * 17222 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 17223 * - `function(scope)`: execute the function with the current `scope` parameter. 17224 * 17225 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 17226 * @returns {*} The result of evaluating the expression. 17227 */ 17228 $eval: function(expr, locals) { 17229 return $parse(expr)(this, locals); 17230 }, 17231 17232 /** 17233 * @ngdoc method 17234 * @name $rootScope.Scope#$evalAsync 17235 * @kind function 17236 *
17237 * @description 17238 * Executes the expression on the current scope at a later point in time. 17239 * 17240 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 17241 * that: 17242 * 17243 * - it will execute after the function that scheduled the evaluation (preferably before DOM 17244 * rendering). 17245 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 17246 * `expression` execution. 17247 * 17248 * Any exceptions from the execution of the expression are forwarded to the 17249 * {@link ng.$exceptionHandler $exceptionHandler} service. 17250 * 17251 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 17252 * will be scheduled. However, it is encouraged to always call code that changes the model 17253 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 17254 * 17255 * @param {(string|function())=} expression An angular expression to be executed. 17256 * 17257 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 17258 * - `function(scope)`: execute the function with the current `scope` parameter. 17259 * 17260 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 17261 */ 17262 $evalAsync: function(expr, locals) { 17263 // if we are outside of an $digest loop and this is the first time we are scheduling async 17264 // task also schedule async auto-flush 17265 if (!$rootScope.$$phase && !asyncQueue.length) { 17266 $browser.defer(function() { 17267 if (asyncQueue.length) { 17268 $rootScope.$digest(); 17269 } 17270 }); 17271 } 17272 17273 asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); 17274 }, 17275 17276 $$postDigest: function(fn) { 17277 postDigestQueue.push(fn); 17278 }, 17279 17280 /** 17281 * @ngdoc method 17282 * @name $rootScope.Scope#$apply 17283 * @kind function 17284 * 17285 * @description 17286 * `$apply()` is used to execute an expression in angular from outside of the angular 17287 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 17288 * Because we are calling into the angular framework we need to perform proper scope life 17289 * cycle of {@link ng.$exceptionHandler exception handling}, 17290 * {@link ng.$rootScope.Scope#$digest executing watches}. 17291 * 17292 * ## Life cycle 17293 * 17294 * # Pseudo-Code of `$apply()` 17295 * ```js 17296 function $apply(expr) { 17297 try { 17298 return $eval(expr); 17299 } catch (e) { 17300 $exceptionHandler(e); 17301 } finally { 17302 $root.$digest(); 17303 } 17304 } 17305 * ``` 17306 * 17307 * 17308 * Scope's `$apply()` method transitions through the following stages: 17309 * 17310 * 1. The {@link guide/expression expression} is executed using the 17311 * {@link ng.$rootScope.Scope#$eval $eval()} method. 17312 * 2. Any exceptions from the execution of the expression are forwarded to the 17313 * {@link ng.$exceptionHandler $exceptionHandler} service. 17314 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 17315 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 17316 * 17317 * 17318 * @param {(string|function())=} exp An angular expression to be executed. 17319 * 17320 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 17321 * - `function(scope)`: execute the function with current `scope` parameter. 17322 * 17323 * @returns {*} The result of evaluating the expression. 17324 */ 17325 $apply: function(expr) { 17326 try { 17327 beginPhase('$apply'); 17328 try { 17329 return this.$eval(expr); 17330 } finally { 17331 clearPhase(); 17332 } 17333 } catch (e) { 17334 $exceptionHandler(e); 17335 } finally { 17336 try { 17337 $rootScope.$digest(); 17338 } catch (e) { 17339 $exceptionHandler(e); 17340 throw e; 17341 } 17342 } 17343 }, 17344 17345 /** 17346 * @ngdoc method 17347 * @name $rootScope.Scope#$applyAsync 17348 * @kind function 17349 *
17350 * @description 17351 * Schedule the invocation of $apply to occur at a later time. The actual time difference 17352 * varies across browsers, but is typically around ~10 milliseconds. 17353 * 17354 * This can be used to queue up multiple expressions which need to be evaluated in the same 17355 * digest. 17356 * 17357 * @param {(string|function())=} exp An angular expression to be executed. 17358 * 17359 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 17360 * - `function(scope)`: execute the function with current `scope` parameter. 17361 */ 17362 $applyAsync: function(expr) { 17363 var scope = this; 17364 expr && applyAsyncQueue.push($applyAsyncExpression); 17365 expr = $parse(expr); 17366 scheduleApplyAsync(); 17367 17368 function $applyAsyncExpression() { 17369 scope.$eval(expr); 17370 } 17371 }, 17372 17373 /** 17374 * @ngdoc method 17375 * @name $rootScope.Scope#$on 17376 * @kind function 17377 * 17378 * @description 17379 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 17380 * discussion of event life cycle. 17381 * 17382 * The event listener function format is: `function(event, args...)`. The `event` object 17383 * passed into the listener has the following attributes: 17384 * 17385 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 17386 * `$broadcast`-ed. 17387 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 17388 * event propagates through the scope hierarchy, this property is set to null. 17389 * - `name` - `{string}`: name of the event. 17390 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 17391 * further event propagation (available only for events that were `$emit`-ed). 17392 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 17393 * to true. 17394 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 17395 * 17396 * @param {string} name Event name to listen on. 17397 * @param {function(event, ...args)} listener Function to call when the event is emitted. 17398 * @returns {function()} Returns a deregistration function for this listener. 17399 */ 17400 $on: function(name, listener) { 17401 var namedListeners = this.$$listeners[name]; 17402 if (!namedListeners) { 17403 this.$$listeners[name] = namedListeners = []; 17404 } 17405 namedListeners.push(listener); 17406 17407 var current = this; 17408 do { 17409 if (!current.$$listenerCount[name]) { 17410 current.$$listenerCount[name] = 0; 17411 } 17412 current.$$listenerCount[name]++; 17413 } while ((current = current.$parent)); 17414 17415 var self = this; 17416 return function() { 17417 var indexOfListener = namedListeners.indexOf(listener); 17418 if (indexOfListener !== -1) { 17419 namedListeners[indexOfListener] = null; 17420 decrementListenerCount(self, 1, name); 17421 } 17422 }; 17423 }, 17424 17425 17426 /** 17427 * @ngdoc method 17428 * @name $rootScope.Scope#$emit 17429 * @kind function 17430 * 17431 * @description 17432 * Dispatches an event `name` upwards through the scope hierarchy notifying the 17433 * registered {@link ng.$rootScope.Scope#$on} listeners. 17434 * 17435 * The event life cycle starts at the scope on which `$emit` was called. All 17436 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 17437 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 17438 * registered listeners along the way. The event will stop propagating if one of the listeners 17439 * cancels it. 17440 * 17441 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 17442 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 17443 * 17444 * @param {string} name Event name to emit. 17445 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 17446 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 17447 */ 17448 $emit: function(name, args) { 17449 var empty = [], 17450 namedListeners, 17451 scope = this, 17452 stopPropagation = false, 17453 event = { 17454 name: name, 17455 targetScope: scope, 17456 stopPropagation: function() {stopPropagation = true;}, 17457 preventDefault: function() { 17458 event.defaultPrevented = true; 17459 }, 17460 defaultPrevented: false 17461 }, 17462 listenerArgs = concat([event], arguments, 1), 17463 i, length; 17464 17465 do { 17466 namedListeners = scope.$$listeners[name] || empty; 17467 event.currentScope = scope; 17468 for (i = 0, length = namedListeners.length; i < length; i++) { 17469 17470 // if listeners were deregistered, defragment the array 17471 if (!namedListeners[i]) { 17472 namedListeners.splice(i, 1); 17473 i--; 17474 length--; 17475 continue; 17476 } 17477 try { 17478 //allow all listeners attached to the current scope to run 17479 namedListeners[i].apply(null, listenerArgs); 17480 } catch (e) { 17481 $exceptionHandler(e); 17482 } 17483 } 17484 //if any listener on the current scope stops propagation, prevent bubbling 17485 if (stopPropagation) { 17486 event.currentScope = null; 17487 return event; 17488 } 17489 //traverse upwards 17490 scope = scope.$parent; 17491 } while (scope); 17492 17493 event.currentScope = null; 17494 17495 return event; 17496 }, 17497 17498 17499 /** 17500 * @ngdoc method 17501 * @name $rootScope.Scope#$broadcast 17502 * @kind function 17503 *
17504 * @description 17505 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 17506 * registered {@link ng.$rootScope.Scope#$on} listeners. 17507 * 17508 * The event life cycle starts at the scope on which `$broadcast` was called. All 17509 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 17510 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 17511 * scope and calls all registered listeners along the way. The event cannot be canceled. 17512 * 17513 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 17514 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 17515 * 17516 * @param {string} name Event name to broadcast. 17517 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 17518 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 17519 */ 17520 $broadcast: function(name, args) { 17521 var target = this, 17522 current = target, 17523 next = target, 17524 event = { 17525 name: name, 17526 targetScope: target, 17527 preventDefault: function() { 17528 event.defaultPrevented = true; 17529 }, 17530 defaultPrevented: false 17531 }; 17532 17533 if (!target.$$listenerCount[name]) return event; 17534 17535 var listenerArgs = concat([event], arguments, 1), 17536 listeners, i, length; 17537 17538 //down while you can, then up and next sibling or up and next sibling until back at root 17539 while ((current = next)) { 17540 event.currentScope = current; 17541 listeners = current.$$listeners[name] || []; 17542 for (i = 0, length = listeners.length; i < length; i++) { 17543 // if listeners were deregistered, defragment the array 17544 if (!listeners[i]) { 17545 listeners.splice(i, 1); 17546 i--; 17547 length--; 17548 continue; 17549 } 17550 17551 try { 17552 listeners[i].apply(null, listenerArgs); 17553 } catch (e) { 17554 $exceptionHandler(e); 17555 } 17556 } 17557 17558 // Insanity Warning: scope depth-first traversal 17559 // yes, this code is a bit crazy, but it works and we have tests to prove it! 17560 // this piece should be kept in sync with the traversal in $digest 17561 // (though it differs due to having the extra check for $$listenerCount) 17562 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 17563 (current !== target && current.$$nextSibling)))) { 17564 while (current !== target && !(next = current.$$nextSibling)) { 17565 current = current.$parent; 17566 } 17567 } 17568 } 17569 17570 event.currentScope = null; 17571 return event; 17572 } 17573 }; 17574 17575 var $rootScope = new Scope(); 17576 17577 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 17578 var asyncQueue = $rootScope.$$asyncQueue = []; 17579 var postDigestQueue = $rootScope.$$postDigestQueue = []; 17580 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 17581 17582 return $rootScope; 17583 17584 17585 function beginPhase(phase) { 17586 if ($rootScope.$$phase) { 17587 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 17588 } 17589 17590 $rootScope.$$phase = phase; 17591 } 17592 17593 function clearPhase() { 17594 $rootScope.$$phase = null; 17595 } 17596 17597 function incrementWatchersCount(current, count) { 17598 do { 17599 current.$$watchersCount += count; 17600 } while ((current = current.$parent)); 17601 } 17602 17603 function decrementListenerCount(current, count, name) { 17604 do { 17605 current.$$listenerCount[name] -= count; 17606 17607 if (current.$$listenerCount[name] === 0) { 17608 delete current.$$listenerCount[name]; 17609 } 17610 } while ((current = current.$parent)); 17611 } 17612 17613 /** 17614 * function used as an initial value for watchers. 17615 * because it's unique we can easily tell it apart from other values 17616 */ 17617 function initWatchVal() {} 17618 17619 function flushApplyAsync() {
vendor: 5,358 bytes, lines 17620-17762
17620 while (applyAsyncQueue.length) { 17621 try { 17622 applyAsyncQueue.shift()(); 17623 } catch (e) { 17624 $exceptionHandler(e); 17625 } 17626 } 17627 applyAsyncId = null; 17628 } 17629 17630 function scheduleApplyAsync() { 17631 if (applyAsyncId === null) { 17632 applyAsyncId = $browser.defer(function() { 17633 $rootScope.$apply(flushApplyAsync); 17634 }); 17635 } 17636 } 17637 }]; 17638} 17639 17640/** 17641 * @ngdoc service 17642 * @name $rootElement 17643 * 17644 * @description 17645 * The root element of Angular application. This is either the element where {@link 17646 * ng.directive:ngApp ngApp} was declared or the element passed into 17647 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 17648 * location where the application's {@link auto.$injector $injector} service gets 17649 * published, and can be retrieved using `$rootElement.injector()`. 17650 */ 17651 17652 17653// the implementation is in angular.bootstrap 17654 17655/** 17656 * @description 17657 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 17658 */ 17659function $$SanitizeUriProvider() { 17660 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 17661 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 17662 17663 /** 17664 * @description 17665 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 17666 * urls during a[href] sanitization. 17667 * 17668 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 17669 * 17670 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 17671 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 17672 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 17673 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 17674 * 17675 * @param {RegExp=} regexp New regexp to whitelist urls with. 17676 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 17677 * chaining otherwise. 17678 */ 17679 this.aHrefSanitizationWhitelist = function(regexp) { 17680 if (isDefined(regexp)) { 17681 aHrefSanitizationWhitelist = regexp; 17682 return this; 17683 } 17684 return aHrefSanitizationWhitelist; 17685 }; 17686 17687 17688 /** 17689 * @description 17690 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 17691 * urls during img[src] sanitization. 17692 * 17693 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 17694 * 17695 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 17696 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 17697 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 17698 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 17699 * 17700 * @param {RegExp=} regexp New regexp to whitelist urls with. 17701 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 17702 * chaining otherwise. 17703 */ 17704 this.imgSrcSanitizationWhitelist = function(regexp) { 17705 if (isDefined(regexp)) { 17706 imgSrcSanitizationWhitelist = regexp; 17707 return this; 17708 } 17709 return imgSrcSanitizationWhitelist; 17710 }; 17711 17712 this.$get = function() { 17713 return function sanitizeUri(uri, isImage) { 17714 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 17715 var normalizedVal; 17716 normalizedVal = urlResolve(uri).href; 17717 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 17718 return 'unsafe:' + normalizedVal; 17719 } 17720 return uri; 17721 }; 17722 }; 17723} 17724 17725/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 17726 * Any commits to this file should be reviewed with security in mind. * 17727 * Changes to this file can potentially create security vulnerabilities. * 17728 * An approval from 2 Core members with history of modifying * 17729 * this file is required. * 17730 * * 17731 * Does the change somehow allow for arbitrary javascript to be executed? * 17732 * Or allows for someone to change the prototype of built-in objects? * 17733 * Or gives undesired access to variables likes document or window? * 17734 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 17735 17736var $sceMinErr = minErr('$sce'); 17737 17738var SCE_CONTEXTS = { 17739 HTML: 'html', 17740 CSS: 'css', 17741 URL: 'url', 17742 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 17743 // url. (e.g. ng-include, script src, templateUrl) 17744 RESOURCE_URL: 'resourceUrl', 17745 JS: 'js' 17746}; 17747 17748// Helper functions follow. 17749 17750function adjustMatcher(matcher) { 17751 if (matcher === 'self') { 17752 return matcher; 17753 } else if (isString(matcher)) { 17754 // Strings match exactly except for 2 wildcards - '*' and '**'. 17755 // '*' matches any character except those from the set ':/.?&'. 17756 // '**' matches any character (like .* in a RegExp). 17757 // More than 2 *'s raises an error as it's ill defined. 17758 if (matcher.indexOf('***') > -1) { 17759 throw $sceMinErr('iwcard', 17760 'Illegal sequence *** in string matcher. String: {0}', matcher); 17761 } 17762 matcher = escapeForRegexp(matcher).
17763 replace('\\*\\*', '.*'). 17764 replace('\\*', '[^:/.?&;]*'); 17765 return new RegExp('^' + matcher + '$'); 17766 } else if (isRegExp(matcher)) { 17767 // The only other type of matcher allowed is a Regexp. 17768 // Match entire URL / disallow partial matches. 17769 // Flags are reset (i.e. no global, ignoreCase or multiline) 17770 return new RegExp('^' + matcher.source + '$'); 17771 } else { 17772 throw $sceMinErr('imatcher', 17773 'Matchers may only be "self", string patterns or RegExp objects'); 17774 } 17775} 17776 17777 17778function adjustMatchers(matchers) { 17779 var adjustedMatchers = []; 17780 if (isDefined(matchers)) { 17781 forEach(matchers, function(matcher) { 17782 adjustedMatchers.push(adjustMatcher(matcher)); 17783 }); 17784 } 17785 return adjustedMatchers; 17786} 17787 17788 17789/** 17790 * @ngdoc service 17791 * @name $sceDelegate 17792 * @kind function 17793 * 17794 * @description 17795 * 17796 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 17797 * Contextual Escaping (SCE)} services to AngularJS. 17798 * 17799 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 17800 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 17801 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 17802 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 17803 * work because `$sce` delegates to `$sceDelegate` for these operations. 17804 * 17805 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 17806 * 17807 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 17808 * can override it completely to change the behavior of `$sce`, the common case would 17809 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 17810 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 17811 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 17812 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 17813 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 17814 */ 17815 17816/** 17817 * @ngdoc provider 17818 * @name $sceDelegateProvider 17819 * @description 17820 * 17821 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 17822 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 17823 * that the URLs used for sourcing Angular templates are safe. Refer {@link 17824 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 17825 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 17826 * 17827 * For the general details about this service in Angular, read the main page for {@link ng.$sce 17828 * Strict Contextual Escaping (SCE)}. 17829 * 17830 * **Example**: Consider the following case. <a name="example"></a> 17831 * 17832 * - your app is hosted at url `http://myapp.example.com/` 17833 * - but some of your templates are hosted on other domains you control such as 17834 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 17835 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 17836 * 17837 * Here is what a secure configuration for this scenario might look like: 17838 * 17839 * ``` 17840 * angular.module('myApp', []).config(function($sceDelegateProvider) { 17841 * $sceDelegateProvider.resourceUrlWhitelist([ 17842 * // Allow same origin resource loads. 17843 * 'self', 17844 * // Allow loading from our assets domain. Notice the difference between * and **. 17845 * 'http://srv*.assets.example.com/**' 17846 * ]); 17847 * 17848 * // The blacklist overrides the whitelist so the open redirect here is blocked. 17849 * $sceDelegateProvider.resourceUrlBlacklist([ 17850 * 'http://myapp.example.com/clickThru**' 17851 * ]); 17852 * }); 17853 * ``` 17854 */ 17855 17856function $SceDelegateProvider() { 17857 this.SCE_CONTEXTS = SCE_CONTEXTS; 17858 17859 // Resource URLs can also be trusted by policy. 17860 var resourceUrlWhitelist = ['self'], 17861 resourceUrlBlacklist = []; 17862 17863 /** 17864 * @ngdoc method 17865 * @name $sceDelegateProvider#resourceUrlWhitelist 17866 * @kind function 17867 * 17868 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 17869 * provided. This must be an array or null. A snapshot of this array is used so further 17870 * changes to the array are ignored. 17871 * 17872 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 17873 * allowed in this array.
vendor: 4,196 bytes, lines 17874-18000
17874 * 17875 * <div class="alert alert-warning"> 17876 * **Note:** an empty whitelist array will block all URLs! 17877 * </div> 17878 * 17879 * @return {Array} the currently set whitelist array. 17880 * 17881 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 17882 * same origin resource requests. 17883 * 17884 * @description 17885 * Sets/Gets the whitelist of trusted resource URLs. 17886 */ 17887 this.resourceUrlWhitelist = function(value) { 17888 if (arguments.length) { 17889 resourceUrlWhitelist = adjustMatchers(value); 17890 } 17891 return resourceUrlWhitelist; 17892 }; 17893 17894 /** 17895 * @ngdoc method 17896 * @name $sceDelegateProvider#resourceUrlBlacklist 17897 * @kind function 17898 * 17899 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 17900 * provided. This must be an array or null. A snapshot of this array is used so further 17901 * changes to the array are ignored. 17902 * 17903 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 17904 * allowed in this array. 17905 * 17906 * The typical usage for the blacklist is to **block 17907 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 17908 * these would otherwise be trusted but actually return content from the redirected domain. 17909 * 17910 * Finally, **the blacklist overrides the whitelist** and has the final say. 17911 * 17912 * @return {Array} the currently set blacklist array. 17913 * 17914 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 17915 * is no blacklist.) 17916 * 17917 * @description 17918 * Sets/Gets the blacklist of trusted resource URLs. 17919 */ 17920 17921 this.resourceUrlBlacklist = function(value) { 17922 if (arguments.length) { 17923 resourceUrlBlacklist = adjustMatchers(value); 17924 } 17925 return resourceUrlBlacklist; 17926 }; 17927 17928 this.$get = ['$injector', function($injector) { 17929 17930 var htmlSanitizer = function htmlSanitizer(html) { 17931 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 17932 }; 17933 17934 if ($injector.has('$sanitize')) { 17935 htmlSanitizer = $injector.get('$sanitize'); 17936 } 17937 17938 17939 function matchUrl(matcher, parsedUrl) { 17940 if (matcher === 'self') { 17941 return urlIsSameOrigin(parsedUrl); 17942 } else { 17943 // definitely a regex. See adjustMatchers() 17944 return !!matcher.exec(parsedUrl.href); 17945 } 17946 } 17947 17948 function isResourceUrlAllowedByPolicy(url) { 17949 var parsedUrl = urlResolve(url.toString()); 17950 var i, n, allowed = false; 17951 // Ensure that at least one item from the whitelist allows this url. 17952 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 17953 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 17954 allowed = true; 17955 break; 17956 } 17957 } 17958 if (allowed) { 17959 // Ensure that no item from the blacklist blocked this url. 17960 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 17961 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 17962 allowed = false; 17963 break; 17964 } 17965 } 17966 } 17967 return allowed; 17968 } 17969 17970 function generateHolderType(Base) { 17971 var holderType = function TrustedValueHolderType(trustedValue) { 17972 this.$$unwrapTrustedValue = function() { 17973 return trustedValue; 17974 }; 17975 }; 17976 if (Base) { 17977 holderType.prototype = new Base(); 17978 } 17979 holderType.prototype.valueOf = function sceValueOf() { 17980 return this.$$unwrapTrustedValue(); 17981 }; 17982 holderType.prototype.toString = function sceToString() { 17983 return this.$$unwrapTrustedValue().toString(); 17984 }; 17985 return holderType; 17986 } 17987 17988 var trustedValueHolderBase = generateHolderType(), 17989 byType = {}; 17990 17991 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 17992 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 17993 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 17994 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 17995 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 17996 17997 /** 17998 * @ngdoc method 17999 * @name $sceDelegate#trustAs 18000 *
18001 * @description 18002 * Returns an object that is trusted by angular for use in specified strict 18003 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 18004 * attribute interpolation, any dom event binding attribute interpolation 18005 * such as for onclick, etc.) that uses the provided value. 18006 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 18007 * 18008 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 18009 * resourceUrl, html, js and css. 18010 * @param {*} value The value that that should be considered trusted/safe. 18011 * @returns {*} A value that can be used to stand in for the provided `value` in places 18012 * where Angular expects a $sce.trustAs() return value. 18013 */ 18014 function trustAs(type, trustedValue) { 18015 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 18016 if (!Constructor) { 18017 throw $sceMinErr('icontext', 18018 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 18019 type, trustedValue); 18020 } 18021 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 18022 return trustedValue; 18023 } 18024 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 18025 // mutable objects, we ensure here that the value passed in is actually a string. 18026 if (typeof trustedValue !== 'string') { 18027 throw $sceMinErr('itype', 18028 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 18029 type); 18030 } 18031 return new Constructor(trustedValue); 18032 } 18033 18034 /** 18035 * @ngdoc method 18036 * @name $sceDelegate#valueOf 18037 * 18038 * @description 18039 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 18040 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 18041 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 18042 * 18043 * If the passed parameter is not a value that had been returned by {@link 18044 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 18045 * 18046 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 18047 * call or anything else. 18048 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 18049 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 18050 * `value` unchanged. 18051 */ 18052 function valueOf(maybeTrusted) { 18053 if (maybeTrusted instanceof trustedValueHolderBase) { 18054 return maybeTrusted.$$unwrapTrustedValue(); 18055 } else { 18056 return maybeTrusted; 18057 } 18058 } 18059 18060 /** 18061 * @ngdoc method 18062 * @name $sceDelegate#getTrusted 18063 * 18064 * @description 18065 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 18066 * returns the originally supplied value if the queried context type is a supertype of the 18067 * created type. If this condition isn't satisfied, throws an exception. 18068 * 18069 * <div class="alert alert-danger"> 18070 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 18071 * (XSS) vulnerability in your application. 18072 * </div> 18073 * 18074 * @param {string} type The kind of context in which this value is to be used. 18075 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 18076 * `$sceDelegate.trustAs`} call. 18077 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 18078 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 18079 */ 18080 function getTrusted(type, maybeTrusted) { 18081 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 18082 return maybeTrusted; 18083 } 18084 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 18085 if (constructor && maybeTrusted instanceof constructor) { 18086 return maybeTrusted.$$unwrapTrustedValue(); 18087 } 18088 // If we get here, then we may only take one of two actions. 18089 // 1. sanitize the value for the requested type, or 18090 // 2. throw an exception. 18091 if (type === SCE_CONTEXTS.RESOURCE_URL) { 18092 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 18093 return maybeTrusted; 18094 } else { 18095 throw $sceMinErr('insecurl', 18096 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 18097 maybeTrusted.toString()); 18098 } 18099 } else if (type === SCE_CONTEXTS.HTML) { 18100 return htmlSanitizer(maybeTrusted); 18101 } 18102 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 18103 } 18104 18105 return { trustAs: trustAs, 18106 getTrusted: getTrusted,
18107 valueOf: valueOf }; 18108 }]; 18109} 18110 18111 18112/** 18113 * @ngdoc provider 18114 * @name $sceProvider 18115 * @description 18116 * 18117 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 18118 * - enable/disable Strict Contextual Escaping (SCE) in a module 18119 * - override the default implementation with a custom delegate 18120 * 18121 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 18122 */ 18123 18124/* jshint maxlen: false*/ 18125 18126/** 18127 * @ngdoc service 18128 * @name $sce 18129 * @kind function 18130 * 18131 * @description 18132 * 18133 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 18134 * 18135 * # Strict Contextual Escaping 18136 * 18137 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 18138 * contexts to result in a value that is marked as safe to use for that context. One example of 18139 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 18140 * to these contexts as privileged or SCE contexts. 18141 * 18142 * As of version 1.2, Angular ships with SCE enabled by default. 18143 * 18144 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 18145 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 18146 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
18147 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 18148 * to the top of your HTML document. 18149 * 18150 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 18151 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 18152 * 18153 * Here's an example of a binding in a privileged context: 18154 * 18155 * ``` 18156 * <input ng-model="userHtml" aria-label="User input"> 18157 * <div ng-bind-html="userHtml"></div> 18158 * ``` 18159 * 18160 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 18161 * disabled, this application allows the user to render arbitrary HTML into the DIV. 18162 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 18163 * bindings. (HTML is just one example of a context where rendering user controlled input creates 18164 * security vulnerabilities.) 18165 * 18166 * For the case of HTML, you might use a library, either on the client side, or on the server side, 18167 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 18168 * 18169 * How would you ensure that every place that used these types of bindings was bound to a value that 18170 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 18171 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 18172 * properties/fields and forgot to update the binding to the sanitized value? 18173 * 18174 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 18175 * determine that something explicitly says it's safe to use a value for binding in that
18176 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 18177 * for those values that you can easily tell are safe - because they were received from your server, 18178 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 18179 * allowing only the files in a specific directory to do this. Ensuring that the internal API 18180 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 18181 * 18182 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 18183 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 18184 * obtain values that will be accepted by SCE / privileged contexts. 18185 * 18186 * 18187 * ## How does it work? 18188 * 18189 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 18190 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 18191 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 18192 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 18193 * 18194 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 18195 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 18196 * simplified): 18197 * 18198 * ``` 18199 * var ngBindHtmlDirective = ['$sce', function($sce) { 18200 * return function(scope, element, attr) { 18201 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 18202 * element.html(value || ''); 18203 * }); 18204 * }; 18205 * }]; 18206 * ``` 18207 * 18208 * ## Impact on loading templates 18209 * 18210 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 18211 * `templateUrl`'s specified by {@link guide/directive directives}. 18212 * 18213 * By default, Angular only loads templates from the same domain and protocol as the application 18214 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 18215 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 18216 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 18217 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 18218 * 18219 * *Please note*: 18220 * The browser's 18221 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 18222 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 18223 * policy apply in addition to this and may further restrict whether the template is successfully 18224 * loaded. This means that without the right CORS policy, loading templates from a different domain 18225 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 18226 * browsers. 18227 * 18228 * ## This feels like too much overhead 18229 * 18230 * It's important to remember that SCE only applies to interpolation expressions. 18231 * 18232 * If your expressions are constant literals, they're automatically trusted and you don't need to 18233 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 18234 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 18235 * 18236 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 18237 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 18238 * 18239 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 18240 * templates in `ng-include` from your application's domain without having to even know about SCE. 18241 * It blocks loading templates from other domains or loading templates over http from an https 18242 * served document. You can change these by setting your own custom {@link 18243 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 18244 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 18245 * 18246 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 18247 * application that's secure and can be audited to verify that with much more ease than bolting 18248 * security onto an application later. 18249 * 18250 * <a name="contexts"></a> 18251 * ## What trusted context types are supported? 18252 * 18253 * | Context | Notes | 18254 * |---------------------|----------------| 18255 * | `$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. | 18256 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 18257 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | 18258 * | `$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` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | 18259 * | `$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. | 18260 * 18261 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 18262 *
18263 * Each element in these arrays must be one of the following: 18264 * 18265 * - **'self'** 18266 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 18267 * domain** as the application document using the **same protocol**. 18268 * - **String** (except the special value `'self'`) 18269 * - The string is matched against the full *normalized / absolute URL* of the resource 18270 * being tested (substring matches are not good enough.) 18271 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 18272 * match themselves. 18273 * - `*`: matches zero or more occurrences of any character other than one of the following 6 18274 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 18275 * in a whitelist. 18276 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 18277 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 18278 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 18279 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 18280 * http://foo.example.com/templates/**). 18281 * - **RegExp** (*see caveat below*) 18282 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 18283 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 18284 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 18285 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 18286 * small number of cases. A `.` character in the regex used when matching the scheme or a 18287 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 18288 * is highly recommended to use the string patterns and only fall back to regular expressions 18289 * as a last resort. 18290 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 18291 * matched against the **entire** *normalized / absolute URL* of the resource being tested 18292 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 18293 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 18294 * - If you are generating your JavaScript from some other templating engine (not 18295 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 18296 * remember to escape your regular expression (and be aware that you might need more than 18297 * one level of escaping depending on your templating engine and the way you interpolated 18298 * the value.) Do make use of your platform's escaping mechanism as it might be good 18299 * enough before coding your own. E.g. Ruby has 18300 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 18301 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 18302 * Javascript lacks a similar built in function for escaping. Take a look at Google 18303 * Closure library's [goog.string.regExpEscape(s)]( 18304 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 18305 * 18306 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 18307 * 18308 * ## Show me an example using SCE. 18309 * 18310 * <example module="mySceApp" deps="angular-sanitize.js"> 18311 * <file name="index.html"> 18312 * <div ng-controller="AppController as myCtrl"> 18313 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 18314 * <b>User comments</b><br> 18315 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 18316 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 18317 * exploit. 18318 * <div class="well"> 18319 * <div ng-repeat="userComment in myCtrl.userComments"> 18320 * <b>{{userComment.name}}</b>: 18321 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 18322 * <br> 18323 * </div>
18324 * </div> 18325 * </div> 18326 * </file> 18327 * 18328 * <file name="script.js"> 18329 * angular.module('mySceApp', ['ngSanitize']) 18330 * .controller('AppController', ['$http', '$templateCache', '$sce', 18331 * function($http, $templateCache, $sce) { 18332 * var self = this; 18333 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 18334 * self.userComments = userComments; 18335 * }); 18336 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 18337 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 18338 * 'sanitization."">Hover over this text.</span>'); 18339 * }]); 18340 * </file> 18341 * 18342 * <file name="test_data.json"> 18343 * [ 18344 * { "name": "Alice", 18345 * "htmlComment": 18346 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 18347 * }, 18348 * { "name": "Bob", 18349 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 18350 * } 18351 * ] 18352 * </file> 18353 * 18354 * <file name="protractor.js" type="protractor"> 18355 * describe('SCE doc demo', function() { 18356 * it('should sanitize untrusted values', function() { 18357 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 18358 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 18359 * }); 18360 * 18361 * it('should NOT sanitize explicitly trusted values', function() { 18362 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 18363 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 18364 * 'sanitization."">Hover over this text.</span>'); 18365 * }); 18366 * }); 18367 * </file> 18368 * </example> 18369 * 18370 * 18371 * 18372 * ## Can I disable SCE completely? 18373 * 18374 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 18375 * for little coding overhead. It will be much harder to take an SCE disabled application and 18376 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 18377 * for cases where you have a lot of existing code that was written before SCE was introduced and 18378 * you're migrating them a module at a time. 18379 * 18380 * That said, here's how you can completely disable SCE: 18381 * 18382 * ``` 18383 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 18384 * // Completely disable SCE. For demonstration purposes only! 18385 * // Do not use in new projects. 18386 * $sceProvider.enabled(false); 18387 * }); 18388 * ``` 18389 * 18390 */ 18391/* jshint maxlen: 100 */ 18392 18393function $SceProvider() { 18394 var enabled = true; 18395 18396 /** 18397 * @ngdoc method 18398 * @name $sceProvider#enabled 18399 * @kind function 18400 * 18401 * @param {boolean=} value If provided, then enables/disables SCE. 18402 * @return {boolean} true if SCE is enabled, false otherwise. 18403 * 18404 * @description 18405 * Enables/disables SCE and returns the current value. 18406 */ 18407 this.enabled = function(value) { 18408 if (arguments.length) { 18409 enabled = !!value; 18410 } 18411 return enabled; 18412 }; 18413 18414 18415 /* Design notes on the default implementation for SCE. 18416 * 18417 * The API contract for the SCE delegate 18418 * ------------------------------------- 18419 * The SCE delegate object must provide the following 3 methods: 18420 * 18421 * - trustAs(contextEnum, value) 18422 * This method is used to tell the SCE service that the provided value is OK to use in the 18423 * contexts specified by contextEnum. It must return an object that will be accepted by 18424 * getTrusted() for a compatible contextEnum and return this value. 18425 * 18426 * - valueOf(value) 18427 * For values that were not produced by trustAs(), return them as is. For values that were 18428 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 18429 * trustAs is wrapping the given values into some type, this operation unwraps it when given 18430 * such a value. 18431 * 18432 * - getTrusted(contextEnum, value) 18433 * This function should return the a value that is safe to use in the context specified by 18434 * contextEnum or throw and exception otherwise. 18435 * 18436 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 18437 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 18438 * instance, an implementation could maintain a registry of all trusted objects by context. In 18439 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 18440 * return the same object passed in if it was found in the registry under a compatible context or 18441 * throw an exception otherwise. An implementation might only wrap values some of the time based 18442 * on some criteria. getTrusted() might return a value and not throw an exception for special 18443 * constants or objects even if not wrapped. All such implementations fulfill this contract. 18444 * 18445 * 18446 * A note on the inheritance model for SCE contexts 18447 * ------------------------------------------------ 18448 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 18449 * is purely an implementation details. 18450 * 18451 * The contract is simply this: 18452 * 18453 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 18454 * will also succeed. 18455 * 18456 * Inheritance happens to capture this in a natural way. In some future, we
18457 * may not use inheritance anymore. That is OK because no code outside of 18458 * sce.js and sceSpecs.js would need to be aware of this detail. 18459 */ 18460 18461 this.$get = ['$parse', '$sceDelegate', function( 18462 $parse, $sceDelegate) { 18463 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 18464 // the "expression(javascript expression)" syntax which is insecure. 18465 if (enabled && msie < 8) { 18466 throw $sceMinErr('iequirks', 18467 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 18468 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 18469 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 18470 } 18471 18472 var sce = shallowCopy(SCE_CONTEXTS); 18473 18474 /** 18475 * @ngdoc method 18476 * @name $sce#isEnabled 18477 * @kind function 18478 * 18479 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 18480 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 18481 * 18482 * @description 18483 * Returns a boolean indicating if SCE is enabled. 18484 */ 18485 sce.isEnabled = function() { 18486 return enabled; 18487 }; 18488 sce.trustAs = $sceDelegate.trustAs; 18489 sce.getTrusted = $sceDelegate.getTrusted; 18490 sce.valueOf = $sceDelegate.valueOf; 18491 18492 if (!enabled) { 18493 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 18494 sce.valueOf = identity; 18495 } 18496 18497 /** 18498 * @ngdoc method 18499 * @name $sce#parseAs 18500 * 18501 * @description 18502 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 18503 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 18504 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 18505 * *result*)} 18506 * 18507 * @param {string} type The kind of SCE context in which this result will be used. 18508 * @param {string} expression String expression to compile. 18509 * @returns {function(context, locals)} a function which represents the compiled expression: 18510 * 18511 * * `context` – `{object}` – an object against which any expressions embedded in the strings 18512 * are evaluated against (typically a scope object). 18513 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 18514 * `context`. 18515 */ 18516 sce.parseAs = function sceParseAs(type, expr) { 18517 var parsed = $parse(expr); 18518 if (parsed.literal && parsed.constant) { 18519 return parsed; 18520 } else { 18521 return $parse(expr, function(value) { 18522 return sce.getTrusted(type, value); 18523 }); 18524 } 18525 }; 18526 18527 /** 18528 * @ngdoc method 18529 * @name $sce#trustAs 18530 * 18531 * @description 18532 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 18533 * returns an object that is trusted by angular for use in specified strict contextual 18534 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 18535 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 18536 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 18537 * escaping. 18538 * 18539 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 18540 * resourceUrl, html, js and css. 18541 * @param {*} value The value that that should be considered trusted/safe. 18542 * @returns {*} A value that can be used to stand in for the provided `value` in places 18543 * where Angular expects a $sce.trustAs() return value. 18544 */ 18545 18546 /** 18547 * @ngdoc method 18548 * @name $sce#trustAsHtml 18549 * 18550 * @description 18551 * Shorthand method. `$sce.trustAsHtml(value)` → 18552 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 18553 * 18554 * @param {*} value The value to trustAs. 18555 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 18556 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 18557 * only accept expressions that are either literal constants or are the 18558 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 18559 */ 18560 18561 /** 18562 * @ngdoc method 18563 * @name $sce#trustAsUrl 18564 *
18565 * @description 18566 * Shorthand method. `$sce.trustAsUrl(value)` → 18567 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 18568 * 18569 * @param {*} value The value to trustAs. 18570 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 18571 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 18572 * only accept expressions that are either literal constants or are the 18573 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 18574 */ 18575 18576 /** 18577 * @ngdoc method 18578 * @name $sce#trustAsResourceUrl 18579 * 18580 * @description 18581 * Shorthand method. `$sce.trustAsResourceUrl(value)` → 18582 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 18583 * 18584 * @param {*} value The value to trustAs. 18585 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 18586 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 18587 * only accept expressions that are either literal constants or are the return 18588 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 18589 */ 18590 18591 /** 18592 * @ngdoc method 18593 * @name $sce#trustAsJs 18594 * 18595 * @description 18596 * Shorthand method. `$sce.trustAsJs(value)` → 18597 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 18598 * 18599 * @param {*} value The value to trustAs. 18600 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 18601 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 18602 * only accept expressions that are either literal constants or are the 18603 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 18604 */ 18605 18606 /** 18607 * @ngdoc method 18608 * @name $sce#getTrusted 18609 * 18610 * @description 18611 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 18612 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 18613 * originally supplied value if the queried context type is a supertype of the created type. 18614 * If this condition isn't satisfied, throws an exception. 18615 * 18616 * @param {string} type The kind of context in which this value is to be used. 18617 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 18618 * call. 18619 * @returns {*} The value the was originally provided to 18620 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 18621 * Otherwise, throws an exception. 18622 */ 18623 18624 /** 18625 * @ngdoc method 18626 * @name $sce#getTrustedHtml 18627 * 18628 * @description 18629 * Shorthand method. `$sce.getTrustedHtml(value)` → 18630 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 18631 * 18632 * @param {*} value The value to pass to `$sce.getTrusted`. 18633 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 18634 */ 18635 18636 /** 18637 * @ngdoc method 18638 * @name $sce#getTrustedCss 18639 * 18640 * @description 18641 * Shorthand method. `$sce.getTrustedCss(value)` → 18642 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 18643 * 18644 * @param {*} value The value to pass to `$sce.getTrusted`. 18645 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 18646 */ 18647 18648 /** 18649 * @ngdoc method 18650 * @name $sce#getTrustedUrl 18651 * 18652 * @description 18653 * Shorthand method. `$sce.getTrustedUrl(value)` → 18654 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 18655 * 18656 * @param {*} value The value to pass to `$sce.getTrusted`. 18657 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 18658 */ 18659 18660 /** 18661 * @ngdoc method 18662 * @name $sce#getTrustedResourceUrl 18663 * 18664 * @description 18665 * Shorthand method. `$sce.getTrustedResourceUrl(value)` → 18666 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 18667 * 18668 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 18669 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 18670 */ 18671 18672 /** 18673 * @ngdoc method 18674 * @name $sce#getTrustedJs 18675 *
18676 * @description 18677 * Shorthand method. `$sce.getTrustedJs(value)` → 18678 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 18679 * 18680 * @param {*} value The value to pass to `$sce.getTrusted`. 18681 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 18682 */ 18683 18684 /** 18685 * @ngdoc method 18686 * @name $sce#parseAsHtml 18687 * 18688 * @description 18689 * Shorthand method. `$sce.parseAsHtml(expression string)` → 18690 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 18691 * 18692 * @param {string} expression String expression to compile. 18693 * @returns {function(context, locals)} a function which represents the compiled expression: 18694 * 18695 * * `context` – `{object}` – an object against which any expressions embedded in the strings 18696 * are evaluated against (typically a scope object). 18697 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 18698 * `context`. 18699 */ 18700 18701 /** 18702 * @ngdoc method 18703 * @name $sce#parseAsCss 18704 * 18705 * @description 18706 * Shorthand method. `$sce.parseAsCss(value)` → 18707 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 18708 * 18709 * @param {string} expression String expression to compile. 18710 * @returns {function(context, locals)} a function which represents the compiled expression: 18711 * 18712 * * `context` – `{object}` – an object against which any expressions embedded in the strings 18713 * are evaluated against (typically a scope object). 18714 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 18715 * `context`. 18716 */ 18717 18718 /** 18719 * @ngdoc method 18720 * @name $sce#parseAsUrl 18721 * 18722 * @description 18723 * Shorthand method. `$sce.parseAsUrl(value)` → 18724 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 18725 * 18726 * @param {string} expression String expression to compile. 18727 * @returns {function(context, locals)} a function which represents the compiled expression: 18728 * 18729 * * `context` – `{object}` – an object against which any expressions embedded in the strings 18730 * are evaluated against (typically a scope object). 18731 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 18732 * `context`. 18733 */ 18734 18735 /** 18736 * @ngdoc method 18737 * @name $sce#parseAsResourceUrl 18738 * 18739 * @description 18740 * Shorthand method. `$sce.parseAsResourceUrl(value)` → 18741 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 18742 * 18743 * @param {string} expression String expression to compile. 18744 * @returns {function(context, locals)} a function which represents the compiled expression: 18745 * 18746 * * `context` – `{object}` – an object against which any expressions embedded in the strings 18747 * are evaluated against (typically a scope object). 18748 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 18749 * `context`. 18750 */ 18751 18752 /** 18753 * @ngdoc method 18754 * @name $sce#parseAsJs 18755 * 18756 * @description 18757 * Shorthand method. `$sce.parseAsJs(value)` → 18758 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 18759 * 18760 * @param {string} expression String expression to compile. 18761 * @returns {function(context, locals)} a function which represents the compiled expression: 18762 * 18763 * * `context` – `{object}` – an object against which any expressions embedded in the strings 18764 * are evaluated against (typically a scope object). 18765 * * `locals` – `{object=}` – local variables context object, useful for overriding values in 18766 * `context`. 18767 */ 18768 18769 // Shorthand delegations. 18770 var parse = sce.parseAs, 18771 getTrusted = sce.getTrusted, 18772 trustAs = sce.trustAs; 18773 18774 forEach(SCE_CONTEXTS, function(enumValue, name) { 18775 var lName = lowercase(name); 18776 sce[camelCase("parse_as_" + lName)] = function(expr) { 18777 return parse(enumValue, expr); 18778 }; 18779 sce[camelCase("get_trusted_" + lName)] = function(value) { 18780 return getTrusted(enumValue, value); 18781 }; 18782 sce[camelCase("trust_as_" + lName)] = function(value) { 18783 return trustAs(enumValue, value); 18784 }; 18785 }); 18786 18787 return sce; 18788 }]; 18789} 18790 18791/** 18792 * !!! This is an undocumented "private" service !!! 18793 * 18794 * @name $sniffer 18795 * @requires $window 18796 * @requires $document 18797 * 18798 * @property {boolean} history Does the browser support html5 history api ? 18799 * @property {boolean} transitions Does the browser support CSS transition events ? 18800 * @property {boolean} animations Does the browser support CSS animation events ? 18801 * 18802 * @description 18803 * This is very simple implementation of testing browser's features. 18804 */ 18805function $SnifferProvider() { 18806 this.$get = ['$window', '$document', function($window, $document) { 18807 var eventSupport = {}, 18808 // Chrome Packaged Apps are not allowed to access `history.pushState`. They can be detected by 18809 // the presence of `chrome.app.runtime` (see https://developer.chrome.com/apps/api_index) 18810 isChromePackagedApp = $window.chrome && $window.chrome.app && $window.chrome.app.runtime, 18811 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, 18812 android = 18813 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 18814 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 18815 document = $document[0] || {}, 18816 vendorPrefix, 18817 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 18818 bodyStyle = document.body && document.body.style, 18819 transitions = false, 18820 animations = false, 18821 match; 18822 18823 if (bodyStyle) { 18824 for (var prop in bodyStyle) {
18825 if (match = vendorRegex.exec(prop)) { 18826 vendorPrefix = match[0]; 18827 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 18828 break; 18829 } 18830 } 18831 18832 if (!vendorPrefix) { 18833 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 18834 } 18835 18836 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 18837 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 18838 18839 if (android && (!transitions || !animations)) { 18840 transitions = isString(bodyStyle.webkitTransition); 18841 animations = isString(bodyStyle.webkitAnimation); 18842 } 18843 } 18844 18845 18846 return { 18847 // Android has history.pushState, but it does not update location correctly 18848 // so let's not use the history API at all. 18849 // http://code.google.com/p/android/issues/detail?id=17471 18850 // https://github.com/angular/angular.js/issues/904 18851 18852 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 18853 // so let's not use the history API also 18854 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 18855 // jshint -W018 18856 history: !!(hasHistoryPushState && !(android < 4) && !boxee), 18857 // jshint +W018 18858 hasEvent: function(event) { 18859 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 18860 // it. In particular the event is not fired when backspace or delete key are pressed or 18861 // when cut operation is performed. 18862 // IE10+ implements 'input' event but it erroneously fires under various situations, 18863 // e.g. when placeholder changes, or a form is focused. 18864 if (event === 'input' && msie <= 11) return false; 18865 18866 if (isUndefined(eventSupport[event])) { 18867 var divElm = document.createElement('div'); 18868 eventSupport[event] = 'on' + event in divElm; 18869 } 18870 18871 return eventSupport[event]; 18872 }, 18873 csp: csp(), 18874 vendorPrefix: vendorPrefix, 18875 transitions: transitions, 18876 animations: animations, 18877 android: android 18878 }; 18879 }]; 18880} 18881 18882var $templateRequestMinErr = minErr('$compile'); 18883 18884/** 18885 * @ngdoc provider 18886 * @name $templateRequestProvider 18887 * @description 18888 * Used to configure the options passed to the {@link $http} service when making a template request. 18889 * 18890 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 18891 * requesting a template. 18892 */ 18893function $TemplateRequestProvider() { 18894 18895 var httpOptions; 18896 18897 /** 18898 * @ngdoc method 18899 * @name $templateRequestProvider#httpOptions 18900 * @description 18901 * The options to be passed to the {@link $http} service when making the request. 18902 * You can use this to override options such as the "Accept" header for template requests. 18903 * 18904 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 18905 * options if not overridden here. 18906 * 18907 * @param {string=} value new value for the {@link $http} options. 18908 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 18909 */ 18910 this.httpOptions = function(val) { 18911 if (val) { 18912 httpOptions = val; 18913 return this; 18914 } 18915 return httpOptions; 18916 }; 18917 18918 /** 18919 * @ngdoc service 18920 * @name $templateRequest 18921 * 18922 * @description 18923 * The `$templateRequest` service runs security checks then downloads the provided template using 18924 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 18925 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 18926 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 18927 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 18928 * when `tpl` is of type string and `$templateCache` has the matching entry. 18929 * 18930 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 18931 * can configure this via {@link $templateRequestProvider#httpOptions}. 18932 * 18933 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 18934 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 18935 * 18936 * @return {Promise} a promise for the HTTP response data of the given URL. 18937 * 18938 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
vendor: 4,422 bytes, lines 18939-19065
18939 */ 18940 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 18941 18942 function handleRequestFn(tpl, ignoreRequestError) { 18943 handleRequestFn.totalPendingRequests++; 18944 18945 // We consider the template cache holds only trusted templates, so 18946 // there's no need to go through whitelisting again for keys that already 18947 // are included in there. This also makes Angular accept any script 18948 // directive, no matter its name. However, we still need to unwrap trusted 18949 // types. 18950 if (!isString(tpl) || !$templateCache.get(tpl)) { 18951 tpl = $sce.getTrustedResourceUrl(tpl); 18952 } 18953 18954 var transformResponse = $http.defaults && $http.defaults.transformResponse; 18955 18956 if (isArray(transformResponse)) { 18957 transformResponse = transformResponse.filter(function(transformer) { 18958 return transformer !== defaultHttpResponseTransform; 18959 }); 18960 } else if (transformResponse === defaultHttpResponseTransform) { 18961 transformResponse = null; 18962 } 18963 18964 return $http.get(tpl, extend({ 18965 cache: $templateCache, 18966 transformResponse: transformResponse 18967 }, httpOptions)) 18968 ['finally'](function() { 18969 handleRequestFn.totalPendingRequests--; 18970 }) 18971 .then(function(response) { 18972 $templateCache.put(tpl, response.data); 18973 return response.data; 18974 }, handleError); 18975 18976 function handleError(resp) { 18977 if (!ignoreRequestError) { 18978 throw $templateRequestMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 18979 tpl, resp.status, resp.statusText); 18980 } 18981 return $q.reject(resp); 18982 } 18983 } 18984 18985 handleRequestFn.totalPendingRequests = 0; 18986 18987 return handleRequestFn; 18988 }]; 18989} 18990 18991function $$TestabilityProvider() { 18992 this.$get = ['$rootScope', '$browser', '$location', 18993 function($rootScope, $browser, $location) { 18994 18995 /** 18996 * @name $testability 18997 * 18998 * @description 18999 * The private $$testability service provides a collection of methods for use when debugging 19000 * or by automated test and debugging tools. 19001 */ 19002 var testability = {}; 19003 19004 /** 19005 * @name $$testability#findBindings 19006 * 19007 * @description 19008 * Returns an array of elements that are bound (via ng-bind or {{}}) 19009 * to expressions matching the input. 19010 * 19011 * @param {Element} element The element root to search from. 19012 * @param {string} expression The binding expression to match. 19013 * @param {boolean} opt_exactMatch If true, only returns exact matches 19014 * for the expression. Filters and whitespace are ignored. 19015 */ 19016 testability.findBindings = function(element, expression, opt_exactMatch) { 19017 var bindings = element.getElementsByClassName('ng-binding'); 19018 var matches = []; 19019 forEach(bindings, function(binding) { 19020 var dataBinding = angular.element(binding).data('$binding'); 19021 if (dataBinding) { 19022 forEach(dataBinding, function(bindingName) { 19023 if (opt_exactMatch) { 19024 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 19025 if (matcher.test(bindingName)) { 19026 matches.push(binding); 19027 } 19028 } else { 19029 if (bindingName.indexOf(expression) != -1) { 19030 matches.push(binding); 19031 } 19032 } 19033 }); 19034 } 19035 }); 19036 return matches; 19037 }; 19038 19039 /** 19040 * @name $$testability#findModels 19041 * 19042 * @description 19043 * Returns an array of elements that are two-way found via ng-model to 19044 * expressions matching the input. 19045 * 19046 * @param {Element} element The element root to search from. 19047 * @param {string} expression The model expression to match. 19048 * @param {boolean} opt_exactMatch If true, only returns exact matches 19049 * for the expression. 19050 */ 19051 testability.findModels = function(element, expression, opt_exactMatch) { 19052 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 19053 for (var p = 0; p < prefixes.length; ++p) { 19054 var attributeEquals = opt_exactMatch ? '=' : '*='; 19055 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 19056 var elements = element.querySelectorAll(selector); 19057 if (elements.length) { 19058 return elements; 19059 } 19060 } 19061 }; 19062 19063 /** 19064 * @name $$testability#getLocation 19065 *
19066 * @description 19067 * Shortcut for getting the location in a browser agnostic way. Returns 19068 * the path, search, and hash. (e.g. /path?a=b#hash) 19069 */ 19070 testability.getLocation = function() { 19071 return $location.url(); 19072 }; 19073 19074 /** 19075 * @name $$testability#setLocation 19076 * 19077 * @description 19078 * Shortcut for navigating to a location without doing a full page reload. 19079 * 19080 * @param {string} url The location url (path, search and hash, 19081 * e.g. /path?a=b#hash) to go to. 19082 */ 19083 testability.setLocation = function(url) { 19084 if (url !== $location.url()) { 19085 $location.url(url); 19086 $rootScope.$digest(); 19087 } 19088 }; 19089 19090 /** 19091 * @name $$testability#whenStable 19092 * 19093 * @description 19094 * Calls the callback when $timeout and $http requests are completed. 19095 * 19096 * @param {function} callback 19097 */ 19098 testability.whenStable = function(callback) { 19099 $browser.notifyWhenNoOutstandingRequests(callback); 19100 }; 19101 19102 return testability; 19103 }]; 19104} 19105 19106function $TimeoutProvider() { 19107 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 19108 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 19109 19110 var deferreds = {}; 19111 19112 19113 /** 19114 * @ngdoc service 19115 * @name $timeout 19116 * 19117 * @description 19118 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 19119 * block and delegates any exceptions to 19120 * {@link ng.$exceptionHandler $exceptionHandler} service. 19121 * 19122 * The return value of calling `$timeout` is a promise, which will be resolved when 19123 * the delay has passed and the timeout function, if provided, is executed. 19124 * 19125 * To cancel a timeout request, call `$timeout.cancel(promise)`. 19126 * 19127 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 19128 * synchronously flush the queue of deferred functions. 19129 * 19130 * If you only want a promise that will be resolved after some specified delay 19131 * then you can call `$timeout` without the `fn` function. 19132 * 19133 * @param {function()=} fn A function, whose execution should be delayed. 19134 * @param {number=} [delay=0] Delay in milliseconds. 19135 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 19136 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 19137 * @param {...*=} Pass additional parameters to the executed function. 19138 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 19139 * will be resolved with the return value of the `fn` function. 19140 * 19141 */ 19142 function timeout(fn, delay, invokeApply) { 19143 if (!isFunction(fn)) { 19144 invokeApply = delay; 19145 delay = fn; 19146 fn = noop; 19147 } 19148 19149 var args = sliceArgs(arguments, 3), 19150 skipApply = (isDefined(invokeApply) && !invokeApply), 19151 deferred = (skipApply ? $$q : $q).defer(), 19152 promise = deferred.promise, 19153 timeoutId; 19154 19155 timeoutId = $browser.defer(function() { 19156 try { 19157 deferred.resolve(fn.apply(null, args)); 19158 } catch (e) { 19159 deferred.reject(e); 19160 $exceptionHandler(e); 19161 } 19162 finally { 19163 delete deferreds[promise.$$timeoutId]; 19164 } 19165 19166 if (!skipApply) $rootScope.$apply(); 19167 }, delay); 19168 19169 promise.$$timeoutId = timeoutId; 19170 deferreds[timeoutId] = deferred; 19171 19172 return promise; 19173 } 19174 19175 19176 /** 19177 * @ngdoc method 19178 * @name $timeout#cancel 19179 * 19180 * @description 19181 * Cancels a task associated with the `promise`. As a result of this, the promise will be 19182 * resolved with a rejection. 19183 * 19184 * @param {Promise=} promise Promise returned by the `$timeout` function. 19185 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 19186 * canceled. 19187 */ 19188 timeout.cancel = function(promise) { 19189 if (promise && promise.$$timeoutId in deferreds) { 19190 deferreds[promise.$$timeoutId].reject('canceled'); 19191 delete deferreds[promise.$$timeoutId]; 19192 return $browser.defer.cancel(promise.$$timeoutId); 19193 } 19194 return false;
vendor: 5,342 bytes, lines 19195-19316
19195 }; 19196 19197 return timeout; 19198 }]; 19199} 19200 19201// NOTE: The usage of window and document instead of $window and $document here is 19202// deliberate. This service depends on the specific behavior of anchor nodes created by the 19203// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 19204// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 19205// doesn't know about mocked locations and resolves URLs to the real document - which is 19206// exactly the behavior needed here. There is little value is mocking these out for this 19207// service. 19208var urlParsingNode = window.document.createElement("a"); 19209var originUrl = urlResolve(window.location.href); 19210 19211 19212/** 19213 * 19214 * Implementation Notes for non-IE browsers 19215 * ---------------------------------------- 19216 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 19217 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 19218 * URL will be resolved into an absolute URL in the context of the application document. 19219 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 19220 * properties are all populated to reflect the normalized URL. This approach has wide 19221 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 19222 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 19223 * 19224 * Implementation Notes for IE 19225 * --------------------------- 19226 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 19227 * browsers. However, the parsed components will not be set if the URL assigned did not specify 19228 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 19229 * work around that by performing the parsing in a 2nd step by taking a previously normalized 19230 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 19231 * properties such as protocol, hostname, port, etc. 19232 * 19233 * References: 19234 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 19235 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 19236 * http://url.spec.whatwg.org/#urlutils 19237 * https://github.com/angular/angular.js/pull/2902 19238 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 19239 * 19240 * @kind function 19241 * @param {string} url The URL to be parsed. 19242 * @description Normalizes and parses a URL. 19243 * @returns {object} Returns the normalized URL as a dictionary. 19244 * 19245 * | member name | Description | 19246 * |---------------|----------------| 19247 * | href | A normalized version of the provided URL if it was not an absolute URL | 19248 * | protocol | The protocol including the trailing colon | 19249 * | host | The host and port (if the port is non-default) of the normalizedUrl | 19250 * | search | The search params, minus the question mark | 19251 * | hash | The hash string, minus the hash symbol 19252 * | hostname | The hostname 19253 * | port | The port, without ":" 19254 * | pathname | The pathname, beginning with "/" 19255 * 19256 */ 19257function urlResolve(url) { 19258 var href = url; 19259 19260 if (msie) { 19261 // Normalize before parse. Refer Implementation Notes on why this is 19262 // done in two steps on IE. 19263 urlParsingNode.setAttribute("href", href); 19264 href = urlParsingNode.href; 19265 } 19266 19267 urlParsingNode.setAttribute('href', href); 19268 19269 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 19270 return { 19271 href: urlParsingNode.href, 19272 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 19273 host: urlParsingNode.host, 19274 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 19275 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 19276 hostname: urlParsingNode.hostname, 19277 port: urlParsingNode.port, 19278 pathname: (urlParsingNode.pathname.charAt(0) === '/') 19279 ? urlParsingNode.pathname 19280 : '/' + urlParsingNode.pathname 19281 }; 19282} 19283 19284/** 19285 * Parse a request URL and determine whether this is a same-origin request as the application document. 19286 * 19287 * @param {string|object} requestUrl The url of the request as a string that will be resolved 19288 * or a parsed URL object. 19289 * @returns {boolean} Whether the request is for the same origin as the application document. 19290 */ 19291function urlIsSameOrigin(requestUrl) { 19292 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 19293 return (parsed.protocol === originUrl.protocol && 19294 parsed.host === originUrl.host); 19295} 19296 19297/** 19298 * @ngdoc service 19299 * @name $window 19300 * 19301 * @description 19302 * A reference to the browser's `window` object. While `window` 19303 * is globally available in JavaScript, it causes testability problems, because 19304 * it is a global variable. In angular we always refer to it through the 19305 * `$window` service, so it may be overridden, removed or mocked for testing. 19306 * 19307 * Expressions, like the one defined for the `ngClick` directive in the example 19308 * below, are evaluated with respect to the current scope. Therefore, there is 19309 * no risk of inadvertently coding in a dependency on a global value in such an 19310 * expression. 19311 * 19312 * @example 19313 <example module="windowExample"> 19314 <file name="index.html"> 19315 <script> 19316 angular.module('windowExample', [])
19317 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 19318 $scope.greeting = 'Hello, World!'; 19319 $scope.doGreeting = function(greeting) { 19320 $window.alert(greeting); 19321 }; 19322 }]); 19323 </script> 19324 <div ng-controller="ExampleController"> 19325 <input type="text" ng-model="greeting" aria-label="greeting" /> 19326 <button ng-click="doGreeting(greeting)">ALERT</button> 19327 </div> 19328 </file> 19329 <file name="protractor.js" type="protractor"> 19330 it('should display the greeting in the input box', function() { 19331 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 19332 // If we click the button it will block the test runner 19333 // element(':button').click(); 19334 }); 19335 </file> 19336 </example> 19337 */ 19338function $WindowProvider() { 19339 this.$get = valueFn(window); 19340} 19341 19342/** 19343 * @name $$cookieReader 19344 * @requires $document 19345 * 19346 * @description 19347 * This is a private service for reading cookies used by $http and ngCookies 19348 * 19349 * @return {Object} a key/value map of the current cookies 19350 */ 19351function $$CookieReader($document) { 19352 var rawDocument = $document[0] || {}; 19353 var lastCookies = {}; 19354 var lastCookieString = ''; 19355 19356 function safeDecodeURIComponent(str) { 19357 try { 19358 return decodeURIComponent(str); 19359 } catch (e) { 19360 return str; 19361 } 19362 } 19363 19364 return function() { 19365 var cookieArray, cookie, i, index, name; 19366 var currentCookieString = rawDocument.cookie || ''; 19367 19368 if (currentCookieString !== lastCookieString) { 19369 lastCookieString = currentCookieString; 19370 cookieArray = lastCookieString.split('; '); 19371 lastCookies = {}; 19372 19373 for (i = 0; i < cookieArray.length; i++) { 19374 cookie = cookieArray[i]; 19375 index = cookie.indexOf('='); 19376 if (index > 0) { //ignore nameless cookies 19377 name = safeDecodeURIComponent(cookie.substring(0, index)); 19378 // the first value that is seen for a cookie is the most 19379 // specific one. values for the same cookie name that 19380 // follow are for less specific paths. 19381 if (isUndefined(lastCookies[name])) { 19382 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 19383 } 19384 } 19385 } 19386 } 19387 return lastCookies; 19388 }; 19389} 19390 19391$$CookieReader.$inject = ['$document']; 19392 19393function $$CookieReaderProvider() { 19394 this.$get = $$CookieReader; 19395} 19396 19397/* global currencyFilter: true, 19398 dateFilter: true, 19399 filterFilter: true, 19400 jsonFilter: true, 19401 limitToFilter: true, 19402 lowercaseFilter: true, 19403 numberFilter: true, 19404 orderByFilter: true, 19405 uppercaseFilter: true, 19406 */ 19407 19408/** 19409 * @ngdoc provider 19410 * @name $filterProvider 19411 * @description 19412 * 19413 * Filters are just functions which transform input to an output. However filters need to be 19414 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 19415 * annotated with dependencies and is responsible for creating a filter function. 19416 * 19417 * <div class="alert alert-warning"> 19418 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 19419 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 19420 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 19421 * (`myapp_subsection_filterx`). 19422 * </div> 19423 * 19424 * ```js 19425 * // Filter registration 19426 * function MyModule($provide, $filterProvider) { 19427 * // create a service to demonstrate injection (not always needed) 19428 * $provide.value('greet', function(name){ 19429 * return 'Hello ' + name + '!'; 19430 * }); 19431 * 19432 * // register a filter factory which uses the 19433 * // greet service to demonstrate DI. 19434 * $filterProvider.register('greet', function(greet){ 19435 * // return the filter function which uses the greet service 19436 * // to generate salutation 19437 * return function(text) { 19438 * // filters need to be forgiving so check input validity 19439 * return text && greet(text) || text; 19440 * }; 19441 * }); 19442 * } 19443 * ``` 19444 * 19445 * The filter function is registered with the `$injector` under the filter name suffix with 19446 * `Filter`. 19447 * 19448 * ```js 19449 * it('should be the same instance', inject( 19450 * function($filterProvider) { 19451 * $filterProvider.register('reverse', function(){ 19452 * return ...; 19453 * }); 19454 * }, 19455 * function($filter, reverseFilter) { 19456 * expect($filter('reverse')).toBe(reverseFilter); 19457 * }); 19458 * ``` 19459 * 19460 * 19461 * For more information about how angular filters work, and how to create your own filters, see 19462 * {@link guide/filter Filters} in the Angular Developer Guide. 19463 */ 19464 19465/** 19466 * @ngdoc service 19467 * @name $filter 19468 * @kind function 19469 * @description 19470 * Filters are used for formatting data displayed to the user. 19471 * 19472 * The general syntax in templates is as follows: 19473 * 19474 * {{ expression [| filter_name[:parameter_value] ... ] }} 19475 * 19476 * @param {String} name Name of the filter function to retrieve 19477 * @return {Function} the filter function 19478 * @example 19479 <example name="$filter" module="filterExample"> 19480 <file name="index.html"> 19481 <div ng-controller="MainCtrl"> 19482 <h3>{{ originalText }}</h3> 19483 <h3>{{ filteredText }}</h3> 19484 </div> 19485 </file> 19486 19487 <file name="script.js"> 19488 angular.module('filterExample', []) 19489 .controller('MainCtrl', function($scope, $filter) { 19490 $scope.originalText = 'hello'; 19491 $scope.filteredText = $filter('uppercase')($scope.originalText); 19492 }); 19493 </file> 19494 </example> 19495 */ 19496$FilterProvider.$inject = ['$provide']; 19497function $FilterProvider($provide) { 19498 var suffix = 'Filter'; 19499 19500 /** 19501 * @ngdoc method 19502 * @name $filterProvider#register 19503 * @param {string|Object} name Name of the filter function, or an object map of filters where 19504 * the keys are the filter names and the values are the filter factories. 19505 * 19506 * <div class="alert alert-warning"> 19507 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 19508 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 19509 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 19510 * (`myapp_subsection_filterx`). 19511 * </div> 19512 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 19513 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 19514 * of the registered filter instances. 19515 */ 19516 function register(name, factory) { 19517 if (isObject(name)) { 19518 var filters = {};
19519 forEach(name, function(filter, key) { 19520 filters[key] = register(key, filter); 19521 }); 19522 return filters; 19523 } else { 19524 return $provide.factory(name + suffix, factory); 19525 } 19526 } 19527 this.register = register; 19528 19529 this.$get = ['$injector', function($injector) { 19530 return function(name) { 19531 return $injector.get(name + suffix); 19532 }; 19533 }]; 19534 19535 //////////////////////////////////////// 19536 19537 /* global 19538 currencyFilter: false, 19539 dateFilter: false, 19540 filterFilter: false, 19541 jsonFilter: false, 19542 limitToFilter: false, 19543 lowercaseFilter: false, 19544 numberFilter: false, 19545 orderByFilter: false, 19546 uppercaseFilter: false, 19547 */ 19548 19549 register('currency', currencyFilter); 19550 register('date', dateFilter); 19551 register('filter', filterFilter); 19552 register('json', jsonFilter); 19553 register('limitTo', limitToFilter); 19554 register('lowercase', lowercaseFilter); 19555 register('number', numberFilter); 19556 register('orderBy', orderByFilter); 19557 register('uppercase', uppercaseFilter); 19558} 19559 19560/** 19561 * @ngdoc filter 19562 * @name filter 19563 * @kind function 19564 * 19565 * @description 19566 * Selects a subset of items from `array` and returns it as a new array. 19567 * 19568 * @param {Array} array The source array. 19569 * @param {string|Object|function()} expression The predicate to be used for selecting items from 19570 * `array`. 19571 * 19572 * Can be one of: 19573 * 19574 * - `string`: The string is used for matching against the contents of the `array`. All strings or 19575 * objects with string properties in `array` that match this string will be returned. This also 19576 * applies to nested object properties. 19577 * The predicate can be negated by prefixing the string with `!`. 19578 * 19579 * - `Object`: A pattern object can be used to filter specific properties on objects contained 19580 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 19581 * which have property `name` containing "M" and property `phone` containing "1". A special 19582 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 19583 * property of the object or its nested object properties. That's equivalent to the simple 19584 * substring match with a `string` as described above. The predicate can be negated by prefixing 19585 * the string with `!`. 19586 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 19587 * not containing "M". 19588 * 19589 * Note that a named property will match properties on the same level only, while the special 19590 * `$` property will match properties on the same level or deeper. E.g. an array item like 19591 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 19592 * **will** be matched by `{$: 'John'}`. 19593 * 19594 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 19595 * The function is called for each element of the array, with the element, its index, and 19596 * the entire array itself as arguments. 19597 * 19598 * The final result is an array of those elements that the predicate returned true for. 19599 * 19600 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 19601 * determining if the expected value (from the filter expression) and actual value (from 19602 * the object in the array) should be considered a match. 19603 * 19604 * Can be one of: 19605 * 19606 * - `function(actual, expected)`: 19607 * The function will be given the object value and the predicate value to compare and 19608 * should return true if both values should be considered equal. 19609 * 19610 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 19611 * This is essentially strict comparison of expected and actual. 19612 * 19613 * - `false|undefined`: A short hand for a function which will look for a substring match in case 19614 * insensitive way. 19615 * 19616 * Primitive values are converted to strings. Objects are not compared against primitives, 19617 * unless they have a custom `toString` method (e.g. `Date` objects). 19618 * 19619 * @example 19620 <example> 19621 <file name="index.html"> 19622 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 19623 {name:'Mary', phone:'800-BIG-MARY'}, 19624 {name:'Mike', phone:'555-4321'}, 19625 {name:'Adam', phone:'555-5678'}, 19626 {name:'Julie', phone:'555-8765'}, 19627 {name:'Juliette', phone:'555-5678'}]"></div> 19628 19629 <label>Search: <input ng-model="searchText"></label> 19630 <table id="searchTextResults"> 19631 <tr><th>
19631Name</th><th>Phone</th></tr> 19632 <tr ng-repeat="friend in friends | filter:searchText"> 19633 <td>{{friend.name}}</td> 19634 <td>{{friend.phone}}</td> 19635 </tr> 19636 </table> 19637 <hr> 19638 <label>Any: <input ng-model="search.$"></label> <br> 19639 <label>Name only <input ng-model="search.name"></label><br> 19640 <label>Phone only <input ng-model="search.phone"></label><br> 19641 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 19642 <table id="searchObjResults"> 19643 <tr><th>Name</th><th>Phone</th></tr> 19644 <tr ng-repeat="friendObj in friends | filter:search:strict"> 19645 <td>{{friendObj.name}}</td> 19646 <td>{{friendObj.phone}}</td> 19647 </tr> 19648 </table> 19649 </file> 19650 <file name="protractor.js" type="protractor"> 19651 var expectFriendNames = function(expectedNames, key) { 19652 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 19653 arr.forEach(function(wd, i) { 19654 expect(wd.getText()).toMatch(expectedNames[i]); 19655 }); 19656 }); 19657 }; 19658 19659 it('should search across all fields when filtering with a string', function() { 19660 var searchText = element(by.model('searchText')); 19661 searchText.clear(); 19662 searchText.sendKeys('m'); 19663 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 19664 19665 searchText.clear(); 19666 searchText.sendKeys('76'); 19667 expectFriendNames(['John', 'Julie'], 'friend'); 19668 }); 19669 19670 it('should search in specific fields when filtering with a predicate object', function() { 19671 var searchAny = element(by.model('search.$')); 19672 searchAny.clear(); 19673 searchAny.sendKeys('i'); 19674 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 19675 }); 19676 it('should use a equal comparison when comparator is true', function() { 19677 var searchName = element(by.model('search.name')); 19678 var strict = element(by.model('strict')); 19679 searchName.clear(); 19680 searchName.sendKeys('Julie'); 19681 strict.click(); 19682 expectFriendNames(['Julie'], 'friendObj'); 19683 }); 19684 </file> 19685 </example> 19686 */ 19687function filterFilter() { 19688 return function(array, expression, comparator) { 19689 if (!isArrayLike(array)) { 19690 if (array == null) { 19691 return array; 19692 } else { 19693 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 19694 } 19695 } 19696 19697 var expressionType = getTypeForFilter(expression); 19698 var predicateFn; 19699 var matchAgainstAnyProp; 19700 19701 switch (expressionType) { 19702 case 'function': 19703 predicateFn = expression; 19704 break; 19705 case 'boolean': 19706 case 'null': 19707 case 'number': 19708 case 'string': 19709 matchAgainstAnyProp = true; 19710 //jshint -W086 19711 case 'object': 19712 //jshint +W086 19713 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 19714 break; 19715 default: 19716 return array; 19717 } 19718 19719 return Array.prototype.filter.call(array, predicateFn); 19720 }; 19721} 19722 19723// Helper functions for `filterFilter` 19724function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 19725 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 19726 var predicateFn; 19727 19728 if (comparator === true) { 19729 comparator = equals; 19730 } else if (!isFunction(comparator)) { 19731 comparator = function(actual, expected) { 19732 if (isUndefined(actual)) { 19733 // No substring matching against `undefined` 19734 return false; 19735 } 19736 if ((actual === null) || (expected === null)) { 19737 // No substring matching against `null`; only match against `null` 19738 return actual === expected; 19739 } 19740 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 19741 // Should not compare primitives against objects, unless they have custom `toString` method 19742 return false; 19743 } 19744 19745 actual = lowercase('' + actual); 19746 expected = lowercase('' + expected); 19747 return actual.indexOf(expected) !== -1; 19748 }; 19749 } 19750 19751 predicateFn = function(item) { 19752 if (shouldMatchPrimitives && !isObject(item)) { 19753 return deepCompare(item, expression.$, comparator, false); 19754 } 19755 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 19756 }; 19757 19758 return predicateFn; 19759} 19760 19761function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 19762 var actualType = getTypeForFilter(actual); 19763 var expectedType = getTypeForFilter(expected); 19764 19765 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 19766 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 19767 } else if (isArray(actual)) { 19768 // In case `actual` is an array, consider it a match 19769 // if ANY of it's items matches `expected` 19770 return actual.some(function(item) { 19771 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 19772 }); 19773 } 19774 19775 switch (actualType) { 19776 case 'object': 19777 var key; 19778 if (matchAgainstAnyProp) { 19779 for (key in actual) { 19780 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 19781 return true; 19782 } 19783 } 19784 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 19785 } else if (expectedType === 'object') { 19786 for (key in expected) { 19787 var expectedVal = expected[key]; 19788 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 19789 continue; 19790 } 19791 19792 var matchAnyProperty = key === '$'; 19793 var actualVal = matchAnyProperty ? actual : actual[key]; 19794 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 19795 return false;
19796 } 19797 } 19798 return true; 19799 } else { 19800 return comparator(actual, expected); 19801 } 19802 break; 19803 case 'function': 19804 return false; 19805 default: 19806 return comparator(actual, expected); 19807 } 19808} 19809 19810// Used for easily differentiating between `null` and actual `object` 19811function getTypeForFilter(val) { 19812 return (val === null) ? 'null' : typeof val; 19813} 19814 19815var MAX_DIGITS = 22; 19816var DECIMAL_SEP = '.'; 19817var ZERO_CHAR = '0'; 19818 19819/** 19820 * @ngdoc filter 19821 * @name currency 19822 * @kind function 19823 * 19824 * @description 19825 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 19826 * symbol for current locale is used. 19827 * 19828 * @param {number} amount Input to filter. 19829 * @param {string=} symbol Currency symbol or identifier to be displayed. 19830 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 19831 * @returns {string} Formatted number. 19832 * 19833 * 19834 * @example 19835 <example module="currencyExample"> 19836 <file name="index.html"> 19837 <script> 19838 angular.module('currencyExample', []) 19839 .controller('ExampleController', ['$scope', function($scope) { 19840 $scope.amount = 1234.56; 19841 }]); 19842 </script>
19843 <div ng-controller="ExampleController"> 19844 <input type="number" ng-model="amount" aria-label="amount"> <br> 19845 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 19846 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 19847 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 19848 </div> 19849 </file> 19850 <file name="protractor.js" type="protractor"> 19851 it('should init with 1234.56', function() { 19852 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 19853 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 19854 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 19855 }); 19856 it('should update', function() { 19857 if (browser.params.browser == 'safari') { 19858 // Safari does not understand the minus key. See 19859 // https://github.com/angular/protractor/issues/481 19860 return; 19861 } 19862 element(by.model('amount')).clear(); 19863 element(by.model('amount')).sendKeys('-1234'); 19864 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 19865 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 19866 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 19867 }); 19868 </file> 19869 </example> 19870 */ 19871currencyFilter.$inject = ['$locale']; 19872function currencyFilter($locale) { 19873 var formats = $locale.NUMBER_FORMATS; 19874 return function(amount, currencySymbol, fractionSize) { 19875 if (isUndefined(currencySymbol)) { 19876 currencySymbol = formats.CURRENCY_SYM; 19877 } 19878 19879 if (isUndefined(fractionSize)) { 19880 fractionSize = formats.PATTERNS[1].maxFrac; 19881 } 19882 19883 // if null or undefined pass it through 19884 return (amount == null) 19885 ? amount 19886 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 19887 replace(/\u00A4/g, currencySymbol); 19888 }; 19889} 19890 19891/** 19892 * @ngdoc filter 19893 * @name number 19894 * @kind function 19895 * 19896 * @description 19897 * Formats a number as text. 19898 * 19899 * If the input is null or undefined, it will just be returned. 19900 * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively. 19901 * If the input is not a number an empty string is returned. 19902 * 19903 * 19904 * @param {number|string} number Number to format. 19905 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 19906 * If this is not provided then the fraction size is computed from the current locale's number 19907 * formatting pattern. In the case of the default locale, it will be 3. 19908 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current 19909 * locale (e.g., in the en_US locale it will have "." as the decimal separator and 19910 * include "," group separators after each third digit). 19911 * 19912 * @example 19913 <example module="numberFilterExample
vendor: 3,034 bytes, lines 19913-19992
19913"> 19914 <file name="index.html"> 19915 <script> 19916 angular.module('numberFilterExample', []) 19917 .controller('ExampleController', ['$scope', function($scope) { 19918 $scope.val = 1234.56789; 19919 }]); 19920 </script> 19921 <div ng-controller="ExampleController"> 19922 <label>Enter number: <input ng-model='val'></label><br> 19923 Default formatting: <span id='number-default'>{{val | number}}</span><br> 19924 No fractions: <span>{{val | number:0}}</span><br> 19925 Negative number: <span>{{-val | number:4}}</span> 19926 </div> 19927 </file> 19928 <file name="protractor.js" type="protractor"> 19929 it('should format numbers', function() { 19930 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 19931 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 19932 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 19933 }); 19934 19935 it('should update', function() { 19936 element(by.model('val')).clear(); 19937 element(by.model('val')).sendKeys('3374.333'); 19938 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 19939 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 19940 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 19941 }); 19942 </file> 19943 </example> 19944 */ 19945numberFilter.$inject = ['$locale']; 19946function numberFilter($locale) { 19947 var formats = $locale.NUMBER_FORMATS; 19948 return function(number, fractionSize) { 19949 19950 // if null or undefined pass it through 19951 return (number == null) 19952 ? number 19953 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 19954 fractionSize); 19955 }; 19956} 19957 19958/** 19959 * Parse a number (as a string) into three components that can be used 19960 * for formatting the number. 19961 * 19962 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 19963 * 19964 * @param {string} numStr The number to parse 19965 * @return {object} An object describing this number, containing the following keys: 19966 * - d : an array of digits containing leading zeros as necessary 19967 * - i : the number of the digits in `d` that are to the left of the decimal point 19968 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 19969 * 19970 */ 19971function parse(numStr) { 19972 var exponent = 0, digits, numberOfIntegerDigits; 19973 var i, j, zeros; 19974 19975 // Decimal point? 19976 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 19977 numStr = numStr.replace(DECIMAL_SEP, ''); 19978 } 19979 19980 // Exponential form? 19981 if ((i = numStr.search(/e/i)) > 0) { 19982 // Work out the exponent. 19983 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 19984 numberOfIntegerDigits += +numStr.slice(i + 1); 19985 numStr = numStr.substring(0, i); 19986 } else if (numberOfIntegerDigits < 0) { 19987 // There was no decimal point or exponent so it is an integer. 19988 numberOfIntegerDigits = numStr.length; 19989 } 19990 19991 // Count the number of leading zeros. 19992 for (i = 0; numStr.charAt(i) ==
vendor: 13,216 bytes, lines 19992-20363
19992 ZERO_CHAR; i++) {/* jshint noempty: false */} 19993 19994 if (i == (zeros = numStr.length)) { 19995 // The digits are all zero. 19996 digits = [0]; 19997 numberOfIntegerDigits = 1; 19998 } else { 19999 // Count the number of trailing zeros 20000 zeros--; 20001 while (numStr.charAt(zeros) == ZERO_CHAR) zeros--; 20002 20003 // Trailing zeros are insignificant so ignore them 20004 numberOfIntegerDigits -= i; 20005 digits = []; 20006 // Convert string to array of digits without leading/trailing zeros. 20007 for (j = 0; i <= zeros; i++, j++) { 20008 digits[j] = +numStr.charAt(i); 20009 } 20010 } 20011 20012 // If the number overflows the maximum allowed digits then use an exponent. 20013 if (numberOfIntegerDigits > MAX_DIGITS) { 20014 digits = digits.splice(0, MAX_DIGITS - 1); 20015 exponent = numberOfIntegerDigits - 1; 20016 numberOfIntegerDigits = 1; 20017 } 20018 20019 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 20020} 20021 20022/** 20023 * Round the parsed number to the specified number of decimal places 20024 * This function changed the parsedNumber in-place 20025 */ 20026function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 20027 var digits = parsedNumber.d; 20028 var fractionLen = digits.length - parsedNumber.i; 20029 20030 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 20031 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 20032 20033 // The index of the digit to where rounding is to occur 20034 var roundAt = fractionSize + parsedNumber.i; 20035 var digit = digits[roundAt]; 20036 20037 if (roundAt > 0) { 20038 // Drop fractional digits beyond `roundAt` 20039 digits.splice(Math.max(parsedNumber.i, roundAt)); 20040 20041 // Set non-fractional digits beyond `roundAt` to 0 20042 for (var j = roundAt; j < digits.length; j++) { 20043 digits[j] = 0; 20044 } 20045 } else { 20046 // We rounded to zero so reset the parsedNumber 20047 fractionLen = Math.max(0, fractionLen); 20048 parsedNumber.i = 1; 20049 digits.length = Math.max(1, roundAt = fractionSize + 1); 20050 digits[0] = 0; 20051 for (var i = 1; i < roundAt; i++) digits[i] = 0; 20052 } 20053 20054 if (digit >= 5) { 20055 if (roundAt - 1 < 0) { 20056 for (var k = 0; k > roundAt; k--) { 20057 digits.unshift(0); 20058 parsedNumber.i++; 20059 } 20060 digits.unshift(1); 20061 parsedNumber.i++; 20062 } else { 20063 digits[roundAt - 1]++; 20064 } 20065 } 20066 20067 // Pad out with zeros to get the required fraction length 20068 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); 20069 20070 20071 // Do any carrying, e.g. a digit was rounded up to 10 20072 var carry = digits.reduceRight(function(carry, d, i, digits) { 20073 d = d + carry; 20074 digits[i] = d % 10; 20075 return Math.floor(d / 10); 20076 }, 0); 20077 if (carry) { 20078 digits.unshift(carry); 20079 parsedNumber.i++; 20080 } 20081} 20082 20083/** 20084 * Format a number into a string 20085 * @param {number} number The number to format 20086 * @param {{ 20087 * minFrac, // the minimum number of digits required in the fraction part of the number 20088 * maxFrac, // the maximum number of digits required in the fraction part of the number 20089 * gSize, // number of digits in each group of separated digits 20090 * lgSize, // number of digits in the last group of digits before the decimal separator 20091 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 20092 * posPre, // the string to go in front of a positive number 20093 * negSuf, // the string to go after a negative number (e.g. `)`) 20094 * posSuf // the string to go after a positive number 20095 * }} pattern 20096 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 20097 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 20098 * @param {[type]} fractionSize The size of the fractional part of the number 20099 * @return {string} The number formatted as a string 20100 */ 20101function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 20102 20103 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 20104 20105 var isInfinity = !isFinite(number); 20106 var isZero = false; 20107 var numStr = Math.abs(number) + '', 20108 formattedText = '', 20109 parsedNumber; 20110 20111 if (isInfinity) { 20112 formattedText = '\u221e'; 20113 } else { 20114 parsedNumber = parse(numStr); 20115 20116 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 20117 20118 var digits = parsedNumber.d; 20119 var integerLen = parsedNumber.i; 20120 var exponent = parsedNumber.e; 20121 var decimals = []; 20122 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 20123 20124 // pad zeros for small numbers 20125 while (integerLen < 0) { 20126 digits.unshift(0); 20127 integerLen++; 20128 } 20129 20130 // extract decimals digits 20131 if (integerLen > 0) { 20132 decimals = digits.splice(integerLen); 20133 } else { 20134 decimals = digits; 20135 digits = [0]; 20136 } 20137 20138 // format the integer digits with grouping separators 20139 var groups = []; 20140 if (digits.length >= pattern.lgSize) { 20141 groups.unshift(digits.splice(-pattern.lgSize).join('')); 20142 } 20143 while (digits.length > pattern.gSize) { 20144 groups.unshift(digits.splice(-pattern.gSize).join('')); 20145 } 20146 if (digits.length) { 20147 groups.unshift(digits.join('')); 20148 } 20149 formattedText = groups.join(groupSep); 20150 20151 // append the decimal digits 20152 if (decimals.length) { 20153 formattedText += decimalSep + decimals.join(''); 20154 } 20155 20156 if (exponent) { 20157 formattedText += 'e+' + exponent; 20158 } 20159 } 20160 if (number < 0 && !isZero) { 20161 return pattern.negPre + formattedText + pattern.negSuf; 20162 } else { 20163 return pattern.posPre + formattedText + pattern.posSuf; 20164 } 20165} 20166 20167function padNumber(num, digits, trim, negWrap) { 20168 var neg = ''; 20169 if (num < 0 || (negWrap && num <= 0)) { 20170 if (negWrap) { 20171 num = -num + 1; 20172 } else { 20173 num = -num; 20174 neg = '-'; 20175 } 20176 } 20177 num = '' + num; 20178 while (num.length < digits) num = ZERO_CHAR + num; 20179 if (trim) { 20180 num = num.substr(num.length - digits); 20181 } 20182 return neg + num; 20183} 20184 20185 20186function dateGetter(name, size, offset, trim, negWrap) { 20187 offset = offset || 0; 20188 return function(date) { 20189 var value = date['get' + name](); 20190 if (offset > 0 || value > -offset) { 20191 value += offset; 20192 } 20193 if (value === 0 && offset == -12) value = 12; 20194 return padNumber(value, size, trim, negWrap); 20195 }; 20196} 20197 20198function dateStrGetter(name, shortForm, standAlone) { 20199 return function(date, formats) { 20200 var value = date['get' + name](); 20201 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); 20202 var get = uppercase(propPrefix + name); 20203 20204 return formats[get][value]; 20205 }; 20206} 20207 20208function timeZoneGetter(date, formats, offset) { 20209 var zone = -1 * offset; 20210 var paddedZone = (zone >= 0) ? "+" : ""; 20211 20212 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 20213 padNumber(Math.abs(zone % 60), 2); 20214 20215 return paddedZone; 20216} 20217 20218function getFirstThursdayOfYear(year) { 20219 // 0 = index of January 20220 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 20221 // 4 = index of Thursday (+1 to account for 1st = 5) 20222 // 11 = index of *next* Thursday (+1 account for 1st = 12) 20223 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 20224} 20225 20226function getThursdayThisWeek(datetime) { 20227 return new Date(datetime.getFullYear(), datetime.getMonth(), 20228 // 4 = index of Thursday 20229 datetime.getDate() + (4 - datetime.getDay())); 20230} 20231 20232function weekGetter(size) { 20233 return function(date) { 20234 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 20235 thisThurs = getThursdayThisWeek(date); 20236 20237 var diff = +thisThurs - +firstThurs, 20238 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 20239 20240 return padNumber(result, size); 20241 }; 20242} 20243 20244function ampmGetter(date, formats) { 20245 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 20246} 20247 20248function eraGetter(date, formats) { 20249 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 20250} 20251 20252function longEraGetter(date, formats) { 20253 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 20254} 20255 20256var DATE_FORMATS = { 20257 yyyy: dateGetter('FullYear', 4, 0, false, true), 20258 yy: dateGetter('FullYear', 2, 0, true, true), 20259 y: dateGetter('FullYear', 1, 0, false, true), 20260 MMMM: dateStrGetter('Month'), 20261 MMM: dateStrGetter('Month', true), 20262 MM: dateGetter('Month', 2, 1), 20263 M: dateGetter('Month', 1, 1), 20264 LLLL: dateStrGetter('Month', false, true), 20265 dd: dateGetter('Date', 2), 20266 d: dateGetter('Date', 1), 20267 HH: dateGetter('Hours', 2), 20268 H: dateGetter('Hours', 1), 20269 hh: dateGetter('Hours', 2, -12), 20270 h: dateGetter('Hours', 1, -12), 20271 mm: dateGetter('Minutes', 2), 20272 m: dateGetter('Minutes', 1), 20273 ss: dateGetter('Seconds', 2), 20274 s: dateGetter('Seconds', 1), 20275 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 20276 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 20277 sss: dateGetter('Milliseconds', 3), 20278 EEEE: dateStrGetter('Day'), 20279 EEE: dateStrGetter('Day', true), 20280 a: ampmGetter, 20281 Z: timeZoneGetter, 20282 ww: weekGetter(2), 20283 w: weekGetter(1), 20284 G: eraGetter, 20285 GG: eraGetter, 20286 GGG: eraGetter, 20287 GGGG: longEraGetter 20288}; 20289 20290var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 20291 NUMBER_STRING = /^\-?\d+$/; 20292 20293/** 20294 * @ngdoc filter 20295 * @name date 20296 * @kind function 20297 * 20298 * @description 20299 * Formats `date` to a string based on the requested `format`. 20300 * 20301 * `format` string can be composed of the following elements: 20302 * 20303 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 20304 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 20305 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 20306 * * `'MMMM'`: Month in year (January-December) 20307 * * `'MMM'`: Month in year (Jan-Dec) 20308 * * `'MM'`: Month in year, padded (01-12) 20309 * * `'M'`: Month in year (1-12) 20310 * * `'LLLL'`: Stand-alone month in year (January-December) 20311 * * `'dd'`: Day in month, padded (01-31) 20312 * * `'d'`: Day in month (1-31) 20313 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 20314 * * `'EEE'`: Day in Week, (Sun-Sat) 20315 * * `'HH'`: Hour in day, padded (00-23) 20316 * * `'H'`: Hour in day (0-23) 20317 * * `'hh'`: Hour in AM/PM, padded (01-12) 20318 * * `'h'`: Hour in AM/PM, (1-12) 20319 * * `'mm'`: Minute in hour, padded (00-59) 20320 * * `'m'`: Minute in hour (0-59) 20321 * * `'ss'`: Second in minute, padded (00-59) 20322 * * `'s'`: Second in minute (0-59) 20323 * * `'sss'`: Millisecond in second, padded (000-999) 20324 * * `'a'`: AM/PM marker 20325 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 20326 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 20327 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 20328 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 20329 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 20330 * 20331 * `format` string can also be one of the following predefined 20332 * {@link guide/i18n localizable formats}: 20333 * 20334 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 20335 * (e.g. Sep 3, 2010 12:05:08 PM) 20336 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 20337 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 20338 * (e.g. Friday, September 3, 2010) 20339 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 20340 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 20341 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 20342 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 20343 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 20344 * 20345 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 20346 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 20347 * (e.g. `"h 'o''clock'"`). 20348 * 20349 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 20350 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 20351 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 20352 * specified in the string input, the time is considered to be in the local timezone. 20353 * @param {string=} format Formatting rules (see Description). If not specified, 20354 * `mediumDate` is used. 20355 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 20356 * continental US time zone abbreviations, but for general use, use a time zone offset, for 20357 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 20358 * If not specified, the timezone of the browser will be used. 20359 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 20360 * 20361 * @example 20362 <example> 20363 <file name="index.html">
vendor: 4,095 bytes, lines 20364-20476
20364 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 20365 <span>{{1288323623006 | date:'medium'}}</span><br> 20366 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 20367 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 20368 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 20369 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 20370 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 20371 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 20372 </file> 20373 <file name="protractor.js" type="protractor"> 20374 it('should format date', function() { 20375 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 20376 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 20377 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 20378 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 20379 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 20380 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 20381 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 20382 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 20383 }); 20384 </file> 20385 </example> 20386 */ 20387dateFilter.$inject = ['$locale']; 20388function dateFilter($locale) { 20389 20390 20391 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 20392 // 1 2 3 4 5 6 7 8 9 10 11 20393 function jsonStringToDate(string) { 20394 var match; 20395 if (match = string.match(R_ISO8601_STR)) { 20396 var date = new Date(0), 20397 tzHour = 0, 20398 tzMin = 0, 20399 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 20400 timeSetter = match[8] ? date.setUTCHours : date.setHours; 20401 20402 if (match[9]) { 20403 tzHour = toInt(match[9] + match[10]); 20404 tzMin = toInt(match[9] + match[11]); 20405 } 20406 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 20407 var h = toInt(match[4] || 0) - tzHour; 20408 var m = toInt(match[5] || 0) - tzMin; 20409 var s = toInt(match[6] || 0); 20410 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 20411 timeSetter.call(date, h, m, s, ms); 20412 return date; 20413 } 20414 return string; 20415 } 20416 20417 20418 return function(date, format, timezone) { 20419 var text = '', 20420 parts = [], 20421 fn, match; 20422 20423 format = format || 'mediumDate'; 20424 format = $locale.DATETIME_FORMATS[format] || format; 20425 if (isString(date)) { 20426 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 20427 } 20428 20429 if (isNumber(date)) { 20430 date = new Date(date); 20431 } 20432 20433 if (!isDate(date) || !isFinite(date.getTime())) { 20434 return date; 20435 } 20436 20437 while (format) { 20438 match = DATE_FORMATS_SPLIT.exec(format); 20439 if (match) { 20440 parts = concat(parts, match, 1); 20441 format = parts.pop(); 20442 } else { 20443 parts.push(format); 20444 format = null; 20445 } 20446 } 20447 20448 var dateTimezoneOffset = date.getTimezoneOffset(); 20449 if (timezone) { 20450 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 20451 date = convertTimezoneToLocal(date, timezone, true); 20452 } 20453 forEach(parts, function(value) { 20454 fn = DATE_FORMATS[value]; 20455 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 20456 : value === "''" ? "'" : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 20457 }); 20458 20459 return text; 20460 }; 20461} 20462 20463 20464/** 20465 * @ngdoc filter 20466 * @name json 20467 * @kind function 20468 * 20469 * @description 20470 * Allows you to convert a JavaScript object into JSON string. 20471 * 20472 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 20473 * the binding is automatically converted to JSON. 20474 * 20475 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 20476 * @param {number=}
20476 spacing The number of spaces to use per indentation, defaults to 2. 20477 * @returns {string} JSON string. 20478 * 20479 * 20480 * @example 20481 <example> 20482 <file name="index.html"> 20483 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 20484 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 20485 </file> 20486 <file name="protractor.js" type="protractor"> 20487 it('should jsonify filtered objects', function() { 20488 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 20489 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 20490 }); 20491 </file> 20492 </example> 20493 * 20494 */ 20495function jsonFilter() { 20496 return function(object, spacing) { 20497 if (isUndefined(spacing)) { 20498 spacing = 2; 20499 } 20500 return toJson(object, spacing); 20501 }; 20502} 20503 20504 20505/** 20506 * @ngdoc filter 20507 * @name lowercase 20508 * @kind function 20509 * @description 20510 * Converts string to lowercase. 20511 * @see angular.lowercase 20512 */ 20513var lowercaseFilter = valueFn(lowercase); 20514 20515 20516/** 20517 * @ngdoc filter 20518 * @name uppercase 20519 * @kind function 20520 * @description 20521 * Converts string to uppercase. 20522 * @see angular.uppercase 20523 */ 20524var uppercaseFilter = valueFn(uppercase); 20525 20526/** 20527 * @ngdoc filter 20528 * @name limitTo 20529 * @kind function 20530 * 20531 * @description 20532 * Creates a new array or string containing only a specified number of elements. The elements 20533 * are taken from either the beginning or the end of the source array, string or number, as specified by 20534 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 20535 * converted to a string. 20536 * 20537 * @param {Array|string|number} input Source array, string or number to be limited. 20538 * @param {string|number} limit The length of the returned array or string. If the `limit` number 20539 * is positive, `limit` number of items from the beginning of the source array/string are copied. 20540 * If the number is negative, `limit` number of items from the end of the source array/string 20541 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 20542 * the input will be returned unchanged. 20543 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 20544 * indicates an offset from the end of `input`. Defaults to `0`. 20545 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 20546 * had less than `limit` elements. 20547 * 20548 * @example 20549 <example module="limitToExample"> 20550 <file name="index.html"> 20551 <script> 20552 angular.module('limitToExample', []) 20553 .controller('ExampleController', ['$scope', function($scope) { 20554 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 20555 $scope.letters = "abcdefghi"; 20556 $scope.longNumber = 2345432342; 20557 $scope.numLimit = 3; 20558 $scope.letterLimit = 3; 20559 $scope.longNumberLimit = 3; 20560 }]); 20561 </script>
20562 <div ng-controller="ExampleController"> 20563 <label> 20564 Limit {{numbers}} to: 20565 <input type="number" step="1" ng-model="numLimit"> 20566 </label> 20567 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 20568 <label> 20569 Limit {{letters}} to: 20570 <input type="number" step="1" ng-model="letterLimit"> 20571 </label> 20572 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 20573 <label> 20574 Limit {{longNumber}} to: 20575 <input type="number" step="1" ng-model="longNumberLimit"> 20576 </label> 20577 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 20578 </div> 20579 </file> 20580 <file name="protractor.js" type="protractor"> 20581 var numLimitInput = element(by.model('numLimit')); 20582 var letterLimitInput = element(by.model('letterLimit')); 20583 var longNumberLimitInput = element(by.model('longNumberLimit')); 20584 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 20585 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 20586 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 20587 20588 it('should limit the number array to first three items', function() { 20589 expect(numLimitInput.getAttribute('value')).toBe('3'); 20590 expect(letterLimitInput.getAttribute('value')).toBe('3'); 20591 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 20592 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 20593 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 20594 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 20595 }); 20596 20597 // There is a bug in safari and protractor that doesn't like the minus key 20598 // it('should update the output when -3 is entered', function() { 20599 // numLimitInput.clear(); 20600 // numLimitInput.sendKeys('-3'); 20601 // letterLimitInput.clear(); 20602 // letterLimitInput.sendKeys('-3'); 20603 // longNumberLimitInput.clear(); 20604 // longNumberLimitInput.sendKeys('-3'); 20605 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 20606 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 20607 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 20608 // }); 20609 20610 it('should not exceed the maximum size of input array', function() { 20611 numLimitInput.clear(); 20612 numLimitInput.sendKeys('100'); 20613 letterLimitInput.clear(); 20614 letterLimitInput.sendKeys('100'); 20615 longNumberLimitInput.clear(); 20616 longNumberLimitInput.sendKeys('100'); 20617 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 20618 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 20619 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 20620 }); 20621 </file> 20622 </example> 20623*/ 20624function limitToFilter() { 20625 return function(input, limit, begin) { 20626 if (Math.abs(Number(limit)) === Infinity) { 20627 limit = Number(limit); 20628 } else { 20629 limit = toInt(limit); 20630 } 20631 if (isNaN(limit)) return input; 20632 20633 if (isNumber(input)) input = input.toString(); 20634 if (!isArray(input) && !isString(input)) return input; 20635 20636 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 20637 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 20638 20639 if (limit >= 0) { 20640 return input.slice(begin, begin + limit); 20641 } else { 20642 if (begin === 0) { 20643 return input.slice(limit, input.length); 20644 } else { 20645 return input.slice(Math.max(0, begin + limit), begin); 20646 } 20647 } 20648 }; 20649} 20650 20651/** 20652 * @ngdoc filter 20653 * @name orderBy 20654 * @kind function 20655 * 20656 * @description 20657 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 20658 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 20659 * as expected, make sure they are actually being saved as numbers and not strings. 20660 * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported. 20661 * 20662 * @param {Array} array The array (or array-like object) to sort. 20663 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 20664 * used by the comparator to determine the order of elements. 20665 * 20666 * Can be one of: 20667 * 20668 * - `function`: Getter function. The result of this function will be sorted using the 20669 * `<`, `===`, `>` operator. 20670 * - `string`: An Angular expression. The result of this expression is used to compare elements 20671 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 20672 * 3 first characters of a property called `name`). The result of a constant expression 20673 * is interpreted as a property name to be used in comparisons (for example `"special name"` 20674 * to sort object by the value of their `special name` property). An expression can be 20675 * optionally prefixed with `+` or `-` to control ascending or descending sort order 20676 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 20677 * element itself is used to compare where sorting. 20678 * - `Array`: An array of function or string predicates. The first predicate in the array 20679 * is used for sorting, but when two items are equivalent, the next predicate is used. 20680 * 20681 * If the predicate is missing or empty then it defaults to `'+'`. 20682 * 20683 * @param {boolean=} reverse Reverse the order of the array. 20684 * @returns {Array} Sorted copy of the source array. 20685 * 20686 * 20687 * @example 20688 * The example below demonstrates a simple ngRepeat, where the data is sorted 20689 * by age in descending order (predicate is set to `'-age'`). 20690 * `reverse` is not set, which means it defaults to `false`. 20691 <example module="orderByExample"> 20692 <file name="index.html">
20693 <div ng-controller="ExampleController"> 20694 <table class="friend"> 20695 <tr> 20696 <th>Name</th> 20697 <th>Phone Number</th> 20698 <th>Age</th> 20699 </tr> 20700 <tr ng-repeat="friend in friends | orderBy:'-age'"> 20701 <td>{{friend.name}}</td> 20702 <td>{{friend.phone}}</td> 20703 <td>{{friend.age}}</td> 20704 </tr> 20705 </table> 20706 </div> 20707 </file> 20708 <file name="script.js"> 20709 angular.module('orderByExample', []) 20710 .controller('ExampleController', ['$scope', function($scope) { 20711 $scope.friends = 20712 [{name:'John', phone:'555-1212', age:10}, 20713 {name:'Mary', phone:'555-9876', age:19}, 20714 {name:'Mike', phone:'555-4321', age:21}, 20715 {name:'Adam', phone:'555-5678', age:35}, 20716 {name:'Julie', phone:'555-8765', age:29}]; 20717 }]); 20718 </file> 20719 </example> 20720 * 20721 * The predicate and reverse parameters can be controlled dynamically through scope properties, 20722 * as shown in the next example. 20723 * @example 20724 <example module="orderByExample"> 20725 <file name="index.html">
20726 <div ng-controller="ExampleController"> 20727 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 20728 <hr/> 20729 <button ng-click="predicate=''">Set to unsorted</button> 20730 <table class="friend"> 20731 <tr> 20732 <th> 20733 <button ng-click="order('name')">Name</button> 20734 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 20735 </th> 20736 <th> 20737 <button ng-click="order('phone')">Phone Number</button> 20738 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 20739 </th> 20740 <th> 20741 <button ng-click="order('age')">Age</button> 20742 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 20743 </th> 20744 </tr> 20745 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 20746 <td>{{friend.name}}</td> 20747 <td>{{friend.phone}}</td> 20748 <td>{{friend.age}}</td> 20749 </tr> 20750 </table> 20751 </div> 20752 </file> 20753 <file name="script.js"> 20754 angular.module('orderByExample', []) 20755 .controller('ExampleController', ['$scope', function($scope) { 20756 $scope.friends = 20757 [{name:'John', phone:'555-1212', age:10}, 20758 {name:'Mary', phone:'555-9876', age:19}, 20759 {name:'Mike', phone:'555-4321', age:21}, 20760 {name:'Adam', phone:'555-5678', age:35}, 20761 {name:'Julie', phone:'555-8765', age:29}]; 20762 $scope.predicate = 'age'; 20763 $scope.reverse = true; 20764 $scope.order = function(predicate) { 20765 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 20766 $scope.predicate = predicate; 20767 }; 20768 }]); 20769 </file> 20770 <file name="style.css"> 20771 .sortorder:after { 20772 content: '\25b2'; 20773 } 20774 .sortorder.reverse:after { 20775 content: '\25bc'; 20776 } 20777 </file> 20778 </example> 20779 * 20780 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 20781 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 20782 * desired parameters. 20783 * 20784 * Example: 20785 * 20786 * @example 20787 <example module="orderByExample"> 20788 <file name="index.html">
20789 <div ng-controller="ExampleController"> 20790 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 20791 <table class="friend"> 20792 <tr> 20793 <th> 20794 <button ng-click="order('name')">Name</button> 20795 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 20796 </th> 20797 <th> 20798 <button ng-click="order('phone')">Phone Number</button> 20799 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 20800 </th> 20801 <th> 20802 <button ng-click="order('age')">Age</button> 20803 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 20804 </th> 20805 </tr> 20806 <tr ng-repeat="friend in friends"> 20807 <td>{{friend.name}}</td> 20808 <td>{{friend.phone}}</td> 20809 <td>{{friend.age}}</td> 20810 </tr> 20811 </table> 20812 </div> 20813 </file> 20814 20815 <file name="script.js"> 20816 angular.module('orderByExample', []) 20817 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 20818 var orderBy = $filter('orderBy'); 20819 $scope.friends = [ 20820 { name: 'John', phone: '555-1212', age: 10 }, 20821 { name: 'Mary', phone: '555-9876', age: 19 }, 20822 { name: 'Mike', phone: '555-4321', age: 21 }, 20823 { name: 'Adam', phone: '555-5678', age: 35 }, 20824 { name: 'Julie', phone: '555-8765', age: 29 } 20825 ]; 20826 $scope.order = function(predicate) { 20827 $scope.predicate = predicate; 20828 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 20829 $scope.friends = orderBy($scope.friends, predicate, $scope.reverse); 20830 }; 20831 $scope.order('age', true); 20832 }]); 20833 </file> 20834 20835 <file name="style.css"> 20836 .sortorder:after { 20837 content: '\25b2'; 20838 } 20839 .sortorder.reverse:after { 20840 content: '\25bc'; 20841 } 20842 </file> 20843</example> 20844 */ 20845orderByFilter.$inject = ['$parse']; 20846function orderByFilter($parse) { 20847 return function(array, sortPredicate, reverseOrder) { 20848 20849 if (array == null) return array; 20850 if (!isArrayLike(array)) { 20851 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 20852 } 20853 20854 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 20855 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 20856 20857 var predicates = processPredicates(sortPredicate, reverseOrder); 20858 // Add a predicate at the end that evaluates to the element index. This makes the 20859 // sort stable as it works as a tie-breaker when all the input predicates cannot 20860 // distinguish between two elements. 20861 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 20862 20863 // The next three lines are a version of a Swartzian Transform idiom from Perl 20864 // (sometimes called the Decorate-Sort-Undecorate idiom) 20865 // See https://en.wikipedia.org/wiki/Schwartzian_transform 20866 var compareValues = Array.prototype.map.call(array, getComparisonObject); 20867 compareValues.sort(doComparison); 20868 array = compareValues.map(function(item) { return item.value; }); 20869 20870 return array; 20871 20872 function getComparisonObject(value, index) { 20873 return { 20874 value: value, 20875 predicateValues: predicates.map(function(predicate) { 20876 return getPredicateValue(predicate.get(value), index); 20877 }) 20878 }; 20879 } 20880 20881 function doComparison(v1, v2) { 20882 var result = 0; 20883 for (var index=0, length = predicates.length; index < length; ++index) { 20884 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 20885 if (result) break; 20886 } 20887 return result; 20888 } 20889 }; 20890 20891 function processPredicates(sortPredicate, reverseOrder) { 20892 reverseOrder = reverseOrder ? -1 : 1; 20893 return sortPredicate.map(function(predicate) { 20894 var descending = 1, get = identity; 20895 20896 if (isFunction(predicate)) { 20897 get = predicate; 20898 } else if (isString(predicate)) { 20899 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 20900 descending = predicate.charAt(0) == '-' ? -1 : 1; 20901 predicate = predicate.substring(1); 20902 } 20903 if (predicate !== '') { 20904 get = $parse(predicate); 20905 if (get.constant) { 20906 var key = get(); 20907 get = function(value) { return value[key]; }; 20908 } 20909 } 20910 }
20911 return { get: get, descending: descending * reverseOrder }; 20912 }); 20913 } 20914 20915 function isPrimitive(value) { 20916 switch (typeof value) { 20917 case 'number': /* falls through */ 20918 case 'boolean': /* falls through */ 20919 case 'string': 20920 return true; 20921 default: 20922 return false; 20923 } 20924 } 20925 20926 function objectValue(value, index) { 20927 // If `valueOf` is a valid function use that 20928 if (typeof value.valueOf === 'function') { 20929 value = value.valueOf(); 20930 if (isPrimitive(value)) return value; 20931 } 20932 // If `toString` is a valid function and not the one from `Object.prototype` use that 20933 if (hasCustomToString(value)) { 20934 value = value.toString(); 20935 if (isPrimitive(value)) return value; 20936 } 20937 // We have a basic object so we use the position of the object in the collection 20938 return index; 20939 } 20940 20941 function getPredicateValue(value, index) { 20942 var type = typeof value; 20943 if (value === null) { 20944 type = 'string'; 20945 value = 'null'; 20946 } else if (type === 'string') { 20947 value = value.toLowerCase(); 20948 } else if (type === 'object') { 20949 value = objectValue(value, index); 20950 } 20951 return { value: value, type: type }; 20952 } 20953 20954 function compare(v1, v2) { 20955 var result = 0; 20956 if (v1.type === v2.type) { 20957 if (v1.value !== v2.value) { 20958 result = v1.value < v2.value ? -1 : 1; 20959 } 20960 } else { 20961 result = v1.type < v2.type ? -1 : 1; 20962 } 20963 return result; 20964 } 20965} 20966 20967function ngDirective(directive) { 20968 if (isFunction(directive)) { 20969 directive = { 20970 link: directive 20971 }; 20972 } 20973 directive.restrict = directive.restrict || 'AC'; 20974 return valueFn(directive); 20975} 20976 20977/** 20978 * @ngdoc directive 20979 * @name a 20980 * @restrict E 20981 * 20982 * @description 20983 * Modifies the default behavior of the html A tag so that the default action is prevented when 20984 * the href attribute is empty. 20985 * 20986 * This change permits the easy creation of action links with the `ngClick` directive 20987 * without changing the location or causing page reloads, e.g.: 20988 * `<a href="" ng-click="list.addItem()">Add Item</a>` 20989 */ 20990var htmlAnchorDirective = valueFn({ 20991 restrict: 'E', 20992 compile: function(element, attr) { 20993 if (!attr.href && !attr.xlinkHref) { 20994 return function(scope, element) { 20995 // If the linked element is not an anchor tag anymore, do nothing 20996 if (element[0].nodeName.toLowerCase() !== 'a') return; 20997 20998 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 20999 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 21000 'xlink:href' : 'href'; 21001 element.on('click', function(event) { 21002 // if we have no href url, then don't navigate anywhere. 21003 if (!element.attr(href)) { 21004 event.preventDefault(); 21005 } 21006 }); 21007 }; 21008 } 21009 } 21010}); 21011 21012/** 21013 * @ngdoc directive 21014 * @name ngHref 21015 * @restrict A 21016 * @priority 99 21017 * 21018 * @description 21019 * Using Angular markup like `{{hash}}` in an href attribute will 21020 * make the link go to the wrong URL if the user clicks it before 21021 * Angular has a chance to replace the `{{hash}}` markup with its 21022 * value. Until Angular replaces the markup the link will be broken 21023 * and will most likely return a 404 error. The `ngHref` directive 21024 * solves this problem. 21025 * 21026 * The wrong way to write it: 21027 * ```html 21028 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 21029 * ``` 21030 * 21031 * The correct way to write it: 21032 * ```html 21033 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 21034 * ``` 21035 * 21036 * @element A 21037 * @param {template} ngHref any string which can contain `{{}}` markup. 21038 * 21039 * @example 21040 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 21041 * in links and their different behaviors: 21042 <example> 21043 <file name="index.html"> 21044 <input ng-model="value" /><br /> 21045 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 21046 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 21047 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 21048 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 21049 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 21050 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 21051 </file> 21052 <file name="protractor.js" type="protractor"> 21053 it('should execute ng-click but not reload when href without value', function() {
21054 element(by.id('link-1')).click(); 21055 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 21056 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 21057 }); 21058 21059 it('should execute ng-click but not reload when href empty string', function() { 21060 element(by.id('link-2')).click(); 21061 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 21062 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 21063 }); 21064 21065 it('should execute ng-click and change url when ng-href specified', function() { 21066 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 21067 21068 element(by.id('link-3')).click(); 21069 21070 // At this point, we navigate away from an Angular page, so we need 21071 // to use browser.driver to get the base webdriver. 21072 21073 browser.wait(function() { 21074 return browser.driver.getCurrentUrl().then(function(url) { 21075 return url.match(/\/123$/); 21076 }); 21077 }, 5000, 'page should navigate to /123'); 21078 }); 21079 21080 it('should execute ng-click but not reload when href empty string and name specified', function() { 21081 element(by.id('link-4')).click(); 21082 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 21083 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 21084 }); 21085 21086 it('should execute ng-click but not reload when no href but name specified', function() { 21087 element(by.id('link-5')).click(); 21088 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 21089 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 21090 }); 21091 21092 it('should only change url when only ng-href', function() { 21093 element(by.model('value')).clear(); 21094 element(by.model('value')).sendKeys('6'); 21095 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 21096 21097 element(by.id('link-6')).click(); 21098 21099 // At this point, we navigate away from an Angular page, so we need 21100 // to use browser.driver to get the base webdriver. 21101 browser.wait(function() { 21102 return browser.driver.getCurrentUrl().then(function(url) { 21103 return url.match(/\/6$/); 21104 }); 21105 }, 5000, 'page should navigate to /6'); 21106 }); 21107 </file> 21108 </example> 21109 */ 21110 21111/** 21112 * @ngdoc directive 21113 * @name ngSrc 21114 * @restrict A 21115 * @priority 99 21116 * 21117 * @description 21118 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 21119 * work right: The browser will fetch from the URL with the literal 21120 * text `{{hash}}` until Angular replaces the expression inside 21121 * `{{hash}}`. The `ngSrc` directive solves this problem. 21122 * 21123 * The buggy way to write it: 21124 * ```html 21125 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 21126 * ``` 21127 * 21128 * The correct way to write it: 21129 * ```html 21130 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 21131 * ``` 21132 * 21133 * @element IMG 21134 * @param {template} ngSrc any string which can contain `{{}}` markup. 21135 */ 21136 21137/** 21138 * @ngdoc directive 21139 * @name ngSrcset 21140 * @restrict A 21141 * @priority 99 21142 * 21143 * @description 21144 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 21145 * work right: The browser will fetch from the URL with the literal 21146 * text `{{hash}}` until Angular replaces the expression inside 21147 * `{{hash}}`. The `ngSrcset` directive solves this problem. 21148 * 21149 * The buggy way to write it: 21150 * ```html 21151 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 21152 * ``` 21153 * 21154 * The correct way to write it: 21155 * ```html 21156 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 21157 * ``` 21158 * 21159 * @element IMG 21160 * @param {template} ngSrcset any string which can contain `{{}}` markup. 21161 */ 21162 21163/** 21164 * @ngdoc directive 21165 * @name ngDisabled 21166 * @restrict A 21167 * @priority 100 21168 * 21169 * @description 21170 * 21171 * This directive sets the `disabled` attribute on the element if the 21172 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 21173 * 21174 * A special directive is necessary because we cannot use interpolation inside the `disabled` 21175 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 21176 * 21177 * @example 21178 <example> 21179 <file name="index.html"> 21180 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 21181 <button ng-model="button" ng-disabled="checked">Button</button> 21182 </file> 21183 <file name="protractor.js" type="protractor"> 21184 it('should toggle button', function() { 21185 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
21186 element(by.model('checked')).click(); 21187 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 21188 }); 21189 </file> 21190 </example> 21191 * 21192 * @element INPUT 21193 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 21194 * then the `disabled` attribute will be set on the element 21195 */ 21196 21197 21198/** 21199 * @ngdoc directive 21200 * @name ngChecked 21201 * @restrict A 21202 * @priority 100 21203 * 21204 * @description 21205 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 21206 * 21207 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 21208 * as this can lead to unexpected behavior. 21209 * 21210 * A special directive is necessary because we cannot use interpolation inside the `checked` 21211 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 21212 * 21213 * @example 21214 <example> 21215 <file name="index.html"> 21216 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 21217 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 21218 </file> 21219 <file name="protractor.js" type="protractor"> 21220 it('should check both checkBoxes', function() { 21221 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 21222 element(by.model('master')).click(); 21223 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 21224 }); 21225 </file> 21226 </example> 21227 * 21228 * @element INPUT 21229 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 21230 * then the `checked` attribute will be set on the element 21231 */ 21232 21233 21234/** 21235 * @ngdoc directive 21236 * @name ngReadonly 21237 * @restrict A 21238 * @priority 100 21239 * 21240 * @description 21241 * 21242 * Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy. 21243 * 21244 * A special directive is necessary because we cannot use interpolation inside the `readOnly` 21245 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 21246 * 21247 * @example 21248 <example> 21249 <file name="index.html"> 21250 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 21251 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 21252 </file> 21253 <file name="protractor.js" type="protractor"> 21254 it('should toggle readonly attr', function() { 21255 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 21256 element(by.model('checked')).click(); 21257 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 21258 }); 21259 </file> 21260 </example> 21261 * 21262 * @element INPUT 21263 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 21264 * then special attribute "readonly" will be set on the element 21265 */ 21266 21267 21268/** 21269 * @ngdoc directive 21270 * @name ngSelected 21271 * @restrict A 21272 * @priority 100 21273 * 21274 * @description 21275 * 21276 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 21277 * 21278 * A special directive is necessary because we cannot use interpolation inside the `selected` 21279 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 21280 * 21281 * @example 21282 <example> 21283 <file name="index.html"> 21284 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 21285 <select aria-label="ngSelected demo"> 21286 <option>Hello!</option> 21287 <option id="greet" ng-selected="selected">Greetings!</option> 21288 </select> 21289 </file> 21290 <file name="protractor.js" type="protractor"> 21291 it('should select Greetings!', function() { 21292 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 21293 element(by.model('selected')).click(); 21294 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 21295 }); 21296 </file> 21297 </example> 21298 * 21299 * @element OPTION 21300 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 21301 * then special attribute "selected" will be set on the element 21302 */ 21303 21304/** 21305 * @ngdoc directive 21306 * @name ngOpen 21307 * @restrict A 21308 * @priority 100 21309 * 21310 * @description 21311 * 21312 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 21313 * 21314 * A special directive is necessary because we cannot use interpolation inside the `open` 21315 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 21316 * 21317 * @example 21318 <example> 21319 <file name="index.html"> 21320 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 21321 <details id="details" ng-open="open"> 21322 <summary>Show/Hide me</summary> 21323 </details> 21324 </file> 21325 <file name="protractor.js" type="protractor"> 21326 it('should toggle open', function() { 21327 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
21328 element(by.model('open')).click(); 21329 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 21330 }); 21331 </file> 21332 </example> 21333 * 21334 * @element DETAILS 21335 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 21336 * then special attribute "open" will be set on the element 21337 */ 21338 21339var ngAttributeAliasDirectives = {}; 21340 21341// boolean attrs are evaluated 21342forEach(BOOLEAN_ATTR, function(propName, attrName) { 21343 // binding to multiple is not supported 21344 if (propName == "multiple") return; 21345 21346 function defaultLinkFn(scope, element, attr) { 21347 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 21348 attr.$set(attrName, !!value); 21349 }); 21350 } 21351 21352 var normalized = directiveNormalize('ng-' + attrName); 21353 var linkFn = defaultLinkFn; 21354 21355 if (propName === 'checked') { 21356 linkFn = function(scope, element, attr) { 21357 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 21358 if (attr.ngModel !== attr[normalized]) { 21359 defaultLinkFn(scope, element, attr); 21360 } 21361 }; 21362 } 21363 21364 ngAttributeAliasDirectives[normalized] = function() { 21365 return { 21366 restrict: 'A', 21367 priority: 100, 21368 link: linkFn 21369 }; 21370 }; 21371}); 21372 21373// aliased input attrs are evaluated 21374forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 21375 ngAttributeAliasDirectives[ngAttr] = function() { 21376 return { 21377 priority: 100, 21378 link: function(scope, element, attr) { 21379 //special case ngPattern when a literal regular expression value 21380 //is used as the expression (this way we don't have to watch anything). 21381 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 21382 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 21383 if (match) { 21384 attr.$set("ngPattern", new RegExp(match[1], match[2])); 21385 return; 21386 } 21387 } 21388 21389 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 21390 attr.$set(ngAttr, value); 21391 }); 21392 } 21393 }; 21394 }; 21395}); 21396 21397// ng-src, ng-srcset, ng-href are interpolated 21398forEach(['src', 'srcset', 'href'], function(attrName) { 21399 var normalized = directiveNormalize('ng-' + attrName); 21400 ngAttributeAliasDirectives[normalized] = function() { 21401 return { 21402 priority: 99, // it needs to run after the attributes are interpolated 21403 link: function(scope, element, attr) { 21404 var propName = attrName, 21405 name = attrName; 21406 21407 if (attrName === 'href' && 21408 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 21409 name = 'xlinkHref'; 21410 attr.$attr[name] = 'xlink:href'; 21411 propName = null; 21412 } 21413 21414 attr.$observe(normalized, function(value) { 21415 if (!value) { 21416 if (attrName === 'href') { 21417 attr.$set(name, null); 21418 } 21419 return; 21420 } 21421 21422 attr.$set(name, value); 21423 21424 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 21425 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 21426 // to set the property as well to achieve the desired effect. 21427 // we use attr[attrName] value since $set can sanitize the url. 21428 if (msie && propName) element.prop(propName, attr[name]); 21429 }); 21430 } 21431 }; 21432 }; 21433}); 21434 21435/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 21436 */ 21437var nullFormCtrl = { 21438 $addControl: noop, 21439 $$renameControl: nullFormRenameControl, 21440 $removeControl: noop, 21441 $setValidity: noop, 21442 $setDirty: noop, 21443 $setPristine: noop, 21444 $setSubmitted: noop 21445}, 21446SUBMITTED_CLASS = 'ng-submitted'; 21447 21448function nullFormRenameControl(control, name) { 21449 control.$name = name; 21450} 21451 21452/** 21453 * @ngdoc type 21454 * @name form.FormController 21455 * 21456 * @property {boolean} $pristine True if user has not interacted with the form yet. 21457 * @property {boolean} $dirty True if user has already interacted with the form. 21458 * @property {boolean} $valid True if all of the containing forms and controls are valid. 21459 * @property {boolean} $invalid True if at least one containing control or form is invali
21459d. 21460 * @property {boolean} $pending True if at least one containing control or form is pending. 21461 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 21462 * 21463 * @property {Object} $error Is an object hash, containing references to controls or 21464 * forms with failing validators, where: 21465 * 21466 * - keys are validation tokens (error names), 21467 * - values are arrays of controls or forms that have a failing validator for given error name. 21468 * 21469 * Built-in validation tokens: 21470 * 21471 * - `email` 21472 * - `max` 21473 * - `maxlength` 21474 * - `min` 21475 * - `minlength` 21476 * - `number` 21477 * - `pattern` 21478 * - `required` 21479 * - `url` 21480 * - `date` 21481 * - `datetimelocal` 21482 * - `time` 21483 * - `week` 21484 * - `month` 21485 * 21486 * @description 21487 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 21488 * such as being valid/invalid or dirty/pristine. 21489 * 21490 * Each {@link ng.directive:form form} directive creates an instance 21491 * of `FormController`. 21492 * 21493 */ 21494//asks for $scope to fool the BC controller module 21495FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 21496function FormController(element, attrs, $scope, $animate, $interpolate) { 21497 var form = this, 21498 controls = []; 21499 21500 // init state 21501 form.$error = {}; 21502 form.$$success = {}; 21503 form.$pending = undefined; 21504 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 21505 form.$dirty = false; 21506 form.$pristine = true; 21507 form.$valid = true; 21508 form.$invalid = false; 21509 form.$submitted = false; 21510 form.$$parentForm = nullFormCtrl; 21511 21512 /** 21513 * @ngdoc method 21514 * @name form.FormController#$rollbackViewValue 21515 * 21516 * @description 21517 * Rollback all form controls pending updates to the `$modelValue`. 21518 * 21519 * Updates may be pending by a debounced event or because the input is waiting for a some future 21520 * event defined in `ng-model-options`. This method is typically needed by the reset button of 21521 * a form that uses `ng-model-options` to pend updates. 21522 */ 21523 form.$rollbackViewValue = function() { 21524 forEach(controls, function(control) { 21525 control.$rollbackViewValue(); 21526 }); 21527 }; 21528 21529 /** 21530 * @ngdoc method 21531 * @name form.FormController#$commitViewValue 21532 * 21533 * @description 21534 * Commit all form controls pending updates to the `$modelValue`. 21535 * 21536 * Updates may be pending by a debounced event or because the input is waiting for a some future 21537 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 21538 * usually handles calling this in response to input events. 21539 */ 21540 form.$commitViewValue = function() { 21541 forEach(controls, function(control) { 21542 control.$commitViewValue(); 21543 }); 21544 }; 21545 21546 /** 21547 * @ngdoc method 21548 * @name form.FormController#$addControl 21549 * @param {object} control control object, either a {@link form.FormController} or an 21550 * {@link ngModel.NgModelController} 21551 * 21552 * @description 21553 * Register a control with the form. Input elements using ngModelController do this automatically 21554 * when they are linked. 21555 * 21556 * Note that the current state of the control will not be reflected on the new parent form. This 21557 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 21558 * state. 21559 * 21560 * However, if the method is used programmatically, for example by adding dynamically created controls, 21561 * or controls that have been previously removed without destroying their corresponding DOM element, 21562 * it's the developers responsibility to make sure the current state propagates to the parent form. 21563 * 21564 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 21565 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 21566 */ 21567 form.$addControl = function(control) { 21568 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 21569 // and not added to the scope. Now we throw an error. 21570 assertNotHasOwnProperty(control.$name, 'input'); 21571 controls.push(control); 21572 21573 if (control.$name) { 21574 form[control.$name] = control; 21575 } 21576 21577 control.$$parentForm = form; 21578 }; 21579 21580 // Private API: rename a form control 21581 form.$$renameControl = function(control, newName) { 21582 var oldName = control.$name; 21583 21584 if (form[oldName] === control) { 21585 delete form[oldName]; 21586 } 21587 form[newName] = control; 21588 control.$name = newName; 21589 }; 21590 21591 /** 21592 * @ngdoc method
21593 * @name form.FormController#$removeControl 21594 * @param {object} control control object, either a {@link form.FormController} or an 21595 * {@link ngModel.NgModelController} 21596 * 21597 * @description 21598 * Deregister a control from the form. 21599 * 21600 * Input elements using ngModelController do this automatically when they are destroyed. 21601 * 21602 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 21603 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 21604 * different from case to case. For example, removing the only `$dirty` control from a form may or 21605 * may not mean that the form is still `$dirty`. 21606 */ 21607 form.$removeControl = function(control) { 21608 if (control.$name && form[control.$name] === control) { 21609 delete form[control.$name]; 21610 } 21611 forEach(form.$pending, function(value, name) { 21612 form.$setValidity(name, null, control); 21613 }); 21614 forEach(form.$error, function(value, name) { 21615 form.$setValidity(name, null, control); 21616 }); 21617 forEach(form.$$success, function(value, name) { 21618 form.$setValidity(name, null, control); 21619 }); 21620 21621 arrayRemove(controls, control); 21622 control.$$parentForm = nullFormCtrl; 21623 }; 21624 21625 21626 /** 21627 * @ngdoc method 21628 * @name form.FormController#$setValidity 21629 * 21630 * @description 21631 * Sets the validity of a form control. 21632 * 21633 * This method will also propagate to parent forms. 21634 */ 21635 addSetValidityMethod({ 21636 ctrl: this, 21637 $element: element, 21638 set: function(object, property, controller) { 21639 var list = object[property]; 21640 if (!list) { 21641 object[property] = [controller]; 21642 } else { 21643 var index = list.indexOf(controller); 21644 if (index === -1) { 21645 list.push(controller); 21646 } 21647 } 21648 }, 21649 unset: function(object, property, controller) { 21650 var list = object[property]; 21651 if (!list) { 21652 return; 21653 } 21654 arrayRemove(list, controller); 21655 if (list.length === 0) { 21656 delete object[property]; 21657 } 21658 }, 21659 $animate: $animate 21660 }); 21661 21662 /** 21663 * @ngdoc method 21664 * @name form.FormController#$setDirty 21665 * 21666 * @description 21667 * Sets the form to a dirty state. 21668 * 21669 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 21670 * state (ng-dirty class). This method will also propagate to parent forms. 21671 */ 21672 form.$setDirty = function() { 21673 $animate.removeClass(element, PRISTINE_CLASS); 21674 $animate.addClass(element, DIRTY_CLASS); 21675 form.$dirty = true; 21676 form.$pristine = false; 21677 form.$$parentForm.$setDirty(); 21678 }; 21679 21680 /** 21681 * @ngdoc method 21682 * @name form.FormController#$setPristine 21683 * 21684 * @description 21685 * Sets the form to its pristine state. 21686 * 21687 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 21688 * state (ng-pristine class). This method will also propagate to all the controls contained 21689 * in this form. 21690 * 21691 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 21692 * saving or resetting it. 21693 */ 21694 form.$setPristine = function() { 21695 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 21696 form.$dirty = false; 21697 form.$pristine = true; 21698 form.$submitted = false; 21699 forEach(controls, function(control) { 21700 control.$setPristine(); 21701 }); 21702 }; 21703 21704 /** 21705 * @ngdoc method 21706 * @name form.FormController#$setUntouched 21707 * 21708 * @description 21709 * Sets the form to its untouched state. 21710 * 21711 * This method can be called to remove the 'ng-touched' class and set the form controls to their 21712 * untouched state (ng-untouched class). 21713 * 21714 * Setting a form controls back to their untouched state is often useful when setting the form 21715 * back to its pristine state. 21716 */ 21717 form.$setUntouched = function() { 21718 forEach(controls, function(control) { 21719 control.$setUntouched(); 21720 }); 21721 }; 21722 21723 /** 21724 * @ngdoc method 21725 * @name form.FormController#$setSubmitted 21726 * 21727 * @description 21728 * Sets the form to its submitted state. 21729 */ 21730 form.$setSubmitted = function() {
21731 $animate.addClass(element, SUBMITTED_CLASS); 21732 form.$submitted = true; 21733 form.$$parentForm.$setSubmitted(); 21734 }; 21735} 21736 21737/** 21738 * @ngdoc directive 21739 * @name ngForm 21740 * @restrict EAC 21741 * 21742 * @description 21743 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 21744 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 21745 * sub-group of controls needs to be determined. 21746 * 21747 * Note: the purpose of `ngForm` is to group controls, 21748 * but not to be a replacement for the `<form>` tag with all of its capabilities 21749 * (e.g. posting to the server, ...). 21750 * 21751 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 21752 * related scope, under this name. 21753 * 21754 */ 21755 21756 /** 21757 * @ngdoc directive 21758 * @name form 21759 * @restrict E 21760 * 21761 * @description 21762 * Directive that instantiates 21763 * {@link form.FormController FormController}. 21764 * 21765 * If the `name` attribute is specified, the form controller is published onto the current scope under 21766 * this name. 21767 * 21768 * # Alias: {@link ng.directive:ngForm `ngForm`} 21769 * 21770 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 21771 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 21772 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 21773 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 21774 * of controls needs to be determined. 21775 * 21776 * # CSS classes 21777 * - `ng-valid` is set if the form is valid. 21778 * - `ng-invalid` is set if the form is invalid. 21779 * - `ng-pending` is set if the form is pending. 21780 * - `ng-pristine` is set if the form is pristine. 21781 * - `ng-dirty` is set if the form is dirty. 21782 * - `ng-submitted` is set if the form was submitted. 21783 * 21784 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 21785 * 21786 * 21787 * # Submitting a form and preventing the default action 21788 * 21789 * Since the role of forms in client-side Angular applications is different than in classical 21790 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 21791 * page reload that sends the data to the server. Instead some javascript logic should be triggered 21792 * to handle the form submission in an application-specific way. 21793 * 21794 * For this reason, Angular prevents the default action (form submission to the server) unless the 21795 * `<form>` element has an `action` attribute specified. 21796 * 21797 * You can use one of the following two ways to specify what javascript method should be called when 21798 * a form is submitted: 21799 * 21800 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 21801 * - {@link ng.directive:ngClick ngClick} directive on the first 21802 * button or input field of type submit (input[type=submit]) 21803 * 21804 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 21805 * or {@link ng.directive:ngClick ngClick} directives. 21806 * This is because of the following form submission rules in the HTML specification: 21807 * 21808 * - If a form has only one input field then hitting enter in this field triggers form submit 21809 * (`ngSubmit`) 21810 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 21811 * doesn't trigger submit 21812 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 21813 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 21814 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 21815 * 21816 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 21817 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 21818 * to have access to the updated model. 21819 * 21820 * ## Animation Hooks 21821 * 21822 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 21823 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 21824 * other validations that are performed within the form. Animations in ngForm are similar to how 21825 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 21826 * as JS animations. 21827 * 21828 * The following example shows a simple way to utilize CSS transitions to style a form element
21829 * that has been rendered as invalid after it has been validated: 21830 * 21831 * <pre> 21832 * //be sure to include ngAnimate as a module to hook into more 21833 * //advanced animations 21834 * .my-form { 21835 * transition:0.5s linear all; 21836 * background: white; 21837 * } 21838 * .my-form.ng-invalid { 21839 * background: red; 21840 * color:white; 21841 * } 21842 * </pre> 21843 * 21844 * @example 21845 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 21846 <file name="index.html"> 21847 <script> 21848 angular.module('formExample', []) 21849 .controller('FormController', ['$scope', function($scope) { 21850 $scope.userType = 'guest'; 21851 }]); 21852 </script> 21853 <style> 21854 .my-form { 21855 transition:all linear 0.5s; 21856 background: transparent; 21857 } 21858 .my-form.ng-invalid { 21859 background: red; 21860 } 21861 </style> 21862 <form name="myForm" ng-controller="FormController" class="my-form"> 21863 userType: <input name="input" ng-model="userType" required> 21864 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 21865 <code>userType = {{userType}}</code><br> 21866 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 21867 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 21868 <code>myForm.$valid = {{myForm.$valid}}</code><br> 21869 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 21870 </form> 21871 </file> 21872 <file name="protractor.js" type="protractor"> 21873 it('should initialize to model', function() { 21874 var userType = element(by.binding('userType')); 21875 var valid = element(by.binding('myForm.input.$valid')); 21876 21877 expect(userType.getText()).toContain('guest'); 21878 expect(valid.getText()).toContain('true'); 21879 }); 21880 21881 it('should be invalid if empty', function() { 21882 var userType = element(by.binding('userType')); 21883 var valid = element(by.binding('myForm.input.$valid')); 21884 var userInput = element(by.model('userType')); 21885 21886 userInput.clear(); 21887 userInput.sendKeys(''); 21888 21889 expect(userType.getText()).toEqual('userType ='); 21890 expect(valid.getText()).toContain('false'); 21891 }); 21892 </file> 21893 </example> 21894 * 21895 * @param {string=} name Name of the form. If specified, the form controller will be published into 21896 * related scope, under this name. 21897 */ 21898var formDirectiveFactory = function(isNgForm) { 21899 return ['$timeout', '$parse', function($timeout, $parse) { 21900 var formDirective = { 21901 name: 'form', 21902 restrict: isNgForm ? 'EAC' : 'E', 21903 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 21904 controller: FormController, 21905 compile: function ngFormCompile(formElement, attr) { 21906 // Setup initial state of the control 21907 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 21908 21909 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 21910 21911 return { 21912 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 21913 var controller = ctrls[0]; 21914 21915 // if `action` attr is not present on the form, prevent the default action (submission) 21916 if (!('action' in attr)) { 21917 // we can't use jq events because if a form is destroyed during submission the default 21918 // action is not prevented. see #1238 21919 // 21920 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 21921 // page reload if the form was destroyed by submission of the form via a click handler 21922 // on a button in the form. Looks like an IE9 specific bug. 21923 var handleFormSubmission = function(event) { 21924 scope.$apply(function() { 21925 controller.$commitViewValue(); 21926 controller.$setSubmitted(); 21927 }); 21928 21929 event.preventDefault(); 21930 }; 21931 21932 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 21933 21934 // unregister the preventDefault listener so that we don't not leak memory but in a 21935 // way that will achieve the prevention of the default action. 21936 formElement.on('$destroy', function() { 21937 $timeout(function() { 21938 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 21939 }, 0, false); 21940 }); 21941 } 21942 21943 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 21944 parentFormCtrl.$addControl(controller); 21945 21946 var setter = nameAttr ? getSetter(controller.$name) : noop; 21947 21948 if (nameAttr) { 21949 setter(scope, controller); 21950 attr.$observe(nameAttr, function(newValue) { 21951 if (controller.$name === newValue) return; 21952 setter(scope, undefined); 21953 controller.$$parentForm.$$renameControl(controller, newValue); 21954 setter = getSetter(controller.$name); 21955 setter(scope, controller); 21956 }); 21957 } 21958 formElement.on('$destroy', function() { 21959 controller.$$parentForm.$removeControl(controller); 21960 setter(scope, undefined); 21961 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 21962 }); 21963 } 21964 }; 21965 } 21966 }; 21967 21968 return formDirective; 21969 21970 function getSetter(expression) { 21971 if (expression === '') { 21972 //create an assignable expression, so forms with an empty name can be renamed later 21973 return $parse('this[""]').assign; 21974 } 21975 return $parse(expression).assign || noop; 21976 } 21977 }]; 21978}; 21979 21980var formDirective = formDirectiveFactory(); 21981var ngFormDirective = formDirectiveFactory(true); 21982 21983/* global VALID_CLASS: false, 21984 INVALID_CLASS: false, 21985 PRISTINE_CLASS: false, 21986 DIRTY_CLASS: false, 21987 UNTOUCHED_CLASS: false, 21988 TOUCHED_CLASS: false, 21989 ngModelMinErr: false, 21990*/ 21991 21992// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 21993var 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)$/;
21994// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 21995// Note: We are being more lenient, because browsers are too. 21996// 1. Scheme 21997// 2. Slashes 21998// 3. Username 21999// 4. Password 22000// 5. Hostname 22001// 6. Port 22002// 7. Path 22003// 8. Query 22004// 9. Fragment 22005// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999 22006var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 22007var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 22008var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 22009var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; 22010var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 22011var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; 22012var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; 22013var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 22014 22015var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; 22016var PARTIAL_VALIDATION_TYPES = createMap(); 22017forEach('date,datetime-local,month,time,week'.split(','), function(type) { 22018 PARTIAL_VALIDATION_TYPES[type] = true; 22019}); 22020 22021var inputType = { 22022 22023 /** 22024 * @ngdoc input 22025 * @name input[text] 22026 * 22027 * @description 22028 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 22029 * 22030 * 22031 * @param {string} ngModel Assignable angular expression to data-bind to. 22032 * @param {string=} name Property name of the form under which the control is published. 22033 * @param {string=} required Adds `required` validation error key if the value is not entered. 22034 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22035 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22036 * `required` when you want to data-bind to the `required` attribute. 22037 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22038 * minlength. 22039 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22040 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22041 * any length. 22042 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22043 * that contains the regular expression body that will be converted to a regular expression 22044 * as in the ngPattern directive. 22045 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22046 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22047 * If the expression evaluates to a RegExp object, then this is used directly. 22048 * If the expression evaluates to a string, then it will be converted to a RegExp 22049 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22050 * `new RegExp('^abc$')`.<br /> 22051 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22052 * start at the index of the last search's match, thus not taking the whole input value into 22053 * account. 22054 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22055 * interaction with the input element. 22056 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22057 * This parameter is ignored for input[type=password] controls, which will never trim the 22058 * input. 22059 * 22060 * @example 22061 <example name="text-input-directive" module="textInputExample"> 22062 <file name="index.html"> 22063 <script> 22064 angular.module('textInputExample', []) 22065 .controller('ExampleController', ['$scope', function($scope) { 22066 $scope.example = { 22067 text: 'guest', 22068 word: /^\s*\w*\s*$/ 22069 }; 22070 }]); 22071 </script>
22072 <form name="myForm" ng-controller="ExampleController"> 22073 <label>Single word: 22074 <input type="text" name="input" ng-model="example.text" 22075 ng-pattern="example.word" required ng-trim="false"> 22076 </label> 22077 <div role="alert"> 22078 <span class="error" ng-show="myForm.input.$error.required"> 22079 Required!</span> 22080 <span class="error" ng-show="myForm.input.$error.pattern"> 22081 Single word only!</span> 22082 </div> 22083 <tt>text = {{example.text}}</tt><br/> 22084 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22085 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22086 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22087 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22088 </form> 22089 </file> 22090 <file name="protractor.js" type="protractor"> 22091 var text = element(by.binding('example.text')); 22092 var valid = element(by.binding('myForm.input.$valid')); 22093 var input = element(by.model('example.text')); 22094 22095 it('should initialize to model', function() { 22096 expect(text.getText()).toContain('guest'); 22097 expect(valid.getText()).toContain('true'); 22098 }); 22099 22100 it('should be invalid if empty', function() { 22101 input.clear(); 22102 input.sendKeys(''); 22103 22104 expect(text.getText()).toEqual('text ='); 22105 expect(valid.getText()).toContain('false'); 22106 }); 22107 22108 it('should be invalid if multi word', function() { 22109 input.clear(); 22110 input.sendKeys('hello world'); 22111 22112 expect(valid.getText()).toContain('false'); 22113 }); 22114 </file> 22115 </example> 22116 */ 22117 'text': textInputType, 22118 22119 /** 22120 * @ngdoc input 22121 * @name input[date] 22122 * 22123 * @description 22124 * Input with date validation and transformation. In browsers that do not yet support 22125 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 22126 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 22127 * modern browsers do not yet support this input type, it is important to provide cues to users on the 22128 * expected input format via a placeholder or label. 22129 * 22130 * The model must always be a Date object, otherwise Angular will throw an error. 22131 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 22132 * 22133 * The timezone to be used to read/write the `Date` instance in the model can be defined using 22134 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 22135 * 22136 * @param {string} ngModel Assignable angular expression to data-bind to. 22137 * @param {string=} name Property name of the form under which the control is published. 22138 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 22139 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 22140 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 22141 * constraint validation. 22142 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 22143 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 22144 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 22145 * constraint validation. 22146 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 22147 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 22148 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 22149 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 22150 * @param {string=} required Sets `required` validation error key if the value is not entered. 22151 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22152 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22153 * `required` when you want to data-bind to the `required` attribute. 22154 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22155 * interaction with the input element. 22156 * 22157 * @example 22158 <example name="date-input-directive" module="dateInputExample"> 22159 <file name="index.html"> 22160 <script> 22161 angular.module('dateInputExample', []) 22162 .controller('DateController', ['$scope', function($scope) { 22163 $scope.example = { 22164 value: new Date(2013, 9, 22) 22165 }; 22166 }]); 22167 </script> 22168 <form name="myForm" ng-controller="DateController as dateCtrl"> 22169 <label for="exampleInput">Pick a date in 2013:</label> 22170 <input type="date" id="exampleInput" name="input" ng-model="example.value" 22171 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 22172 <div role="alert">
22173 <span class="error" ng-show="myForm.input.$error.required"> 22174 Required!</span> 22175 <span class="error" ng-show="myForm.input.$error.date"> 22176 Not a valid date!</span> 22177 </div> 22178 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 22179 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22180 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22181 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22182 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22183 </form> 22184 </file> 22185 <file name="protractor.js" type="protractor"> 22186 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 22187 var valid = element(by.binding('myForm.input.$valid')); 22188 var input = element(by.model('example.value')); 22189 22190 // currently protractor/webdriver does not support 22191 // sending keys to all known HTML5 input controls 22192 // for various browsers (see https://github.com/angular/protractor/issues/562). 22193 function setInput(val) { 22194 // set the value of the element and force validation. 22195 var scr = "var ipt = document.getElementById('exampleInput'); " + 22196 "ipt.value = '" + val + "';" + 22197 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 22198 browser.executeScript(scr); 22199 } 22200 22201 it('should initialize to model', function() { 22202 expect(value.getText()).toContain('2013-10-22'); 22203 expect(valid.getText()).toContain('myForm.input.$valid = true'); 22204 }); 22205 22206 it('should be invalid if empty', function() { 22207 setInput(''); 22208 expect(value.getText()).toEqual('value ='); 22209 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22210 }); 22211 22212 it('should be invalid if over max', function() { 22213 setInput('2015-01-01'); 22214 expect(value.getText()).toContain(''); 22215 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22216 }); 22217 </file> 22218 </example> 22219 */ 22220 'date': createDateInputType('date', DATE_REGEXP, 22221 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 22222 'yyyy-MM-dd'), 22223 22224 /** 22225 * @ngdoc input 22226 * @name input[datetime-local] 22227 * 22228 * @description 22229 * Input with datetime validation and transformation. In browsers that do not yet support 22230 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 22231 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 22232 * 22233 * The model must always be a Date object, otherwise Angular will throw an error. 22234 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 22235 * 22236 * The timezone to be used to read/write the `Date` instance in the model can be defined using 22237 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 22238 * 22239 * @param {string} ngModel Assignable angular expression to data-bind to. 22240 * @param {string=} name Property name of the form under which the control is published. 22241 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 22242 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 22243 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 22244 * Note that `min` will also add native HTML5 constraint validation. 22245 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 22246 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 22247 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 22248 * Note that `max` will also add native HTML5 constraint validation. 22249 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 22250 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 22251 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 22252 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 22253 * @param {string=} required Sets `required` validation error key if the value is not entered. 22254 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22255 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22256 * `required` when you want to data-bind to the `required` attribute. 22257 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22258 * interaction with the input element. 22259 * 22260 * @example 22261 <example name="datetimelocal-input-directive" module="dateExample"> 22262 <file name="index.html"> 22263 <script> 22264 angular.module('dateExample', []) 22265 .controller('DateController', ['$scope', function($scope) { 22266 $scope.example = { 22267 value: new Date(2010, 11, 28, 14, 57) 22268 }; 22269 }]); 22270 </script> 22271 <form name="myForm" ng-controller="DateController as dateCtrl"> 22272 <label for="exampleInput">Pick a date between in 2013:</label> 22273 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 22274 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 22275 <div role="alert">
22276 <span class="error" ng-show="myForm.input.$error.required"> 22277 Required!</span> 22278 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 22279 Not a valid date!</span> 22280 </div> 22281 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 22282 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22283 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22284 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22285 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22286 </form> 22287 </file> 22288 <file name="protractor.js" type="protractor"> 22289 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 22290 var valid = element(by.binding('myForm.input.$valid')); 22291 var input = element(by.model('example.value')); 22292 22293 // currently protractor/webdriver does not support 22294 // sending keys to all known HTML5 input controls 22295 // for various browsers (https://github.com/angular/protractor/issues/562). 22296 function setInput(val) { 22297 // set the value of the element and force validation. 22298 var scr = "var ipt = document.getElementById('exampleInput'); " + 22299 "ipt.value = '" + val + "';" + 22300 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 22301 browser.executeScript(scr); 22302 } 22303 22304 it('should initialize to model', function() { 22305 expect(value.getText()).toContain('2010-12-28T14:57:00'); 22306 expect(valid.getText()).toContain('myForm.input.$valid = true'); 22307 }); 22308 22309 it('should be invalid if empty', function() { 22310 setInput(''); 22311 expect(value.getText()).toEqual('value ='); 22312 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22313 }); 22314 22315 it('should be invalid if over max', function() { 22316 setInput('2015-01-01T23:59:00'); 22317 expect(value.getText()).toContain(''); 22318 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22319 }); 22320 </file> 22321 </example> 22322 */ 22323 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 22324 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 22325 'yyyy-MM-ddTHH:mm:ss.sss'), 22326 22327 /** 22328 * @ngdoc input 22329 * @name input[time] 22330 * 22331 * @description 22332 * Input with time validation and transformation. In browsers that do not yet support 22333 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 22334 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 22335 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 22336 * 22337 * The model must always be a Date object, otherwise Angular will throw an error. 22338 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 22339 * 22340 * The timezone to be used to read/write the `Date` instance in the model can be defined using 22341 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 22342 * 22343 * @param {string} ngModel Assignable angular expression to data-bind to. 22344 * @param {string=} name Property name of the form under which the control is published. 22345 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 22346 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 22347 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 22348 * native HTML5 constraint validation. 22349 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 22350 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 22351 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 22352 * native HTML5 constraint validation. 22353 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 22354 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 22355 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 22356 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 22357 * @param {string=} required Sets `required` validation error key if the value is not entered. 22358 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22359 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22360 * `required` when you want to data-bind to the `required` attribute. 22361 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22362 * interaction with the input element. 22363 * 22364 * @example 22365 <example name="time-input-directive" module="timeExample"> 22366 <file name="index.html"> 22367 <script> 22368 angular.module('timeExample', []) 22369 .controller('DateController', ['$scope', function($scope) { 22370 $scope.example = { 22371 value: new Date(1970, 0, 1, 14, 57, 0) 22372 }; 22373 }]); 22374 </script> 22375 <form name="myForm" ng-controller="DateController as dateCtrl"> 22376 <label for="exampleInput">Pick a time between 8am and 5pm:</label> 22377 <input type="time" id="exampleInput" name="input" ng-model="example.value" 22378 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 22379 <div role="alert">
22380 <span class="error" ng-show="myForm.input.$error.required"> 22381 Required!</span> 22382 <span class="error" ng-show="myForm.input.$error.time"> 22383 Not a valid date!</span> 22384 </div> 22385 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 22386 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22387 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22388 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22389 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22390 </form> 22391 </file> 22392 <file name="protractor.js" type="protractor"> 22393 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 22394 var valid = element(by.binding('myForm.input.$valid')); 22395 var input = element(by.model('example.value')); 22396 22397 // currently protractor/webdriver does not support 22398 // sending keys to all known HTML5 input controls 22399 // for various browsers (https://github.com/angular/protractor/issues/562). 22400 function setInput(val) { 22401 // set the value of the element and force validation. 22402 var scr = "var ipt = document.getElementById('exampleInput'); " + 22403 "ipt.value = '" + val + "';" + 22404 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 22405 browser.executeScript(scr); 22406 } 22407 22408 it('should initialize to model', function() { 22409 expect(value.getText()).toContain('14:57:00'); 22410 expect(valid.getText()).toContain('myForm.input.$valid = true'); 22411 }); 22412 22413 it('should be invalid if empty', function() { 22414 setInput(''); 22415 expect(value.getText()).toEqual('value ='); 22416 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22417 }); 22418 22419 it('should be invalid if over max', function() { 22420 setInput('23:59:00'); 22421 expect(value.getText()).toContain(''); 22422 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22423 }); 22424 </file> 22425 </example> 22426 */ 22427 'time': createDateInputType('time', TIME_REGEXP, 22428 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 22429 'HH:mm:ss.sss'), 22430 22431 /** 22432 * @ngdoc input 22433 * @name input[week] 22434 * 22435 * @description 22436 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 22437 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 22438 * week format (yyyy-W##), for example: `2013-W02`. 22439 * 22440 * The model must always be a Date object, otherwise Angular will throw an error. 22441 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 22442 * 22443 * The timezone to be used to read/write the `Date` instance in the model can be defined using 22444 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 22445 * 22446 * @param {string} ngModel Assignable angular expression to data-bind to. 22447 * @param {string=} name Property name of the form under which the control is published. 22448 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 22449 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 22450 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 22451 * native HTML5 constraint validation. 22452 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 22453 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 22454 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 22455 * native HTML5 constraint validation. 22456 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 22457 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 22458 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 22459 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 22460 * @param {string=} required Sets `required` validation error key if the value is not entered. 22461 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22462 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22463 * `required` when you want to data-bind to the `required` attribute. 22464 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22465 * interaction with the input element. 22466 * 22467 * @example 22468 <example name="week-input-directive" module="weekExample"> 22469 <file name="index.html"> 22470 <script> 22471 angular.module('weekExample', []) 22472 .controller('DateController', ['$scope', function($scope) { 22473 $scope.example = { 22474 value: new Date(2013, 0, 3) 22475 }; 22476 }]); 22477 </script> 22478 <form name="myForm" ng-controller="DateController as dateCtrl"> 22479 <label>Pick a date between in 2013: 22480 <input id="exampleInput" type="week" name="input" ng-model="example.value" 22481 placeholder="YYYY-W##" min="2012-W32" 22482 max="2013-W52" required /> 22483 </label> 22484 <div role="alert">
22485 <span class="error" ng-show="myForm.input.$error.required"> 22486 Required!</span> 22487 <span class="error" ng-show="myForm.input.$error.week"> 22488 Not a valid date!</span> 22489 </div> 22490 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 22491 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22492 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22493 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22494 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22495 </form> 22496 </file> 22497 <file name="protractor.js" type="protractor"> 22498 var value = element(by.binding('example.value | date: "yyyy-Www"')); 22499 var valid = element(by.binding('myForm.input.$valid')); 22500 var input = element(by.model('example.value')); 22501 22502 // currently protractor/webdriver does not support 22503 // sending keys to all known HTML5 input controls 22504 // for various browsers (https://github.com/angular/protractor/issues/562). 22505 function setInput(val) { 22506 // set the value of the element and force validation. 22507 var scr = "var ipt = document.getElementById('exampleInput'); " + 22508 "ipt.value = '" + val + "';" + 22509 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 22510 browser.executeScript(scr); 22511 } 22512 22513 it('should initialize to model', function() { 22514 expect(value.getText()).toContain('2013-W01'); 22515 expect(valid.getText()).toContain('myForm.input.$valid = true'); 22516 }); 22517 22518 it('should be invalid if empty', function() { 22519 setInput(''); 22520 expect(value.getText()).toEqual('value ='); 22521 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22522 }); 22523 22524 it('should be invalid if over max', function() { 22525 setInput('2015-W01'); 22526 expect(value.getText()).toContain(''); 22527 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22528 }); 22529 </file> 22530 </example> 22531 */ 22532 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 22533 22534 /** 22535 * @ngdoc input 22536 * @name input[month] 22537 * 22538 * @description 22539 * Input with month validation and transformation. In browsers that do not yet support 22540 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 22541 * month format (yyyy-MM), for example: `2009-01`. 22542 * 22543 * The model must always be a Date object, otherwise Angular will throw an error. 22544 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 22545 * If the model is not set to the first of the month, the next view to model update will set it 22546 * to the first of the month. 22547 * 22548 * The timezone to be used to read/write the `Date` instance in the model can be defined using 22549 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 22550 * 22551 * @param {string} ngModel Assignable angular expression to data-bind to. 22552 * @param {string=} name Property name of the form under which the control is published. 22553 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 22554 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 22555 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 22556 * native HTML5 constraint validation. 22557 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 22558 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 22559 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 22560 * native HTML5 constraint validation. 22561 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 22562 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 22563 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 22564 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 22565 22566 * @param {string=} required Sets `required` validation error key if the value is not entered. 22567 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22568 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22569 * `required` when you want to data-bind to the `required` attribute. 22570 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22571 * interaction with the input element. 22572 * 22573 * @example 22574 <example name="month-input-directive" module="monthExample"> 22575 <file name="index.html"> 22576 <script> 22577 angular.module('monthExample', []) 22578 .controller('DateController', ['$scope', function($scope) { 22579 $scope.example = { 22580 value: new Date(2013, 9, 1) 22581 }; 22582 }]); 22583 </script> 22584 <form name="myForm" ng-controller="DateController as dateCtrl"> 22585 <label for="exampleInput">Pick a month in 2013:</label> 22586 <input id="exampleInput" type="month" name="input" ng-model="example.value" 22587 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 22588 <div role="alert">
22589 <span class="error" ng-show="myForm.input.$error.required"> 22590 Required!</span> 22591 <span class="error" ng-show="myForm.input.$error.month"> 22592 Not a valid month!</span> 22593 </div> 22594 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 22595 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22596 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22597 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22598 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22599 </form> 22600 </file> 22601 <file name="protractor.js" type="protractor"> 22602 var value = element(by.binding('example.value | date: "yyyy-MM"')); 22603 var valid = element(by.binding('myForm.input.$valid')); 22604 var input = element(by.model('example.value')); 22605 22606 // currently protractor/webdriver does not support 22607 // sending keys to all known HTML5 input controls 22608 // for various browsers (https://github.com/angular/protractor/issues/562). 22609 function setInput(val) { 22610 // set the value of the element and force validation. 22611 var scr = "var ipt = document.getElementById('exampleInput'); " + 22612 "ipt.value = '" + val + "';" + 22613 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 22614 browser.executeScript(scr); 22615 } 22616 22617 it('should initialize to model', function() { 22618 expect(value.getText()).toContain('2013-10'); 22619 expect(valid.getText()).toContain('myForm.input.$valid = true'); 22620 }); 22621 22622 it('should be invalid if empty', function() { 22623 setInput(''); 22624 expect(value.getText()).toEqual('value ='); 22625 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22626 }); 22627 22628 it('should be invalid if over max', function() { 22629 setInput('2015-01'); 22630 expect(value.getText()).toContain(''); 22631 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22632 }); 22633 </file> 22634 </example> 22635 */ 22636 'month': createDateInputType('month', MONTH_REGEXP, 22637 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 22638 'yyyy-MM'), 22639 22640 /** 22641 * @ngdoc input 22642 * @name input[number] 22643 * 22644 * @description 22645 * Text input with number validation and transformation. Sets the `number` validation 22646 * error if not a valid number. 22647 * 22648 * <div class="alert alert-warning"> 22649 * The model must always be of type `number` otherwise Angular will throw an error. 22650 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 22651 * error docs for more information and an example of how to convert your model if necessary. 22652 * </div> 22653 * 22654 * ## Issues with HTML5 constraint validation 22655 * 22656 * In browsers that follow the 22657 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 22658 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 22659 * If a non-number is entered in the input, the browser will report the value as an empty string, 22660 * which means the view / model values in `ngModel` and subsequently the scope value 22661 * will also be an empty string. 22662 * 22663 * 22664 * @param {string} ngModel Assignable angular expression to data-bind to. 22665 * @param {string=} name Property name of the form under which the control is published. 22666 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 22667 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 22668 * @param {string=} required Sets `required` validation error key if the value is not entered. 22669 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22670 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22671 * `required` when you want to data-bind to the `required` attribute. 22672 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22673 * minlength. 22674 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22675 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22676 * any length. 22677 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22678 * that contains the regular expression body that will be converted to a regular expression 22679 * as in the ngPattern directive. 22680 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22681 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22682 * If the expression evaluates to a RegExp object, then this is used directly. 22683 * If the expression evaluates to a string, then it will be converted to a RegExp 22684 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22685 * `new RegExp('^abc$')`.<br /> 22686 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22687 * start at the index of the last search's match, thus not taking the whole input value into 22688 * account. 22689 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22690 * interaction with the input element. 22691 * 22692 * @example 22693 <example name="number-input-directive" module="numberExample"> 22694 <file name="index.html"> 22695 <script> 22696 angular.module('numberExample', [])
22697 .controller('ExampleController', ['$scope', function($scope) { 22698 $scope.example = { 22699 value: 12 22700 }; 22701 }]); 22702 </script> 22703 <form name="myForm" ng-controller="ExampleController"> 22704 <label>Number: 22705 <input type="number" name="input" ng-model="example.value" 22706 min="0" max="99" required> 22707 </label> 22708 <div role="alert"> 22709 <span class="error" ng-show="myForm.input.$error.required"> 22710 Required!</span> 22711 <span class="error" ng-show="myForm.input.$error.number"> 22712 Not valid number!</span> 22713 </div> 22714 <tt>value = {{example.value}}</tt><br/> 22715 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22716 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22717 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22718 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22719 </form> 22720 </file> 22721 <file name="protractor.js" type="protractor"> 22722 var value = element(by.binding('example.value')); 22723 var valid = element(by.binding('myForm.input.$valid')); 22724 var input = element(by.model('example.value')); 22725 22726 it('should initialize to model', function() { 22727 expect(value.getText()).toContain('12'); 22728 expect(valid.getText()).toContain('true'); 22729 }); 22730 22731 it('should be invalid if empty', function() { 22732 input.clear(); 22733 input.sendKeys(''); 22734 expect(value.getText()).toEqual('value ='); 22735 expect(valid.getText()).toContain('false'); 22736 }); 22737 22738 it('should be invalid if over max', function() { 22739 input.clear(); 22740 input.sendKeys('123'); 22741 expect(value.getText()).toEqual('value ='); 22742 expect(valid.getText()).toContain('false'); 22743 }); 22744 </file> 22745 </example> 22746 */ 22747 'number': numberInputType, 22748 22749 22750 /** 22751 * @ngdoc input 22752 * @name input[url] 22753 * 22754 * @description 22755 * Text input with URL validation. Sets the `url` validation error key if the content is not a 22756 * valid URL. 22757 * 22758 * <div class="alert alert-warning"> 22759 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 22760 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 22761 * the built-in validators (see the {@link guide/forms Forms guide}) 22762 * </div> 22763 * 22764 * @param {string} ngModel Assignable angular expression to data-bind to. 22765 * @param {string=} name Property name of the form under which the control is published. 22766 * @param {string=} required Sets `required` validation error key if the value is not entered. 22767 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22768 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22769 * `required` when you want to data-bind to the `required` attribute. 22770 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22771 * minlength. 22772 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22773 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22774 * any length. 22775 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22776 * that contains the regular expression body that will be converted to a regular expression 22777 * as in the ngPattern directive. 22778 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22779 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22780 * If the expression evaluates to a RegExp object, then this is used directly. 22781 * If the expression evaluates to a string, then it will be converted to a RegExp 22782 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22783 * `new RegExp('^abc$')`.<br /> 22784 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22785 * start at the index of the last search's match, thus not taking the whole input value into 22786 * account. 22787 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22788 * interaction with the input element. 22789 * 22790 * @example 22791 <example name="url-input-directive" module="urlExample"> 22792 <file name="index.html"> 22793 <script> 22794 angular.module('urlExample', [])
22795 .controller('ExampleController', ['$scope', function($scope) { 22796 $scope.url = { 22797 text: 'http://google.com' 22798 }; 22799 }]); 22800 </script> 22801 <form name="myForm" ng-controller="ExampleController"> 22802 <label>URL: 22803 <input type="url" name="input" ng-model="url.text" required> 22804 <label> 22805 <div role="alert"> 22806 <span class="error" ng-show="myForm.input.$error.required"> 22807 Required!</span> 22808 <span class="error" ng-show="myForm.input.$error.url"> 22809 Not valid url!</span> 22810 </div> 22811 <tt>text = {{url.text}}</tt><br/> 22812 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22813 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22814 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22815 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22816 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 22817 </form> 22818 </file> 22819 <file name="protractor.js" type="protractor"> 22820 var text = element(by.binding('url.text')); 22821 var valid = element(by.binding('myForm.input.$valid')); 22822 var input = element(by.model('url.text')); 22823 22824 it('should initialize to model', function() { 22825 expect(text.getText()).toContain('http://google.com'); 22826 expect(valid.getText()).toContain('true'); 22827 }); 22828 22829 it('should be invalid if empty', function() { 22830 input.clear(); 22831 input.sendKeys(''); 22832 22833 expect(text.getText()).toEqual('text ='); 22834 expect(valid.getText()).toContain('false'); 22835 }); 22836 22837 it('should be invalid if not url', function() { 22838 input.clear(); 22839 input.sendKeys('box'); 22840 22841 expect(valid.getText()).toContain('false'); 22842 }); 22843 </file> 22844 </example> 22845 */ 22846 'url': urlInputType, 22847 22848 22849 /** 22850 * @ngdoc input 22851 * @name input[email] 22852 * 22853 * @description 22854 * Text input with email validation. Sets the `email` validation error key if not a valid email 22855 * address. 22856 * 22857 * <div class="alert alert-warning"> 22858 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 22859 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 22860 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 22861 * </div> 22862 * 22863 * @param {string} ngModel Assignable angular expression to data-bind to. 22864 * @param {string=} name Property name of the form under which the control is published. 22865 * @param {string=} required Sets `required` validation error key if the value is not entered. 22866 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22867 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22868 * `required` when you want to data-bind to the `required` attribute. 22869 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22870 * minlength. 22871 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22872 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22873 * any length. 22874 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22875 * that contains the regular expression body that will be converted to a regular expression 22876 * as in the ngPattern directive. 22877 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22878 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22879 * If the expression evaluates to a RegExp object, then this is used directly. 22880 * If the expression evaluates to a string, then it will be converted to a RegExp 22881 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22882 * `new RegExp('^abc$')`.<br /> 22883 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22884 * start at the index of the last search's match, thus not taking the whole input value into 22885 * account. 22886 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22887 * interaction with the input element. 22888 * 22889 * @example 22890 <example name="email-input-directive" module="emailExample"> 22891 <file name="index.html"> 22892 <script> 22893 angular.module('emailExample', [])
22894 .controller('ExampleController', ['$scope', function($scope) { 22895 $scope.email = { 22896 text: '[email protected]' 22897 }; 22898 }]); 22899 </script> 22900 <form name="myForm" ng-controller="ExampleController"> 22901 <label>Email: 22902 <input type="email" name="input" ng-model="email.text" required> 22903 </label> 22904 <div role="alert"> 22905 <span class="error" ng-show="myForm.input.$error.required"> 22906 Required!</span> 22907 <span class="error" ng-show="myForm.input.$error.email"> 22908 Not valid email!</span> 22909 </div> 22910 <tt>text = {{email.text}}</tt><br/> 22911 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22912 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22913 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22914 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22915 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 22916 </form> 22917 </file> 22918 <file name="protractor.js" type="protractor"> 22919 var text = element(by.binding('email.text')); 22920 var valid = element(by.binding('myForm.input.$valid')); 22921 var input = element(by.model('email.text')); 22922 22923 it('should initialize to model', function() { 22924 expect(text.getText()).toContain('[email protected]'); 22925 expect(valid.getText()).toContain('true'); 22926 }); 22927 22928 it('should be invalid if empty', function() { 22929 input.clear(); 22930 input.sendKeys(''); 22931 expect(text.getText()).toEqual('text ='); 22932 expect(valid.getText()).toContain('false'); 22933 }); 22934 22935 it('should be invalid if not email', function() { 22936 input.clear(); 22937 input.sendKeys('xxx'); 22938 22939 expect(valid.getText()).toContain('false'); 22940 }); 22941 </file> 22942 </example> 22943 */ 22944 'email': emailInputType, 22945 22946 22947 /** 22948 * @ngdoc input 22949 * @name input[radio] 22950 * 22951 * @description 22952 * HTML radio button. 22953 * 22954 * @param {string} ngModel Assignable angular expression to data-bind to. 22955 * @param {string} value The value to which the `ngModel` expression should be set when selected. 22956 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 22957 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 22958 * @param {string=} name Property name of the form under which the control is published. 22959 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22960 * interaction with the input element. 22961 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 22962 * is selected. Should be used instead of the `value` attribute if you need 22963 * a non-string `ngModel` (`boolean`, `array`, ...). 22964 * 22965 * @example 22966 <example name="radio-input-directive" module="radioExample"> 22967 <file name="index.html"> 22968 <script> 22969 angular.module('radioExample', []) 22970 .controller('ExampleController', ['$scope', function($scope) { 22971 $scope.color = { 22972 name: 'blue' 22973 }; 22974 $scope.specialValue = { 22975 "id": "12345", 22976 "value": "green" 22977 }; 22978 }]); 22979 </script>
22980 <form name="myForm" ng-controller="ExampleController"> 22981 <label> 22982 <input type="radio" ng-model="color.name" value="red"> 22983 Red 22984 </label><br/> 22985 <label> 22986 <input type="radio" ng-model="color.name" ng-value="specialValue"> 22987 Green 22988 </label><br/> 22989 <label> 22990 <input type="radio" ng-model="color.name" value="blue"> 22991 Blue 22992 </label><br/> 22993 <tt>color = {{color.name | json}}</tt><br/> 22994 </form> 22995 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 22996 </file> 22997 <file name="protractor.js" type="protractor"> 22998 it('should change state', function() { 22999 var color = element(by.binding('color.name')); 23000 23001 expect(color.getText()).toContain('blue'); 23002 23003 element.all(by.model('color.name')).get(0).click(); 23004 23005 expect(color.getText()).toContain('red'); 23006 }); 23007 </file> 23008 </example> 23009 */ 23010 'radio': radioInputType, 23011 23012 23013 /** 23014 * @ngdoc input 23015 * @name input[checkbox] 23016 * 23017 * @description 23018 * HTML checkbox. 23019 * 23020 * @param {string} ngModel Assignable angular expression to data-bind to. 23021 * @param {string=} name Property name of the form under which the control is published. 23022 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 23023 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 23024 * @param {string=} ngChange Angular expression to be executed when input changes due to user 23025 * interaction with the input element. 23026 * 23027 * @example 23028 <example name="checkbox-input-directive" module="checkboxExample"> 23029 <file name="index.html"> 23030 <script> 23031 angular.module('checkboxExample', []) 23032 .controller('ExampleController', ['$scope', function($scope) {
23033 $scope.checkboxModel = { 23034 value1 : true, 23035 value2 : 'YES' 23036 }; 23037 }]); 23038 </script> 23039 <form name="myForm" ng-controller="ExampleController"> 23040 <label>Value1: 23041 <input type="checkbox" ng-model="checkboxModel.value1"> 23042 </label><br/> 23043 <label>Value2: 23044 <input type="checkbox" ng-model="checkboxModel.value2" 23045 ng-true-value="'YES'" ng-false-value="'NO'"> 23046 </label><br/> 23047 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 23048 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 23049 </form> 23050 </file> 23051 <file name="protractor.js" type="protractor"> 23052 it('should change state', function() { 23053 var value1 = element(by.binding('checkboxModel.value1')); 23054 var value2 = element(by.binding('checkboxModel.value2')); 23055 23056 expect(value1.getText()).toContain('true'); 23057 expect(value2.getText()).toContain('YES'); 23058 23059 element(by.model('checkboxModel.value1')).click(); 23060 element(by.model('checkboxModel.value2')).click(); 23061 23062 expect(value1.getText()).toContain('false'); 23063 expect(value2.getText()).toContain('NO'); 23064 }); 23065 </file> 23066 </example> 23067 */ 23068 'checkbox': checkboxInputType, 23069 23070 'hidden': noop, 23071 'button': noop, 23072 'submit': noop, 23073 'reset': noop, 23074 'file': noop 23075}; 23076 23077function stringBasedInputType(ctrl) { 23078 ctrl.$formatters.push(function(value) { 23079 return ctrl.$isEmpty(value) ? value : value.toString(); 23080 }); 23081} 23082 23083function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 23084 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 23085 stringBasedInputType(ctrl); 23086} 23087 23088function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 23089 var type = lowercase(element[0].type); 23090 23091 // In composition mode, users are still inputing intermediate text buffer, 23092 // hold the listener until composition is done. 23093 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 23094 if (!$sniffer.android) { 23095 var composing = false; 23096 23097 element.on('compositionstart', function() { 23098 composing = true; 23099 }); 23100 23101 element.on('compositionend', function() { 23102 composing = false; 23103 listener(); 23104 }); 23105 } 23106 23107 var timeout; 23108 23109 var listener = function(ev) { 23110 if (timeout) { 23111 $browser.defer.cancel(timeout); 23112 timeout = null; 23113 } 23114 if (composing) return; 23115 var value = element.val(), 23116 event = ev && ev.type; 23117 23118 // By default we will trim the value 23119 // If the attribute ng-trim exists we will avoid trimming 23120 // If input type is 'password', the value is never trimmed 23121 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 23122 value = trim(value); 23123 } 23124 23125 // If a control is suffering from bad input (due to native validators), browsers discard its 23126 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 23127 // control's value is the same empty value twice in a row. 23128 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 23129 ctrl.$setViewValue(value, event); 23130 } 23131 }; 23132 23133 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 23134 // input event on backspace, delete or cut 23135 if ($sniffer.hasEvent('input')) { 23136 element.on('input', listener); 23137 } else { 23138 var deferListener = function(ev, input, origValue) { 23139 if (!timeout) { 23140 timeout = $browser.defer(function() { 23141 timeout = null; 23142 if (!input || input.value !== origValue) { 23143 listener(ev); 23144 } 23145 }); 23146 } 23147 }; 23148 23149 element.on('keydown', function(event) { 23150 var key = event.keyCode; 23151 23152 // ignore 23153 // command modifiers arrows 23154 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 23155 23156 deferListener(event, this, this.value); 23157 }); 23158 23159 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 23160 if ($sniffer.hasEvent('paste')) { 23161 element.on('paste cut', deferListener); 23162 } 23163 } 23164 23165 // if user paste into input using mouse on older browser 23166 // or form autocomplete on newer browser, we need "change" event to catch it 23167 element.on('change', listener); 23168 23169 // Some native input types (date-family) have the ability to change validity without 23170 // firing any input/change events. 23171 // For these event types, when native validators are present and the browser supports the type, 23172 // check for validity changes on various DOM events. 23173 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { 23174 element.on(PARTIAL_VALIDATION_EVENTS, function(ev) { 23175 if (!timeout) {
vendor: 5,456 bytes, lines 23176-23356
23176 var validity = this[VALIDITY_STATE_PROPERTY]; 23177 var origBadInput = validity.badInput; 23178 var origTypeMismatch = validity.typeMismatch; 23179 timeout = $browser.defer(function() { 23180 timeout = null; 23181 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { 23182 listener(ev); 23183 } 23184 }); 23185 } 23186 }); 23187 } 23188 23189 ctrl.$render = function() { 23190 // Workaround for Firefox validation #12102. 23191 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 23192 if (element.val() !== value) { 23193 element.val(value); 23194 } 23195 }; 23196} 23197 23198function weekParser(isoWeek, existingDate) { 23199 if (isDate(isoWeek)) { 23200 return isoWeek; 23201 } 23202 23203 if (isString(isoWeek)) { 23204 WEEK_REGEXP.lastIndex = 0; 23205 var parts = WEEK_REGEXP.exec(isoWeek); 23206 if (parts) { 23207 var year = +parts[1], 23208 week = +parts[2], 23209 hours = 0, 23210 minutes = 0, 23211 seconds = 0, 23212 milliseconds = 0, 23213 firstThurs = getFirstThursdayOfYear(year), 23214 addDays = (week - 1) * 7; 23215 23216 if (existingDate) { 23217 hours = existingDate.getHours(); 23218 minutes = existingDate.getMinutes(); 23219 seconds = existingDate.getSeconds(); 23220 milliseconds = existingDate.getMilliseconds(); 23221 } 23222 23223 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 23224 } 23225 } 23226 23227 return NaN; 23228} 23229 23230function createDateParser(regexp, mapping) { 23231 return function(iso, date) { 23232 var parts, map; 23233 23234 if (isDate(iso)) { 23235 return iso; 23236 } 23237 23238 if (isString(iso)) { 23239 // When a date is JSON'ified to wraps itself inside of an extra 23240 // set of double quotes. This makes the date parsing code unable 23241 // to match the date string and parse it as a date. 23242 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 23243 iso = iso.substring(1, iso.length - 1); 23244 } 23245 if (ISO_DATE_REGEXP.test(iso)) { 23246 return new Date(iso); 23247 } 23248 regexp.lastIndex = 0; 23249 parts = regexp.exec(iso); 23250 23251 if (parts) { 23252 parts.shift(); 23253 if (date) { 23254 map = { 23255 yyyy: date.getFullYear(), 23256 MM: date.getMonth() + 1, 23257 dd: date.getDate(), 23258 HH: date.getHours(), 23259 mm: date.getMinutes(), 23260 ss: date.getSeconds(), 23261 sss: date.getMilliseconds() / 1000 23262 }; 23263 } else { 23264 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 23265 } 23266 23267 forEach(parts, function(part, index) { 23268 if (index < mapping.length) { 23269 map[mapping[index]] = +part; 23270 } 23271 }); 23272 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 23273 } 23274 } 23275 23276 return NaN; 23277 }; 23278} 23279 23280function createDateInputType(type, regexp, parseDate, format) { 23281 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 23282 badInputChecker(scope, element, attr, ctrl); 23283 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 23284 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 23285 var previousDate; 23286 23287 ctrl.$$parserName = type; 23288 ctrl.$parsers.push(function(value) { 23289 if (ctrl.$isEmpty(value)) return null; 23290 if (regexp.test(value)) { 23291 // Note: We cannot read ctrl.$modelValue, as there might be a different 23292 // parser/formatter in the processing chain so that the model 23293 // contains some different data format! 23294 var parsedDate = parseDate(value, previousDate); 23295 if (timezone) { 23296 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 23297 } 23298 return parsedDate; 23299 } 23300 return undefined; 23301 }); 23302 23303 ctrl.$formatters.push(function(value) { 23304 if (value && !isDate(value)) { 23305 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 23306 } 23307 if (isValidDate(value)) { 23308 previousDate = value; 23309 if (previousDate && timezone) { 23310 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 23311 } 23312 return $filter('date')(value, format, timezone); 23313 } else { 23314 previousDate = null; 23315 return ''; 23316 } 23317 }); 23318 23319 if (isDefined(attr.min) || attr.ngMin) { 23320 var minVal; 23321 ctrl.$validators.min = function(value) { 23322 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 23323 }; 23324 attr.$observe('min', function(val) { 23325 minVal = parseObservedDateValue(val); 23326 ctrl.$validate(); 23327 }); 23328 } 23329 23330 if (isDefined(attr.max) || attr.ngMax) { 23331 var maxVal; 23332 ctrl.$validators.max = function(value) { 23333 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 23334 }; 23335 attr.$observe('max', function(val) { 23336 maxVal = parseObservedDateValue(val); 23337 ctrl.$validate(); 23338 }); 23339 } 23340 23341 function isValidDate(value) { 23342 // Invalid Date: getTime() returns NaN 23343 return value && !(value.getTime && value.getTime() !== value.getTime()); 23344 } 23345 23346 function parseObservedDateValue(val) { 23347 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 23348 } 23349 }; 23350} 23351 23352function badInputChecker(scope, element, attr, ctrl) { 23353 var node = element[0]; 23354 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 23355 if (nativeValidation) { 23356 ctrl.$parsers.push(function(value) {
23357 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 23358 return validity.badInput || validity.typeMismatch ? undefined : value; 23359 }); 23360 } 23361} 23362 23363function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 23364 badInputChecker(scope, element, attr, ctrl); 23365 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 23366 23367 ctrl.$$parserName = 'number'; 23368 ctrl.$parsers.push(function(value) { 23369 if (ctrl.$isEmpty(value)) return null; 23370 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 23371 return undefined; 23372 }); 23373 23374 ctrl.$formatters.push(function(value) { 23375 if (!ctrl.$isEmpty(value)) { 23376 if (!isNumber(value)) { 23377 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 23378 } 23379 value = value.toString(); 23380 } 23381 return value; 23382 }); 23383 23384 if (isDefined(attr.min) || attr.ngMin) { 23385 var minVal; 23386 ctrl.$validators.min = function(value) { 23387 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 23388 }; 23389 23390 attr.$observe('min', function(val) { 23391 if (isDefined(val) && !isNumber(val)) { 23392 val = parseFloat(val, 10); 23393 } 23394 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 23395 // TODO(matsko): implement validateLater to reduce number of validations 23396 ctrl.$validate(); 23397 }); 23398 } 23399 23400 if (isDefined(attr.max) || attr.ngMax) { 23401 var maxVal; 23402 ctrl.$validators.max = function(value) { 23403 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 23404 }; 23405 23406 attr.$observe('max', function(val) { 23407 if (isDefined(val) && !isNumber(val)) { 23408 val = parseFloat(val, 10); 23409 } 23410 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 23411 // TODO(matsko): implement validateLater to reduce number of validations 23412 ctrl.$validate(); 23413 }); 23414 } 23415} 23416 23417function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 23418 // Note: no badInputChecker here by purpose as `url` is only a validation 23419 // in browsers, i.e. we can always read out input.value even if it is not valid! 23420 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 23421 stringBasedInputType(ctrl); 23422 23423 ctrl.$$parserName = 'url'; 23424 ctrl.$validators.url = function(modelValue, viewValue) { 23425 var value = modelValue || viewValue; 23426 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 23427 }; 23428} 23429 23430function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 23431 // Note: no badInputChecker here by purpose as `url` is only a validation 23432 // in browsers, i.e. we can always read out input.value even if it is not valid! 23433 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 23434 stringBasedInputType(ctrl); 23435 23436 ctrl.$$parserName = 'email'; 23437 ctrl.$validators.email = function(modelValue, viewValue) { 23438 var value = modelValue || viewValue; 23439 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 23440 }; 23441} 23442 23443function radioInputType(scope, element, attr, ctrl) { 23444 // make the name unique, if not defined 23445 if (isUndefined(attr.name)) { 23446 element.attr('name', nextUid()); 23447 } 23448 23449 var listener = function(ev) { 23450 if (element[0].checked) { 23451 ctrl.$setViewValue(attr.value, ev && ev.type); 23452 } 23453 }; 23454 23455 element.on('click', listener); 23456 23457 ctrl.$render = function() { 23458 var value = attr.value; 23459 element[0].checked = (value == ctrl.$viewValue); 23460 }; 23461 23462 attr.$observe('value', ctrl.$render); 23463} 23464 23465function parseConstantExpr($parse, context, name, expression, fallback) { 23466 var parseFn; 23467 if (isDefined(expression)) { 23468 parseFn = $parse(expression); 23469 if (!parseFn.constant) { 23470 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 23471 '`{1}`.', name, expression); 23472 } 23473 return parseFn(context); 23474 } 23475 return fallback; 23476} 23477 23478function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 23479 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 23480 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 23481 23482 var listener = function(ev) { 23483 ctrl.$setViewValue(element[0].checked, ev && ev.type); 23484 }; 23485 23486 element.on('click', listener); 23487 23488 ctrl.$render = function() { 23489 element[0].checked = ctrl.$viewValue; 23490 }; 23491
23492 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 23493 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 23494 // it to a boolean. 23495 ctrl.$isEmpty = function(value) { 23496 return value === false; 23497 }; 23498 23499 ctrl.$formatters.push(function(value) { 23500 return equals(value, trueValue); 23501 }); 23502 23503 ctrl.$parsers.push(function(value) { 23504 return value ? trueValue : falseValue; 23505 }); 23506} 23507 23508 23509/** 23510 * @ngdoc directive 23511 * @name textarea 23512 * @restrict E 23513 * 23514 * @description 23515 * HTML textarea element control with angular data-binding. The data-binding and validation 23516 * properties of this element are exactly the same as those of the 23517 * {@link ng.directive:input input element}. 23518 * 23519 * @param {string} ngModel Assignable angular expression to data-bind to. 23520 * @param {string=} name Property name of the form under which the control is published. 23521 * @param {string=} required Sets `required` validation error key if the value is not entered. 23522 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 23523 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 23524 * `required` when you want to data-bind to the `required` attribute. 23525 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 23526 * minlength. 23527 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 23528 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 23529 * length. 23530 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 23531 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 23532 * If the expression evaluates to a RegExp object, then this is used directly. 23533 * If the expression evaluates to a string, then it will be converted to a RegExp 23534 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 23535 * `new RegExp('^abc$')`.<br /> 23536 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 23537 * start at the index of the last search's match, thus not taking the whole input value into 23538 * account. 23539 * @param {string=} ngChange Angular expression to be executed when input changes due to user 23540 * interaction with the input element. 23541 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 23542 */ 23543 23544 23545/** 23546 * @ngdoc directive 23547 * @name input 23548 * @restrict E 23549 * 23550 * @description 23551 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 23552 * input state control, and validation. 23553 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 23554 * 23555 * <div class="alert alert-warning"> 23556 * **Note:** Not every feature offered is available for all input types. 23557 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 23558 * </div> 23559 * 23560 * @param {string} ngModel Assignable angular expression to data-bind to. 23561 * @param {string=} name Property name of the form under which the control is published. 23562 * @param {string=} required Sets `required` validation error key if the value is not entered. 23563 * @param {boolean=} ngRequired Sets `required` attribute if set to true 23564 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 23565 * minlength. 23566 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 23567 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 23568 * length. 23569 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 23570 * value does not match a RegExp found by evaluating the Angular expression given in the attribute value. 23571 * If the expression evaluates to a RegExp object, then this is used directly. 23572 * If the expression evaluates to a string, then it will be converted to a RegExp 23573 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 23574 * `new RegExp('^abc$')`.<br /> 23575 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 23576 * start at the index of the last search's match, thus not taking the whole input value into 23577 * account. 23578 * @param {string=} ngChange Angular expression to be executed when input changes due to user 23579 * interaction with the input element. 23580 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 23580-23698
23580 will not automatically trim the input. 23581 * This parameter is ignored for input[type=password] controls, which will never trim the 23582 * input. 23583 * 23584 * @example 23585 <example name="input-directive" module="inputExample"> 23586 <file name="index.html"> 23587 <script> 23588 angular.module('inputExample', []) 23589 .controller('ExampleController', ['$scope', function($scope) { 23590 $scope.user = {name: 'guest', last: 'visitor'}; 23591 }]); 23592 </script> 23593 <div ng-controller="ExampleController"> 23594 <form name="myForm"> 23595 <label> 23596 User name: 23597 <input type="text" name="userName" ng-model="user.name" required> 23598 </label> 23599 <div role="alert"> 23600 <span class="error" ng-show="myForm.userName.$error.required"> 23601 Required!</span> 23602 </div> 23603 <label> 23604 Last name: 23605 <input type="text" name="lastName" ng-model="user.last" 23606 ng-minlength="3" ng-maxlength="10"> 23607 </label> 23608 <div role="alert"> 23609 <span class="error" ng-show="myForm.lastName.$error.minlength"> 23610 Too short!</span> 23611 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 23612 Too long!</span> 23613 </div> 23614 </form> 23615 <hr> 23616 <tt>user = {{user}}</tt><br/> 23617 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 23618 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 23619 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 23620 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 23621 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 23622 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 23623 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 23624 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 23625 </div> 23626 </file> 23627 <file name="protractor.js" type="protractor"> 23628 var user = element(by.exactBinding('user')); 23629 var userNameValid = element(by.binding('myForm.userName.$valid')); 23630 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 23631 var lastNameError = element(by.binding('myForm.lastName.$error')); 23632 var formValid = element(by.binding('myForm.$valid')); 23633 var userNameInput = element(by.model('user.name')); 23634 var userLastInput = element(by.model('user.last')); 23635 23636 it('should initialize to model', function() { 23637 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 23638 expect(userNameValid.getText()).toContain('true'); 23639 expect(formValid.getText()).toContain('true'); 23640 }); 23641 23642 it('should be invalid if empty when required', function() { 23643 userNameInput.clear(); 23644 userNameInput.sendKeys(''); 23645 23646 expect(user.getText()).toContain('{"last":"visitor"}'); 23647 expect(userNameValid.getText()).toContain('false'); 23648 expect(formValid.getText()).toContain('false'); 23649 }); 23650 23651 it('should be valid if empty when min length is set', function() { 23652 userLastInput.clear(); 23653 userLastInput.sendKeys(''); 23654 23655 expect(user.getText()).toContain('{"name":"guest","last":""}'); 23656 expect(lastNameValid.getText()).toContain('true'); 23657 expect(formValid.getText()).toContain('true'); 23658 }); 23659 23660 it('should be invalid if less than required min length', function() { 23661 userLastInput.clear(); 23662 userLastInput.sendKeys('xx'); 23663 23664 expect(user.getText()).toContain('{"name":"guest"}'); 23665 expect(lastNameValid.getText()).toContain('false'); 23666 expect(lastNameError.getText()).toContain('minlength'); 23667 expect(formValid.getText()).toContain('false'); 23668 }); 23669 23670 it('should be invalid if longer than max length', function() { 23671 userLastInput.clear(); 23672 userLastInput.sendKeys('some ridiculously long name'); 23673 23674 expect(user.getText()).toContain('{"name":"guest"}'); 23675 expect(lastNameValid.getText()).toContain('false'); 23676 expect(lastNameError.getText()).toContain('maxlength'); 23677 expect(formValid.getText()).toContain('false'); 23678 }); 23679 </file> 23680 </example> 23681 */ 23682var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 23683 function($browser, $sniffer, $filter, $parse) { 23684 return { 23685 restrict: 'E', 23686 require: ['?ngModel'], 23687 link: { 23688 pre: function(scope, element, attr, ctrls) { 23689 if (ctrls[0]) { 23690 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 23691 $browser, $filter, $parse); 23692 } 23693 } 23694 } 23695 }; 23696}]; 23697 23698
23699 23700var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 23701/** 23702 * @ngdoc directive 23703 * @name ngValue 23704 * 23705 * @description 23706 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 23707 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 23708 * the bound value. 23709 * 23710 * `ngValue` is useful when dynamically generating lists of radio buttons using 23711 * {@link ngRepeat `ngRepeat`}, as shown below. 23712 * 23713 * Likewise, `ngValue` can be used to generate `<option>` elements for 23714 * the {@link select `select`} element. In that case however, only strings are supported 23715 * for the `value `attribute, so the resulting `ngModel` will always be a string. 23716 * Support for `select` models with non-string values is available via `ngOptions`. 23717 * 23718 * @element input 23719 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 23720 * of the `input` element 23721 * 23722 * @example 23723 <example name="ngValue-directive" module="valueExample"> 23724 <file name="index.html"> 23725 <script> 23726 angular.module('valueExample', []) 23727 .controller('ExampleController', ['$scope', function($scope) { 23728 $scope.names = ['pizza', 'unicorns', 'robots']; 23729 $scope.my = { favorite: 'unicorns' }; 23730 }]); 23731 </script>
23732 <form ng-controller="ExampleController"> 23733 <h2>Which is your favorite?</h2> 23734 <label ng-repeat="name in names" for="{{name}}"> 23735 {{name}} 23736 <input type="radio" 23737 ng-model="my.favorite" 23738 ng-value="name" 23739 id="{{name}}" 23740 name="favorite"> 23741 </label> 23742 <div>You chose {{my.favorite}}</div> 23743 </form> 23744 </file> 23745 <file name="protractor.js" type="protractor"> 23746 var favorite = element(by.binding('my.favorite')); 23747 23748 it('should initialize to model', function() { 23749 expect(favorite.getText()).toContain('unicorns'); 23750 }); 23751 it('should bind the values to the inputs', function() { 23752 element.all(by.model('my.favorite')).get(0).click(); 23753 expect(favorite.getText()).toContain('pizza'); 23754 }); 23755 </file> 23756 </example> 23757 */ 23758var ngValueDirective = function() { 23759 return { 23760 restrict: 'A', 23761 priority: 100, 23762 compile: function(tpl, tplAttr) { 23763 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 23764 return function ngValueConstantLink(scope, elm, attr) { 23765 attr.$set('value', scope.$eval(attr.ngValue)); 23766 }; 23767 } else { 23768 return function ngValueLink(scope, elm, attr) { 23769 scope.$watch(attr.ngValue, function valueWatchAction(value) { 23770 attr.$set('value', value); 23771 }); 23772 }; 23773 } 23774 } 23775 }; 23776}; 23777 23778/** 23779 * @ngdoc directive 23780 * @name ngBind 23781 * @restrict AC 23782 * 23783 * @description 23784 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 23785 * with the value of a given expression, and to update the text content when the value of that 23786 * expression changes. 23787 * 23788 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 23789 * `{{ expression }}` which is similar but less verbose. 23790 * 23791 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 23792 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 23793 * element attribute, it makes the bindings invisible to the user while the page is loading. 23794 * 23795 * An alternative solution to this problem would be using the 23796 * {@link ng.directive:ngCloak ngCloak} directive. 23797 * 23798 * 23799 * @element ANY 23800 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 23801 * 23802 * @example 23803 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 23804 <example module="bindExample"> 23805 <file name="index.html"> 23806 <script> 23807 angular.module('bindExample', []) 23808 .controller('ExampleController', ['$scope', function($scope) { 23809 $scope.name = 'Whirled'; 23810 }]); 23811 </script>
23812 <div ng-controller="ExampleController"> 23813 <label>Enter name: <input type="text" ng-model="name"></label><br> 23814 Hello <span ng-bind="name"></span>! 23815 </div> 23816 </file> 23817 <file name="protractor.js" type="protractor"> 23818 it('should check ng-bind', function() { 23819 var nameInput = element(by.model('name')); 23820 23821 expect(element(by.binding('name')).getText()).toBe('Whirled'); 23822 nameInput.clear(); 23823 nameInput.sendKeys('world'); 23824 expect(element(by.binding('name')).getText()).toBe('world'); 23825 }); 23826 </file> 23827 </example> 23828 */ 23829var ngBindDirective = ['$compile', function($compile) { 23830 return { 23831 restrict: 'AC', 23832 compile: function ngBindCompile(templateElement) { 23833 $compile.$$addBindingClass(templateElement); 23834 return function ngBindLink(scope, element, attr) { 23835 $compile.$$addBindingInfo(element, attr.ngBind); 23836 element = element[0]; 23837 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 23838 element.textContent = isUndefined(value) ? '' : value; 23839 }); 23840 }; 23841 } 23842 }; 23843}]; 23844 23845 23846/** 23847 * @ngdoc directive 23848 * @name ngBindTemplate 23849 * 23850 * @description 23851 * The `ngBindTemplate` directive specifies that the element 23852 * text content should be replaced with the interpolation of the template 23853 * in the `ngBindTemplate` attribute. 23854 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 23855 * expressions. This directive is needed since some HTML elements 23856 * (such as TITLE and OPTION) cannot contain SPAN elements. 23857 * 23858 * @element ANY 23859 * @param {string} ngBindTemplate template of form 23860 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 23861 * 23862 * @example 23863 * Try it here: enter text in text box and watch the greeting change. 23864 <example module="bindExample"> 23865 <file name="index.html"> 23866 <script> 23867 angular.module('bindExample', []) 23868 .controller('ExampleController', ['$scope', function($scope) { 23869 $scope.salutation = 'Hello'; 23870 $scope.name = 'World'; 23871 }]); 23872 </script>
23873 <div ng-controller="ExampleController"> 23874 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 23875 <label>Name: <input type="text" ng-model="name"></label><br> 23876 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 23877 </div> 23878 </file> 23879 <file name="protractor.js" type="protractor"> 23880 it('should check ng-bind', function() { 23881 var salutationElem = element(by.binding('salutation')); 23882 var salutationInput = element(by.model('salutation')); 23883 var nameInput = element(by.model('name')); 23884 23885 expect(salutationElem.getText()).toBe('Hello World!'); 23886 23887 salutationInput.clear(); 23888 salutationInput.sendKeys('Greetings'); 23889 nameInput.clear(); 23890 nameInput.sendKeys('user'); 23891 23892 expect(salutationElem.getText()).toBe('Greetings user!'); 23893 }); 23894 </file> 23895 </example> 23896 */ 23897var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 23898 return { 23899 compile: function ngBindTemplateCompile(templateElement) { 23900 $compile.$$addBindingClass(templateElement); 23901 return function ngBindTemplateLink(scope, element, attr) { 23902 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 23903 $compile.$$addBindingInfo(element, interpolateFn.expressions); 23904 element = element[0]; 23905 attr.$observe('ngBindTemplate', function(value) { 23906 element.textContent = isUndefined(value) ? '' : value; 23907 }); 23908 }; 23909 } 23910 }; 23911}]; 23912 23913 23914/** 23915 * @ngdoc directive 23916 * @name ngBindHtml 23917 * 23918 * @description 23919 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 23920 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 23921 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 23922 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 23923 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 23924 * 23925 * You may also bypass sanitization for values you know are safe. To do so, bind to 23926 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 23927 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 23928 * 23929 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 23930 * will have an exception (instead of an exploit.) 23931 * 23932 * @element ANY 23933 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 23934 * 23935 * @example 23936 23937 <example module="bindHtmlExample" deps="angular-sanitize.js"> 23938 <file name="index.html">
23939 <div ng-controller="ExampleController"> 23940 <p ng-bind-html="myHTML"></p> 23941 </div> 23942 </file> 23943 23944 <file name="script.js"> 23945 angular.module('bindHtmlExample', ['ngSanitize']) 23946 .controller('ExampleController', ['$scope', function($scope) { 23947 $scope.myHTML = 23948 'I am an <code>HTML</code>string with ' + 23949 '<a href="#">links!</a> and other <em>stuff</em>'; 23950 }]); 23951 </file> 23952 23953 <file name="protractor.js" type="protractor"> 23954 it('should check ng-bind-html', function() { 23955 expect(element(by.binding('myHTML')).getText()).toBe( 23956 'I am an HTMLstring with links! and other stuff'); 23957 }); 23958 </file> 23959 </example> 23960 */ 23961var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 23962 return { 23963 restrict: 'A', 23964 compile: function ngBindHtmlCompile(tElement, tAttrs) { 23965 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 23966 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 23967 return (value || '').toString(); 23968 }); 23969 $compile.$$addBindingClass(tElement); 23970 23971 return function ngBindHtmlLink(scope, element, attr) { 23972 $compile.$$addBindingInfo(element, attr.ngBindHtml); 23973 23974 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 23975 // we re-evaluate the expr because we want a TrustedValueHolderType 23976 // for $sce, not a string 23977 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 23978 }); 23979 }; 23980 } 23981 }; 23982}]; 23983 23984/** 23985 * @ngdoc directive 23986 * @name ngChange 23987 * 23988 * @description 23989 * Evaluate the given expression when the user changes the input. 23990 * The expression is evaluated immediately, unlike the JavaScript onchange event 23991 * which only triggers at the end of a change (usually, when the user leaves the 23992 * form element or presses the return key). 23993 * 23994 * The `ngChange` expression is only evaluated when a change in the input value causes 23995 * a new value to be committed to the model. 23996 * 23997 * It will not be evaluated: 23998 * * if the value returned from the `$parsers` transformation pipeline has not changed 23999 * * if the input has continued to be invalid since the model will stay `null` 24000 * * if the model is changed programmatically and not by a change to the input value 24001 * 24002 * 24003 * Note, this directive requires `ngModel` to be present. 24004 * 24005 * @element input 24006 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 24007 * in input value. 24008 * 24009 * @example 24010 * <example name="ngChange-directive" module="changeExample"> 24011 * <file name="index.html"> 24012 * <script> 24013 * angular.module('changeExample', []) 24014 * .controller('ExampleController', ['$scope', function($scope) { 24015 * $scope.counter = 0; 24016 * $scope.change = function() { 24017 * $scope.counter++; 24018 * }; 24019 * }]); 24020 * </script>
24021 * <div ng-controller="ExampleController"> 24022 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 24023 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 24024 * <label for="ng-change-example2">Confirmed</label><br /> 24025 * <tt>debug = {{confirmed}}</tt><br/> 24026 * <tt>counter = {{counter}}</tt><br/> 24027 * </div> 24028 * </file> 24029 * <file name="protractor.js" type="protractor"> 24030 * var counter = element(by.binding('counter')); 24031 * var debug = element(by.binding('confirmed')); 24032 * 24033 * it('should evaluate the expression if changing from view', function() { 24034 * expect(counter.getText()).toContain('0'); 24035 * 24036 * element(by.id('ng-change-example1')).click(); 24037 * 24038 * expect(counter.getText()).toContain('1'); 24039 * expect(debug.getText()).toContain('true'); 24040 * }); 24041 * 24042 * it('should not evaluate the expression if changing from model', function() { 24043 * element(by.id('ng-change-example2')).click(); 24044 24045 * expect(counter.getText()).toContain('0'); 24046 * expect(debug.getText()).toContain('true'); 24047 * }); 24048 * </file> 24049 * </example> 24050 */ 24051var ngChangeDirective = valueFn({ 24052 restrict: 'A', 24053 require: 'ngModel', 24054 link: function(scope, element, attr, ctrl) { 24055 ctrl.$viewChangeListeners.push(function() { 24056 scope.$eval(attr.ngChange); 24057 }); 24058 } 24059}); 24060 24061function classDirective(name, selector) { 24062 name = 'ngClass' + name; 24063 return ['$animate', function($animate) { 24064 return { 24065 restrict: 'AC', 24066 link: function(scope, element, attr) { 24067 var oldVal; 24068 24069 scope.$watch(attr[name], ngClassWatchAction, true); 24070 24071 attr.$observe('class', function(value) { 24072 ngClassWatchAction(scope.$eval(attr[name])); 24073 }); 24074 24075 24076 if (name !== 'ngClass') { 24077 scope.$watch('$index', function($index, old$index) { 24078 // jshint bitwise: false 24079 var mod = $index & 1; 24080 if (mod !== (old$index & 1)) { 24081 var classes = arrayClasses(scope.$eval(attr[name])); 24082 mod === selector ? 24083 addClasses(classes) : 24084 removeClasses(classes); 24085 } 24086 }); 24087 } 24088 24089 function addClasses(classes) { 24090 var newClasses = digestClassCounts(classes, 1); 24091 attr.$addClass(newClasses); 24092 } 24093 24094 function removeClasses(classes) { 24095 var newClasses = digestClassCounts(classes, -1); 24096 attr.$removeClass(newClasses); 24097 } 24098 24099 function digestClassCounts(classes, count) { 24100 // Use createMap() to prevent class assumptions involving property 24101 // names in Object.prototype 24102 var classCounts = element.data('$classCounts') || createMap(); 24103 var classesToUpdate = []; 24104 forEach(classes, function(className) { 24105 if (count > 0 || classCounts[className]) { 24106 classCounts[className] = (classCounts[className] || 0) + count; 24107 if (classCounts[className] === +(count > 0)) { 24108 classesToUpdate.push(className); 24109 } 24110 } 24111 }); 24112 element.data('$classCounts', classCounts); 24113 return classesToUpdate.join(' '); 24114 } 24115 24116 function updateClasses(oldClasses, newClasses) { 24117 var toAdd = arrayDifference(newClasses, oldClasses); 24118 var toRemove = arrayDifference(oldClasses, newClasses); 24119 toAdd = digestClassCounts(toAdd, 1); 24120 toRemove = digestClassCounts(toRemove, -1); 24121 if (toAdd && toAdd.length) { 24122 $animate.addClass(element, toAdd); 24123 } 24124 if (toRemove && toRemove.length) { 24125 $animate.removeClass(element, toRemove); 24126 } 24127 } 24128 24129 function ngClassWatchAction(newVal) { 24130 if (selector === true || scope.$index % 2 === selector) { 24131 var newClasses = arrayClasses(newVal || []); 24132 if (!oldVal) { 24133 addClasses(newClasses); 24134 } else if (!equals(newVal,oldVal)) { 24135 var oldClasses = arrayClasses(oldVal);
24136 updateClasses(oldClasses, newClasses); 24137 } 24138 } 24139 if (isArray(newVal)) { 24140 oldVal = newVal.map(function(v) { return shallowCopy(v); }); 24141 } else { 24142 oldVal = shallowCopy(newVal); 24143 } 24144 } 24145 } 24146 }; 24147 24148 function arrayDifference(tokens1, tokens2) { 24149 var values = []; 24150 24151 outer: 24152 for (var i = 0; i < tokens1.length; i++) { 24153 var token = tokens1[i]; 24154 for (var j = 0; j < tokens2.length; j++) { 24155 if (token == tokens2[j]) continue outer; 24156 } 24157 values.push(token); 24158 } 24159 return values; 24160 } 24161 24162 function arrayClasses(classVal) { 24163 var classes = []; 24164 if (isArray(classVal)) { 24165 forEach(classVal, function(v) { 24166 classes = classes.concat(arrayClasses(v)); 24167 }); 24168 return classes; 24169 } else if (isString(classVal)) { 24170 return classVal.split(' '); 24171 } else if (isObject(classVal)) { 24172 forEach(classVal, function(v, k) { 24173 if (v) { 24174 classes = classes.concat(k.split(' ')); 24175 } 24176 }); 24177 return classes; 24178 } 24179 return classVal; 24180 } 24181 }]; 24182} 24183 24184/** 24185 * @ngdoc directive 24186 * @name ngClass 24187 * @restrict AC 24188 * 24189 * @description 24190 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 24191 * an expression that represents all classes to be added. 24192 * 24193 * The directive operates in three different ways, depending on which of three types the expression 24194 * evaluates to: 24195 * 24196 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 24197 * names. 24198 * 24199 * 2. If the expression evaluates to an object, then for each key-value pair of the 24200 * object with a truthy value the corresponding key is used as a class name. 24201 * 24202 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 24203 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 24204 * to give you more control over what CSS classes appear. See the code below for an example of this. 24205 * 24206 * 24207 * The directive won't add duplicate classes if a particular class was already set. 24208 * 24209 * When the expression changes, the previously added classes are removed and only then are the 24210 * new classes added. 24211 * 24212 * @animations 24213 * | Animation | Occurs | 24214 * |----------------------------------|-------------------------------------| 24215 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 24216 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 24217 * 24218 * @element ANY 24219 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 24220 * of the evaluation can be a string representing space delimited class 24221 * names, an array, or a map of class names to boolean values. In the case of a map, the 24222 * names of the properties whose values are truthy will be added as css classes to the 24223 * element. 24224 * 24225 * @example Example that demonstrates basic bindings via ngClass directive. 24226 <example> 24227 <file name="index.html"> 24228 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 24229 <label> 24230 <input type="checkbox" ng-model="deleted"> 24231 deleted (apply "strike" class) 24232 </label><br> 24233 <label> 24234 <input type="checkbox" ng-model="important"> 24235 important (apply "bold" class) 24236 </label><br> 24237 <label> 24238 <input type="checkbox" ng-model="error"> 24239 error (apply "has-error" class) 24240 </label> 24241 <hr> 24242 <p ng-class="style">Using String Syntax</p> 24243 <input type="text" ng-model="style" 24244 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 24245 <hr> 24246 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 24247 <input ng-model="style1" 24248 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 24249 <input ng-model="style2" 24250 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 24251 <input ng-model="style3" 24252 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 24253 <hr> 24254 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 24255 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 24256 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 24257 </file> 24258 <file name="style.css"> 24259 .strike { 24260 text-decoration: line-through; 24261 } 24262 .bold { 24263 font-weight: bold; 24264 } 24265 .red { 24266 color: red; 24267 } 24268 .has-error { 24269 color: red; 24270 background-color: yellow; 24271 } 24272 .orange { 24273 color: orange; 24274 } 24275 </file> 24276 <file name="protractor.js" type="protractor"> 24277 var ps = element.all(by.css('p')); 24278 24279 it('should let you toggle the class', function() { 24280 24281 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 24282 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 24283
24284 element(by.model('important')).click(); 24285 expect(ps.first().getAttribute('class')).toMatch(/bold/); 24286 24287 element(by.model('error')).click(); 24288 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 24289 }); 24290 24291 it('should let you toggle string example', function() { 24292 expect(ps.get(1).getAttribute('class')).toBe(''); 24293 element(by.model('style')).clear(); 24294 element(by.model('style')).sendKeys('red'); 24295 expect(ps.get(1).getAttribute('class')).toBe('red'); 24296 }); 24297 24298 it('array example should have 3 classes', function() { 24299 expect(ps.get(2).getAttribute('class')).toBe(''); 24300 element(by.model('style1')).sendKeys('bold'); 24301 element(by.model('style2')).sendKeys('strike'); 24302 element(by.model('style3')).sendKeys('red'); 24303 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 24304 }); 24305 24306 it('array with map example should have 2 classes', function() { 24307 expect(ps.last().getAttribute('class')).toBe(''); 24308 element(by.model('style4')).sendKeys('bold'); 24309 element(by.model('warning')).click(); 24310 expect(ps.last().getAttribute('class')).toBe('bold orange'); 24311 }); 24312 </file> 24313 </example> 24314 24315 ## Animations 24316 24317 The example below demonstrates how to perform animations using ngClass. 24318 24319 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24320 <file name="index.html"> 24321 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 24322 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 24323 <br> 24324 <span class="base-class" ng-class="myVar">Sample Text</span> 24325 </file> 24326 <file name="style.css"> 24327 .base-class { 24328 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24329 } 24330 24331 .base-class.my-class { 24332 color: red; 24333 font-size:3em; 24334 } 24335 </file> 24336 <file name="protractor.js" type="protractor"> 24337 it('should check ng-class', function() { 24338 expect(element(by.css('.base-class')).getAttribute('class')).not. 24339 toMatch(/my-class/); 24340 24341 element(by.id('setbtn')).click(); 24342 24343 expect(element(by.css('.base-class')).getAttribute('class')). 24344 toMatch(/my-class/); 24345 24346 element(by.id('clearbtn')).click(); 24347 24348 expect(element(by.css('.base-class')).getAttribute('class')).not. 24349 toMatch(/my-class/); 24350 }); 24351 </file> 24352 </example> 24353 24354 24355 ## ngClass and pre-existing CSS3 Transitions/Animations 24356 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 24357 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 24358 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 24359 to view the step by step details of {@link $animate#addClass $animate.addClass} and 24360 {@link $animate#removeClass $animate.removeClass}. 24361 */ 24362var ngClassDirective = classDirective('', true); 24363 24364/** 24365 * @ngdoc directive 24366 * @name ngClassOdd 24367 * @restrict AC 24368 * 24369 * @description 24370 * The `ngClassOdd` and `ngClassEven` directives work exactly as 24371 * {@link ng.directive:ngClass ngClass}, except they work in 24372 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 24373 * 24374 * This directive can be applied only within the scope of an 24375 * {@link ng.directive:ngRepeat ngRepeat}. 24376 * 24377 * @element ANY 24378 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 24379 * of the evaluation can be a string representing space delimited class names or an array. 24380 * 24381 * @example 24382 <example> 24383 <file name="index.html"> 24384 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 24385 <li ng-repeat="name in names"> 24386 <span ng-class-odd="'odd'" ng-class-even="'even'"> 24387 {{name}} 24388 </span> 24389 </li> 24390 </ol> 24391 </file> 24392 <file name="style.css"> 24393 .odd { 24394 color: red; 24395 } 24396 .even { 24397 color: blue; 24398 } 24399 </file> 24400 <file name="protractor.js" type="protractor"> 24401 it('should check ng-class-odd and ng-class-even', function() { 24402 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 24403 toMatch(/odd/); 24404 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 24405 toMatch(/even/); 24406 }); 24407 </file> 24408 </example> 24409 */ 24410var ngClassOddDirective = classDirective('Odd', 0); 24411 24412/** 24413 * @ngdoc directive 24414 * @name ngClassEven 24415 * @restrict AC 24416 * 24417 * @description 24418 * The `ngClassOdd` and `ngClassEven` directives work exactly as 24419 * {@link ng.directive:ngClass ngClass}, except they work in 24420 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 24421 * 24422 * This directive can be applied only within the scope of an 24423 * {@link ng.directive:ngRepeat ngRepeat}. 24424 * 24425 * @element ANY 24426 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 24427 * result of the evaluation can be a string representing space delimited class names or an array.
24428 * 24429 * @example 24430 <example> 24431 <file name="index.html"> 24432 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 24433 <li ng-repeat="name in names"> 24434 <span ng-class-odd="'odd'" ng-class-even="'even'"> 24435 {{name}} 24436 </span> 24437 </li> 24438 </ol> 24439 </file> 24440 <file name="style.css"> 24441 .odd { 24442 color: red; 24443 } 24444 .even { 24445 color: blue; 24446 } 24447 </file> 24448 <file name="protractor.js" type="protractor"> 24449 it('should check ng-class-odd and ng-class-even', function() { 24450 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 24451 toMatch(/odd/); 24452 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 24453 toMatch(/even/); 24454 }); 24455 </file> 24456 </example> 24457 */ 24458var ngClassEvenDirective = classDirective('Even', 1); 24459 24460/** 24461 * @ngdoc directive 24462 * @name ngCloak 24463 * @restrict AC 24464 * 24465 * @description 24466 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 24467 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 24468 * directive to avoid the undesirable flicker effect caused by the html template display. 24469 * 24470 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 24471 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 24472 * of the browser view. 24473 * 24474 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 24475 * `angular.min.js`. 24476 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 24477 * 24478 * ```css 24479 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 24480 * display: none !important; 24481 * } 24482 * ``` 24483 * 24484 * When this css rule is loaded by the browser, all html elements (including their children) that 24485 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 24486 * during the compilation of the template it deletes the `ngCloak` element attribute, making 24487 * the compiled element visible. 24488 * 24489 * For the best result, the `angular.js` script must be loaded in the head section of the html 24490 * document; alternatively, the css rule above must be included in the external stylesheet of the 24491 * application. 24492 * 24493 * @element ANY 24494 * 24495 * @example 24496 <example> 24497 <file name="index.html"> 24498 <div id="template1" ng-cloak>{{ 'hello' }}</div> 24499 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 24500 </file> 24501 <file name="protractor.js" type="protractor"> 24502 it('should remove the template directive and css class', function() { 24503 expect($('#template1').getAttribute('ng-cloak')). 24504 toBeNull(); 24505 expect($('#template2').getAttribute('ng-cloak')). 24506 toBeNull(); 24507 }); 24508 </file> 24509 </example> 24510 * 24511 */ 24512var ngCloakDirective = ngDirective({ 24513 compile: function(element, attr) { 24514 attr.$set('ngCloak', undefined); 24515 element.removeClass('ng-cloak'); 24516 } 24517}); 24518 24519/** 24520 * @ngdoc directive 24521 * @name ngController 24522 * 24523 * @description 24524 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 24525 * supports the principles behind the Model-View-Controller design pattern. 24526 * 24527 * MVC components in angular: 24528 * 24529 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties 24530 * are accessed through bindings. 24531 * * View — The template (HTML with data bindings) that is rendered into the View. 24532 * * Controller — The `ngController` directive specifies a Controller class; the class contains business 24533 * logic behind the application to decorate the scope with functions and values 24534 * 24535 * Note that you can also attach controllers to the DOM by declaring it in a route definition 24536 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 24537 * again using `ng-controller` in the template itself. This will cause the controller to be attached 24538 * and executed twice. 24539 * 24540 * @element ANY 24541 * @scope 24542 * @priority 500 24543 * @param {expression} ngController Name of a constructor function registered with the current 24544 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 24545 * that on the current scope evaluates to a constructor function. 24546 * 24547 * The controller instance can be published into a scope property by specifying 24548 * `ng-controller="as propertyName"`. 24549 * 24550 * If the current `$controllerProvider` is configured to use globals (via 24551 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 24552 * also be the name of a globally accessible constructor function (not recommended). 24553 * 24554 * @example 24555 * Here is a simple form for editing user contact information. Adding, remov
24555ing, clearing, and 24556 * greeting are methods declared on the controller (see source tab). These methods can 24557 * easily be called from the angular markup. Any changes to the data are automatically reflected 24558 * in the View without the need for a manual update. 24559 * 24560 * Two different declaration styles are included below: 24561 * 24562 * * one binds methods and properties directly onto the controller using `this`: 24563 * `ng-controller="SettingsController1 as settings"` 24564 * * one injects `$scope` into the controller: 24565 * `ng-controller="SettingsController2"` 24566 * 24567 * The second option is more common in the Angular community, and is generally used in boilerplates 24568 * and in this guide. However, there are advantages to binding properties directly to the controller 24569 * and avoiding scope. 24570 * 24571 * * Using `controller as` makes it obvious which controller you are accessing in the template when 24572 * multiple controllers apply to an element. 24573 * * If you are writing your controllers as classes you have easier access to the properties and 24574 * methods, which will appear on the scope, from inside the controller code. 24575 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 24576 * inheritance masking primitives. 24577 * 24578 * This example demonstrates the `controller as` syntax. 24579 * 24580 * <example name="ngControllerAs" module="controllerAsExample"> 24581 * <file name="index.html"> 24582 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 24583 * <label>Name: <input type="text" ng-model="settings.name"/></label> 24584 * <button ng-click="settings.greet()">greet</button><br/> 24585 * Contact: 24586 * <ul> 24587 * <li ng-repeat="contact in settings.contacts"> 24588 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 24589 * <option>phone</option> 24590 * <option>email</option> 24591 * </select> 24592 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 24593 * <button ng-click="settings.clearContact(contact)">clear</button> 24594 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 24595 * </li> 24596 * <li><button ng-click="settings.addContact()">add</button></li> 24597 * </ul> 24598 * </div> 24599 * </file> 24600 * <file name="app.js"> 24601 * angular.module('controllerAsExample', []) 24602 * .controller('SettingsController1', SettingsController1); 24603 * 24604 * function SettingsController1() { 24605 * this.name = "John Smith"; 24606 * this.contacts = [ 24607 * {type: 'phone', value: '408 555 1212'}, 24608 * {type: 'email', value: '[email protected]'} ]; 24609 * } 24610 * 24611 * SettingsController1.prototype.greet = function() { 24612 * alert(this.name); 24613 * }; 24614 * 24615 * SettingsController1.prototype.addContact = function() { 24616 * this.contacts.push({type: 'email', value: '[email protected]'}); 24617 * }; 24618 * 24619 * SettingsController1.prototype.removeContact = function(contactToRemove) { 24620 * var index = this.contacts.indexOf(contactToRemove); 24621 * this.contacts.splice(index, 1); 24622 * }; 24623 * 24624 * SettingsController1.prototype.clearContact = function(contact) { 24625 * contact.type = 'phone'; 24626 * contact.value = ''; 24627 * }; 24628 * </file> 24629 * <file name="protractor.js" type="protractor"> 24630 * it('should check controller as', function() { 24631 * var container = element(by.id('ctrl-as-exmpl')); 24632 * expect(container.element(by.model('settings.name')) 24633 * .getAttribute('value')).toBe('John Smith'); 24634 * 24635 * var firstRepeat = 24636 * container.element(by.repeater('contact in settings.contacts').row(0)); 24637 * var secondRepeat = 24638 * container.element(by.repeater('contact in settings.contacts').row(1)); 24639 * 24640 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24641 * .toBe('408 555 1212'); 24642 * 24643 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 24644 * .toBe('[email protected]'); 24645 * 24646 * firstRepeat.element(by.buttonText('clear')).click(); 24647 * 24648 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24649 * .toBe(''); 24650 * 24651 * container.element(by.buttonText('add')).click(); 24652 * 24653 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 24654 * .element(by.model('contact.value')) 24655 * .getAttribute('value')) 24656 * .toBe('[email protected]'); 24657 * }); 24658 * </file> 24659 * </example> 24660 * 24661 * This example demonstrates the "attach to `$scope`" style of controller. 24662 * 24663 * <example name="ngController" module="controllerExample"> 24664 * <file name="index.html"> 24665 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 24666 * <label>Name: <input type="text" ng-model="name"/></label> 24667 * <button ng-click="greet()">greet</button><br/> 24668 * Contact: 24669 * <ul> 24670 * <li ng-repeat="contact in contacts"> 24671 * <select ng-model="contact.type" id="select_{{$index}}"> 24672 * <option>phone</option> 24673 * <option>email</option> 24674 * </select> 24675 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 24676 * <button ng-click="clearContact(contact)">clear</button> 24677 * <button ng-click="removeContact(contact)">X</button> 24678 * </li> 24679 * <li>[ <button ng-click="addContact()">add</button> ]</li> 24680 * </ul> 24681 * </div> 24682 * </file> 24683 * <file name="app.js">
24684 * angular.module('controllerExample', []) 24685 * .controller('SettingsController2', ['$scope', SettingsController2]); 24686 * 24687 * function SettingsController2($scope) { 24688 * $scope.name = "John Smith"; 24689 * $scope.contacts = [ 24690 * {type:'phone', value:'408 555 1212'}, 24691 * {type:'email', value:'[email protected]'} ]; 24692 * 24693 * $scope.greet = function() { 24694 * alert($scope.name); 24695 * }; 24696 * 24697 * $scope.addContact = function() { 24698 * $scope.contacts.push({type:'email', value:'[email protected]'}); 24699 * }; 24700 * 24701 * $scope.removeContact = function(contactToRemove) { 24702 * var index = $scope.contacts.indexOf(contactToRemove); 24703 * $scope.contacts.splice(index, 1); 24704 * }; 24705 * 24706 * $scope.clearContact = function(contact) { 24707 * contact.type = 'phone'; 24708 * contact.value = ''; 24709 * }; 24710 * } 24711 * </file> 24712 * <file name="protractor.js" type="protractor"> 24713 * it('should check controller', function() { 24714 * var container = element(by.id('ctrl-exmpl')); 24715 * 24716 * expect(container.element(by.model('name')) 24717 * .getAttribute('value')).toBe('John Smith'); 24718 * 24719 * var firstRepeat = 24720 * container.element(by.repeater('contact in contacts').row(0)); 24721 * var secondRepeat = 24722 * container.element(by.repeater('contact in contacts').row(1)); 24723 * 24724 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24725 * .toBe('408 555 1212'); 24726 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 24727 * .toBe('[email protected]'); 24728 * 24729 * firstRepeat.element(by.buttonText('clear')).click(); 24730 * 24731 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24732 * .toBe(''); 24733 * 24734 * container.element(by.buttonText('add')).click(); 24735 * 24736 * expect(container.element(by.repeater('contact in contacts').row(2)) 24737 * .element(by.model('contact.value')) 24738 * .getAttribute('value')) 24739 * .toBe('[email protected]'); 24740 * }); 24741 * </file> 24742 *</example> 24743 24744 */ 24745var ngControllerDirective = [function() { 24746 return { 24747 restrict: 'A', 24748 scope: true, 24749 controller: '@', 24750 priority: 500 24751 }; 24752}]; 24753 24754/** 24755 * @ngdoc directive 24756 * @name ngCsp 24757 * 24758 * @element html 24759 * @description 24760 * 24761 * Angular has some features that can break certain 24762 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 24763 * 24764 * If you intend to implement these rules then you must tell Angular not to use these features. 24765 * 24766 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 24767 * 24768 * 24769 * The following rules affect Angular: 24770 * 24771 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 24772 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 24773 * increase in the speed of evaluating Angular expressions. 24774 * 24775 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 24776 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 24777 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 24778 * `angular-csp.css` in your HTML manually. 24779 * 24780 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 24781 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 24782 * however, triggers a CSP error to be logged in the console: 24783 * 24784 * ``` 24785 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 24786 * script in the following Content Security Policy directive: "default-src 'self'". Note that 24787 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 24788 * ``` 24789 * 24790 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 24791 * directive on an element of the HTML document that appears before the `<script>` tag that loads 24792 * the `angular.js` file. 24793 * 24794 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 24795 * 24796 * You can specify which of the CSP related Angular features should be deactivated by providing 24797 * a value for the `ng-csp` attribute. The options are as follows: 24798 * 24799 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 24800 *
24801 * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings 24802 * 24803 * You can use these values in the following combinations: 24804 * 24805 * 24806 * * No declaration means that Angular will assume that you can do inline styles, but it will do 24807 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 24808 * of Angular. 24809 * 24810 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 24811 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 24812 * of Angular. 24813 * 24814 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 24815 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 24816 * 24817 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 24818 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 24819 * 24820 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 24821 * styles nor use eval, which is the same as an empty: ng-csp. 24822 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 24823 * 24824 * @example 24825 * This example shows how to apply the `ngCsp` directive to the `html` tag. 24826 ```html 24827 <!doctype html> 24828 <html ng-app ng-csp> 24829 ... 24830 ... 24831 </html> 24832 ``` 24833 * @example 24834 // Note: the suffix `.csp` in the example name triggers 24835 // csp mode in our http server! 24836 <example name="example.csp" module="cspExample" ng-csp="true"> 24837 <file name="index.html"> 24838 <div ng-controller="MainController as ctrl"> 24839 <div> 24840 <button ng-click="ctrl.inc()" id="inc">Increment</button> 24841 <span id="counter"> 24842 {{ctrl.counter}} 24843 </span> 24844 </div> 24845 24846 <div> 24847 <button ng-click="ctrl.evil()" id="evil">Evil</button> 24848 <span id="evilError"> 24849 {{ctrl.evilError}} 24850 </span> 24851 </div> 24852 </div> 24853 </file> 24854 <file name="script.js"> 24855 angular.module('cspExample', []) 24856 .controller('MainController', function() { 24857 this.counter = 0; 24858 this.inc = function() { 24859 this.counter++; 24860 }; 24861 this.evil = function() { 24862 // jshint evil:true 24863 try { 24864 eval('1+2'); 24865 } catch (e) { 24866 this.evilError = e.message; 24867 } 24868 }; 24869 }); 24870 </file> 24871 <file name="protractor.js" type="protractor"> 24872 var util, webdriver; 24873 24874 var incBtn = element(by.id('inc')); 24875 var counter = element(by.id('counter')); 24876 var evilBtn = element(by.id('evil')); 24877 var evilError = element(by.id('evilError')); 24878 24879 function getAndClearSevereErrors() { 24880 return browser.manage().logs().get('browser').then(function(browserLog) { 24881 return browserLog.filter(function(logEntry) { 24882 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 24883 }); 24884 }); 24885 } 24886 24887 function clearErrors() { 24888 getAndClearSevereErrors(); 24889 } 24890 24891 function expectNoErrors() { 24892 getAndClearSevereErrors().then(function(filteredLog) { 24893 expect(filteredLog.length).toEqual(0); 24894 if (filteredLog.length) { 24895 console.log('browser console errors: ' + util.inspect(filteredLog)); 24896 } 24897 }); 24898 } 24899 24900 function expectError(regex) { 24901 getAndClearSevereErrors().then(function(filteredLog) { 24902 var found = false; 24903 filteredLog.forEach(function(log) { 24904 if (log.message.match(regex)) { 24905 found = true; 24906 } 24907 }); 24908 if (!found) { 24909 throw new Error('expected an error that matches ' + regex); 24910 } 24911 }); 24912 } 24913 24914 beforeEach(function() { 24915 util = require('util'); 24916 webdriver = require('protractor/node_modules/selenium-webdriver'); 24917 }); 24918 24919 // For now, we only test on Chrome, 24920 // as Safari does not load the page with Protractor's injected scripts, 24921 // and Firefox webdriver always disables content security policy (#6358) 24922 if (browser.params.browser !== 'chrome') { 24923 return; 24924 } 24925 24926 it('should not report errors when the page is loaded', function() { 24927 // clear errors so we are not dependent on previous tests 24928 clearErrors(); 24929 // Need to reload the page as the page is already loaded when 24930 // we come here 24931 browser.driver.getCurrentUrl().then(function(url) { 24932 browser.get(url); 24933 }); 24934 expectNoErrors(); 24935 }); 24936 24937 it('should evaluate expressions', function() { 24938 expect(counter.getText()).toEqual('0'); 24939 incBtn.click(); 24940 expect(counter.getText()).toEqual('1'); 24941 expectNoErrors(); 24942 }); 24943 24944 it('should throw and report an error when using "eval"', function() { 24945 evilBtn.click(); 24946 expect(evilError.getText()).toMatch(/Content Security Policy/); 24947 expectError(/Content Security Policy/); 24948 }); 24949 </file> 24950 </example> 24951 */ 24952 24953// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
24954// bootstrap the system (before $parse is instantiated), for this reason we just have 24955// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 24956 24957/** 24958 * @ngdoc directive 24959 * @name ngClick 24960 * 24961 * @description 24962 * The ngClick directive allows you to specify custom behavior when 24963 * an element is clicked. 24964 * 24965 * @element ANY 24966 * @priority 0 24967 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 24968 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 24969 * 24970 * @example 24971 <example> 24972 <file name="index.html"> 24973 <button ng-click="count = count + 1" ng-init="count=0"> 24974 Increment 24975 </button> 24976 <span> 24977 count: {{count}} 24978 </span> 24979 </file> 24980 <file name="protractor.js" type="protractor"> 24981 it('should check ng-click', function() { 24982 expect(element(by.binding('count')).getText()).toMatch('0'); 24983 element(by.css('button')).click(); 24984 expect(element(by.binding('count')).getText()).toMatch('1'); 24985 }); 24986 </file> 24987 </example> 24988 */ 24989/* 24990 * A collection of directives that allows creation of custom event handlers that are defined as 24991 * angular expressions and are compiled and executed within the current scope. 24992 */ 24993var ngEventDirectives = {}; 24994 24995// For events that might fire synchronously during DOM manipulation 24996// we need to execute their event handlers asynchronously using $evalAsync, 24997// so that they are not executed in an inconsistent state. 24998var forceAsyncEvents = { 24999 'blur': true, 25000 'focus': true 25001}; 25002forEach( 25003 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 25004 function(eventName) { 25005 var directiveName = directiveNormalize('ng-' + eventName); 25006 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 25007 return { 25008 restrict: 'A', 25009 compile: function($element, attr) { 25010 // We expose the powerful $event object on the scope that provides access to the Window, 25011 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 25012 // checks at the cost of speed since event handler expressions are not executed as 25013 // frequently as regular change detection. 25014 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 25015 return function ngEventHandler(scope, element) { 25016 element.on(eventName, function(event) { 25017 var callback = function() { 25018 fn(scope, {$event:event}); 25019 }; 25020 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 25021 scope.$evalAsync(callback); 25022 } else { 25023 scope.$apply(callback); 25024 } 25025 }); 25026 }; 25027 } 25028 }; 25029 }]; 25030 } 25031); 25032 25033/** 25034 * @ngdoc directive 25035 * @name ngDblclick 25036 * 25037 * @description 25038 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 25039 * 25040 * @element ANY 25041 * @priority 0 25042 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 25043 * a dblclick. (The Event object is available as `$event`) 25044 * 25045 * @example 25046 <example> 25047 <file name="index.html"> 25048 <button ng-dblclick="count = count + 1" ng-init="count=0"> 25049 Increment (on double click) 25050 </button> 25051 count: {{count}} 25052 </file> 25053 </example> 25054 */ 25055 25056 25057/** 25058 * @ngdoc directive 25059 * @name ngMousedown 25060 * 25061 * @description 25062 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 25063 * 25064 * @element ANY 25065 * @priority 0 25066 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 25067 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 25068 * 25069 * @example 25070 <example> 25071 <file name="index.html"> 25072 <button ng-mousedown="count = count + 1" ng-init="count=0"> 25073 Increment (on mouse down) 25074 </button> 25075 count: {{count}} 25076 </file> 25077 </example> 25078 */ 25079 25080 25081/** 25082 * @ngdoc directive 25083 * @name ngMouseup 25084 * 25085 * @description 25086 * Specify custom behavior on mouseup event. 25087 * 25088 * @element ANY 25089 * @priority 0 25090 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 25091 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 25092 * 25093 * @example 25094 <example> 25095 <file name="index.html"> 25096 <button ng-mouseup="count = count + 1" ng-init="count=0"> 25097 Increment (on mouse up) 25098 </button> 25099 count: {{count}} 25100 </file> 25101 </example> 25102 */ 25103 25104/** 25105 * @ngdoc directive 25106 * @name ngMouseover 25107 * 25108 * @description 25109 * Specify custom behavior on mouseover event. 25110 * 25111 * @element ANY 25112 * @priority 0 25113 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 25114 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 25115 * 25116 * @example 25117 <example> 25118 <file name="index.html"> 25119 <button ng-mouseover="count = count + 1" ng-init="count=0"> 25120 Increment (when mouse is over) 25121 </button> 25122 count: {{count}} 25123 </file> 25124 </example> 25125 */ 25126 25127 25128/** 25129 * @ngdoc directive 25130 * @name ngMouseenter 25131 * 25132 * @description 25133 * Specify custom behavior on mouseenter event. 25134 * 25135 * @element ANY 25136 * @priority 0 25137 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 25138 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 25139 * 25140 * @example 25141 <example> 25142 <file name="index.html"> 25143 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 25144 Increment (when mouse enters) 25145 </button> 25146 count: {{count}} 25147 </file> 25148 </example> 25149 */ 25150 25151 25152/** 25153 * @ngdoc directive 25154 * @name ngMouseleave 25155 * 25156 * @description 25157 * Specify custom behavior on mouseleave event. 25158 * 25159 * @element ANY 25160 * @priority 0 25161 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 25162 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 25163 * 25164 * @example 25165 <example> 25166 <file name="index.html"> 25167 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 25168 Increment (when mouse leaves) 25169 </button> 25170 count: {{count}} 25171 </file> 25172 </example> 25173 */ 25174 25175 25176/** 25177 * @ngdoc directive 25178 * @name ngMousemove 25179 * 25180 * @description 25181 * Specify custom behavior on mousemove event. 25182 * 25183 * @element ANY 25184 * @priority 0 25185 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 25186 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 25187 * 25188 * @example 25189 <example> 25190 <file name="index.html"> 25191 <button ng-mousemove="count = count + 1" ng-init="count=0"> 25192 Increment (when mouse moves) 25193 </button> 25194 count: {{count}} 25195 </file> 25196 </example> 25197 */ 25198 25199 25200/** 25201 * @ngdoc directive 25202 * @name ngKeydown 25203 * 25204 * @description 25205 * Specify custom behavior on keydown event. 25206 * 25207 * @element ANY 25208 * @priority 0 25209 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 25210 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 25211 * 25212 * @example 25213 <example> 25214 <file name="index.html"> 25215 <input ng-keydown="count = count + 1" ng-init="count=0"> 25216 key down count: {{count}} 25217 </file> 25218 </example> 25219 */ 25220 25221 25222/** 25223 * @ngdoc directive 25224 * @name ngKeyup 25225 * 25226 * @description 25227 * Specify custom behavior on keyup event. 25228 * 25229 * @element ANY 25230 * @priority 0 25231 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 25232 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 25233 * 25234 * @example 25235 <example> 25236 <file name="index.html"> 25237 <p>Typing in the input box below updates the key count</p> 25238 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 25239 25240 <p>Typing in the input box below updates the keycode</p> 25241 <input ng-keyup="event=$event"> 25242 <p>event keyCode: {{ event.keyCode }}</p> 25243 <p>event altKey: {{ event.altKey }}</p> 25244 </file> 25245 </example> 25246 */ 25247 25248 25249/** 25250 * @ngdoc directive 25251 * @name ngKeypress 25252 * 25253 * @description 25254 * Specify custom behavior on keypress event. 25255 * 25256 * @element ANY 25257 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 25258 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 25259 * and can be interrogated for keyCode, altKey, etc.) 25260 * 25261 * @example 25262 <example> 25263 <file name="index.html"> 25264 <input ng-keypress="count = count + 1" ng-init="count=0"> 25265 key press count: {{count}} 25266 </file> 25267 </example> 25268 */ 25269 25270 25271/** 25272 * @ngdoc directive 25273 * @name ngSubmit 25274 * 25275 * @description 25276 * Enables binding angular expressions to onsubmit events. 25277 * 25278 * Additionally it prevents the default action (which for form means
25278sending the request to the 25279 * server and reloading the current page), but only if the form does not contain `action`, 25280 * `data-action`, or `x-action` attributes. 25281 * 25282 * <div class="alert alert-warning"> 25283 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 25284 * `ngSubmit` handlers together. See the 25285 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 25286 * for a detailed discussion of when `ngSubmit` may be triggered. 25287 * </div> 25288 * 25289 * @element form 25290 * @priority 0 25291 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 25292 * ({@link guide/expression#-event- Event object is available as `$event`}) 25293 * 25294 * @example 25295 <example module="submitExample"> 25296 <file name="index.html"> 25297 <script> 25298 angular.module('submitExample', []) 25299 .controller('ExampleController', ['$scope', function($scope) { 25300 $scope.list = []; 25301 $scope.text = 'hello'; 25302 $scope.submit = function() { 25303 if ($scope.text) { 25304 $scope.list.push(this.text); 25305 $scope.text = ''; 25306 } 25307 }; 25308 }]); 25309 </script>
25310 <form ng-submit="submit()" ng-controller="ExampleController"> 25311 Enter text and hit enter: 25312 <input type="text" ng-model="text" name="text" /> 25313 <input type="submit" id="submit" value="Submit" /> 25314 <pre>list={{list}}</pre> 25315 </form> 25316 </file> 25317 <file name="protractor.js" type="protractor"> 25318 it('should check ng-submit', function() { 25319 expect(element(by.binding('list')).getText()).toBe('list=[]'); 25320 element(by.css('#submit')).click(); 25321 expect(element(by.binding('list')).getText()).toContain('hello'); 25322 expect(element(by.model('text')).getAttribute('value')).toBe(''); 25323 }); 25324 it('should ignore empty strings', function() { 25325 expect(element(by.binding('list')).getText()).toBe('list=[]'); 25326 element(by.css('#submit')).click(); 25327 element(by.css('#submit')).click(); 25328 expect(element(by.binding('list')).getText()).toContain('hello'); 25329 }); 25330 </file> 25331 </example> 25332 */ 25333 25334/** 25335 * @ngdoc directive 25336 * @name ngFocus 25337 * 25338 * @description 25339 * Specify custom behavior on focus event. 25340 * 25341 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 25342 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 25343 * during an `$apply` to ensure a consistent state. 25344 * 25345 * @element window, input, select, textarea, a 25346 * @priority 0 25347 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 25348 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 25349 * 25350 * @example 25351 * See {@link ng.directive:ngClick ngClick} 25352 */ 25353 25354/** 25355 * @ngdoc directive 25356 * @name ngBlur 25357 * 25358 * @description 25359 * Specify custom behavior on blur event. 25360 * 25361 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 25362 * an element has lost focus. 25363 * 25364 * Note: As the `blur` event is executed synchronously also during DOM manipulations 25365 * (e.g. removing a focussed input), 25366 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 25367 * during an `$apply` to ensure a consistent state. 25368 * 25369 * @element window, input, select, textarea, a 25370 * @priority 0 25371 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 25372 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 25373 * 25374 * @example 25375 * See {@link ng.directive:ngClick ngClick} 25376 */ 25377 25378/** 25379 * @ngdoc directive 25380 * @name ngCopy 25381 * 25382 * @description 25383 * Specify custom behavior on copy event. 25384 * 25385 * @element window, input, select, textarea, a 25386 * @priority 0 25387 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 25388 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 25389 * 25390 * @example 25391 <example> 25392 <file name="index.html"> 25393 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 25394 copied: {{copied}} 25395 </file> 25396 </example> 25397 */ 25398 25399/** 25400 * @ngdoc directive 25401 * @name ngCut 25402 * 25403 * @description 25404 * Specify custom behavior on cut event. 25405 * 25406 * @element window, input, select, textarea, a 25407 * @priority 0 25408 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 25409 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 25410 * 25411 * @example 25412 <example> 25413 <file name="index.html"> 25414 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 25415 cut: {{cut}} 25416 </file> 25417 </example> 25418 */ 25419 25420/** 25421 * @ngdoc directive 25422 * @name ngPaste 25423 * 25424 * @description 25425 * Specify custom behavior on paste event. 25426 * 25427 * @element window, input, select, textarea, a 25428 * @priority 0 25429 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 25430 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 25431 * 25432 * @example 25433 <example> 25434 <file name="index.html"> 25435 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 25436 pasted: {{paste}} 25437 </file> 25438 </example> 25439 */ 25440 25441/** 25442 * @ngdoc directive 25443 * @name ngIf 25444 * @restrict A 25445 * @multiElement 25446 * 25447 * @description 25448 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 25449 * {expression}. If the expression assigned to `ngIf` evaluates to a false 25450 * value then the element is removed from the DOM, otherwise a clone of the 25451 * element is reinserted into the DOM. 25452 * 25453 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 25454 * element in the DOM rather than changing its visibility via the `display` css property. A common 25455 * case when this difference is significant is when using css selectors that rely on an element's 25456 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
25456s. 25457 * 25458 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 25459 * is created when the element is restored. The scope created within `ngIf` inherits from 25460 * its parent scope using 25461 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 25462 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 25463 * a javascript primitive defined in the parent scope. In this case any modifications made to the 25464 * variable within the child scope will override (hide) the value in the parent scope. 25465 * 25466 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 25467 * is if an element's class attribute is directly modified after it's compiled, using something like 25468 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 25469 * the added class will be lost because the original compiled state is used to regenerate the element. 25470 * 25471 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 25472 * and `leave` effects. 25473 * 25474 * @animations 25475 * | Animation | Occurs | 25476 * |----------------------------------|-------------------------------------| 25477 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container | 25478 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM | 25479 * 25480 * @element ANY 25481 * @scope 25482 * @priority 600 25483 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 25484 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 25485 * element is added to the DOM tree. 25486 * 25487 * @example 25488 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 25489 <file name="index.html"> 25490 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 25491 Show when checked: 25492 <span ng-if="checked" class="animate-if"> 25493 This is removed when the checkbox is unchecked. 25494 </span> 25495 </file> 25496 <file name="animations.css"> 25497 .animate-if { 25498 background:white; 25499 border:1px solid black; 25500 padding:10px; 25501 } 25502 25503 .animate-if.ng-enter, .animate-if.ng-leave { 25504 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 25505 } 25506 25507 .animate-if.ng-enter, 25508 .animate-if.ng-leave.ng-leave-active { 25509 opacity:0; 25510 } 25511 25512 .animate-if.ng-leave, 25513 .animate-if.ng-enter.ng-enter-active { 25514 opacity:1; 25515 } 25516 </file> 25517 </example> 25518 */ 25519var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { 25520 return { 25521 multiElement: true, 25522 transclude: 'element', 25523 priority: 600, 25524 terminal: true, 25525 restrict: 'A', 25526 $$tlb: true, 25527 link: function($scope, $element, $attr, ctrl, $transclude) { 25528 var block, childScope, previousElements; 25529 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 25530 25531 if (value) { 25532 if (!childScope) { 25533 $transclude(function(clone, newScope) { 25534 childScope = newScope; 25535 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); 25536 // Note: We only need the first/last node of the cloned nodes. 25537 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 25538 // by a directive with templateUrl when its template arrives. 25539 block = { 25540 clone: clone 25541 }; 25542 $animate.enter(clone, $element.parent(), $element); 25543 }); 25544 } 25545 } else { 25546 if (previousElements) { 25547 previousElements.remove(); 25548 previousElements = null; 25549 } 25550 if (childScope) { 25551 childScope.$destroy(); 25552 childScope = null; 25553 } 25554 if (block) { 25555 previousElements = getBlockNodes(block.clone); 25556 $animate.leave(previousElements).then(function() { 25557 previousElements = null; 25558 }); 25559 block = null; 25560 } 25561 } 25562 }); 25563 } 25564 }; 25565}]; 25566 25567/** 25568 * @ngdoc directive 25569 * @name ngInclude 25570 * @restrict ECA 25571 * 25572 * @description 25573 * Fetches, compiles and includes an external HTML fragment. 25574 * 25575 * By default, the template URL is restricted to the same domain and protocol as the 25576 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 25577 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 25578 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 25579 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 25580 * ng.$sce Strict Contextual Escaping}. 25581 * 25582 * In addition, the browser's 25583 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 25584 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 25585 * policy may further restrict whether the template is successfully loaded. 25586 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 25587 * access on some browsers. 25588 * 25589 * @animations 25590 * | Animation | Occurs | 25591 * |----------------------------------|-------------------------------------| 25592 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include | 25593 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include | 25594 * 25595 * The enter and leave animation occur concurrently. 25596 * 25597 * @scope 25598 * @priority 400 25599 * 25600 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 25601 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 25602 * @param {string=} onload Expression to evaluate when a new partial is loaded. 25603 * <div class="alert alert-warning"> 25604 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 25605 * a function with the name on the window element, which will usually throw a 25606 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 25607 * different form that {@link guide/directive#normalization matches} `onload`. 25608 * </div> 25609 * 25610 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 25611 * $anchorScroll} to scroll the viewport after the content is loaded. 25612 * 25613 * - If the attribute is not set, disable scrolling. 25614 * - If the attribute is set without value, enable scrolling. 25615 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 25616 * 25617 * @example 25618 <example module="includeExample" deps="angular-animate.js" animations="true"> 25619 <file name="index.html">
25620 <div ng-controller="ExampleController"> 25621 <select ng-model="template" ng-options="t.name for t in templates"> 25622 <option value="">(blank)</option> 25623 </select> 25624 url of the template: <code>{{template.url}}</code> 25625 <hr/> 25626 <div class="slide-animate-container"> 25627 <div class="slide-animate" ng-include="template.url"></div> 25628 </div> 25629 </div> 25630 </file> 25631 <file name="script.js"> 25632 angular.module('includeExample', ['ngAnimate']) 25633 .controller('ExampleController', ['$scope', function($scope) { 25634 $scope.templates = 25635 [ { name: 'template1.html', url: 'template1.html'}, 25636 { name: 'template2.html', url: 'template2.html'} ]; 25637 $scope.template = $scope.templates[0]; 25638 }]); 25639 </file> 25640 <file name="template1.html"> 25641 Content of template1.html 25642 </file> 25643 <file name="template2.html"> 25644 Content of template2.html 25645 </file> 25646 <file name="animations.css"> 25647 .slide-animate-container { 25648 position:relative; 25649 background:white; 25650 border:1px solid black; 25651 height:40px; 25652 overflow:hidden; 25653 } 25654 25655 .slide-animate { 25656 padding:10px; 25657 } 25658 25659 .slide-animate.ng-enter, .slide-animate.ng-leave { 25660 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 25661 25662 position:absolute; 25663 top:0; 25664 left:0; 25665 right:0; 25666 bottom:0; 25667 display:block; 25668 padding:10px; 25669 } 25670 25671 .slide-animate.ng-enter { 25672 top:-50px; 25673 } 25674 .slide-animate.ng-enter.ng-enter-active { 25675 top:0; 25676 } 25677 25678 .slide-animate.ng-leave { 25679 top:0; 25680 } 25681 .slide-animate.ng-leave.ng-leave-active { 25682 top:50px; 25683 } 25684 </file> 25685 <file name="protractor.js" type="protractor"> 25686 var templateSelect = element(by.model('template')); 25687 var includeElem = element(by.css('[ng-include]')); 25688 25689 it('should load template1.html', function() { 25690 expect(includeElem.getText()).toMatch(/Content of template1.html/); 25691 }); 25692 25693 it('should load template2.html', function() { 25694 if (browser.params.browser == 'firefox') { 25695 // Firefox can't handle using selects 25696 // See https://github.com/angular/protractor/issues/480 25697 return; 25698 } 25699 templateSelect.click(); 25700 templateSelect.all(by.css('option')).get(2).click(); 25701 expect(includeElem.getText()).toMatch(/Content of template2.html/); 25702 }); 25703 25704 it('should change to blank', function() { 25705 if (browser.params.browser == 'firefox') { 25706 // Firefox can't handle using selects 25707 return; 25708 } 25709 templateSelect.click(); 25710 templateSelect.all(by.css('option')).get(0).click(); 25711 expect(includeElem.isPresent()).toBe(false); 25712 }); 25713 </file> 25714 </example> 25715 */ 25716 25717 25718/** 25719 * @ngdoc event 25720 * @name ngInclude#$includeContentRequested 25721 * @eventType emit on the scope ngInclude was declared in 25722 * @description 25723 * Emitted every time the ngInclude content is requested. 25724 * 25725 * @param {Object} angularEvent Synthetic event object. 25726 * @param {String} src URL of content to load. 25727 */ 25728 25729 25730/** 25731 * @ngdoc event 25732 * @name ngInclude#$includeContentLoaded 25733 * @eventType emit on the current ngInclude scope 25734 * @description 25735 * Emitted every time the ngInclude content is reloaded. 25736 * 25737 * @param {Object} angularEvent Synthetic event object. 25738 * @param {String} src URL of content to load. 25739 */ 25740 25741 25742/** 25743 * @ngdoc event 25744 * @name ngInclude#$includeContentError 25745 * @eventType emit on the scope ngInclude was declared in 25746 * @description 25747 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 25748 * 25749 * @param {Object} angularEvent Synthetic event object. 25750 * @param {String} src URL of content to load. 25751 */ 25752var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 25753 function($templateRequest, $anchorScroll, $animate) { 25754 return { 25755 restrict: 'ECA', 25756 priority: 400, 25757 terminal: true, 25758 transclude: 'element', 25759 controller: angular.noop, 25760 compile: function(element, attr) { 25761 var srcExp = attr.ngInclude || attr.src, 25762 onloadExp = attr.onload || '', 25763 autoScrollExp = attr.autoscroll; 25764 25765 return function(scope, $element, $attr, ctrl, $transclude) { 25766 var changeCounter = 0, 25767 currentScope, 25768 previousElement, 25769 currentElement; 25770 25771 var cleanupLastIncludeContent = function() { 25772 if (previousElement) { 25773 previousElement.remove(); 25774 previousElement = null; 25775 } 25776 if (currentScope) { 25777 currentScope.$destroy(); 25778 currentScope = null; 25779 } 25780 if (currentElement) { 25781 $animate.leave(currentElement).then(function() { 25782 previousElement = null; 25783 }); 25784 previousElement = currentElement; 25785 currentElement = null; 25786 } 25787 }; 25788 25789 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 25790 var afterAnimation = function() { 25791 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 25792 $anchorScroll(); 25793 } 25794 }; 25795 var thisChangeId = ++changeCounter; 25796 25797 if (src) { 25798 //set the 2nd param to true to ignore the template request error so that the inner 25799 //contents and scope can be cleaned up. 25800 $templateRequest(src, true).then(function(response) { 25801 if (scope.$$destroyed) return; 25802 25803 if (thisChangeId !== changeCounter) return; 25804 var newScope = scope.$new(); 25805 ctrl.template = response; 25806 25807 // Note: This will also link all children of ng-include that were contained in the original 25808 // html. If that content contains controllers, ... they could pollute/change the scope. 25809 // However, using ng-include on an element with additional content does not make sense... 25810 // Note: We can't remove them in the cloneAttchFn of $transclude as that 25811 // function is called before linking the content, which would apply child 25812 // directives to non existing elements. 25813 var clone = $transclude(newScope, function(clone) { 25814 cleanupLastIncludeContent(); 25815 $animate.enter(clone, null, $element).then(afterAnimation); 25816 }); 25817 25818 currentScope = newScope; 25819 currentElement = clone; 25820 25821 currentScope.$emit('$includeContentLoaded', src); 25822 scope.$eval(onloadExp); 25823 }, function() { 25824 if (scope.$$destroyed) return; 25825 25826 if (thisChangeId === changeCounter) { 25827 cleanupLastIncludeContent();
vendor: 2,842 bytes, lines 25828-25910
25828 scope.$emit('$includeContentError', src); 25829 } 25830 }); 25831 scope.$emit('$includeContentRequested', src); 25832 } else { 25833 cleanupLastIncludeContent(); 25834 ctrl.template = null; 25835 } 25836 }); 25837 }; 25838 } 25839 }; 25840}]; 25841 25842// This directive is called during the $transclude call of the first `ngInclude` directive. 25843// It will replace and compile the content of the element with the loaded template. 25844// We need this directive so that the element content is already filled when 25845// the link function of another directive on the same element as ngInclude 25846// is called. 25847var ngIncludeFillContentDirective = ['$compile', 25848 function($compile) { 25849 return { 25850 restrict: 'ECA', 25851 priority: -400, 25852 require: 'ngInclude', 25853 link: function(scope, $element, $attr, ctrl) { 25854 if (toString.call($element[0]).match(/SVG/)) { 25855 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 25856 // support innerHTML, so detect this here and try to generate the contents 25857 // specially. 25858 $element.empty(); 25859 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope, 25860 function namespaceAdaptedClone(clone) { 25861 $element.append(clone); 25862 }, {futureParentElement: $element}); 25863 return; 25864 } 25865 25866 $element.html(ctrl.template); 25867 $compile($element.contents())(scope); 25868 } 25869 }; 25870 }]; 25871 25872/** 25873 * @ngdoc directive 25874 * @name ngInit 25875 * @restrict AC 25876 * 25877 * @description 25878 * The `ngInit` directive allows you to evaluate an expression in the 25879 * current scope. 25880 * 25881 * <div class="alert alert-danger"> 25882 * This directive can be abused to add unnecessary amounts of logic into your templates. 25883 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 25884 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 25885 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 25886 * rather than `ngInit` to initialize values on a scope. 25887 * </div> 25888 * 25889 * <div class="alert alert-warning"> 25890 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 25891 * sure you have parentheses to ensure correct operator precedence: 25892 * <pre class="prettyprint"> 25893 * `<div ng-init="test1 = ($index | toString)"></div>` 25894 * </pre> 25895 * </div> 25896 * 25897 * @priority 450 25898 * 25899 * @element ANY 25900 * @param {expression} ngInit {@link guide/expression Expression} to eval. 25901 * 25902 * @example 25903 <example module="initExample"> 25904 <file name="index.html"> 25905 <script> 25906 angular.module('initExample', []) 25907 .controller('ExampleController', ['$scope', function($scope) { 25908 $scope.list = [['a', 'b'], ['c', 'd']]; 25909 }]); 25910 </script>
25911 <div ng-controller="ExampleController"> 25912 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 25913 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 25914 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 25915 </div> 25916 </div> 25917 </div> 25918 </file> 25919 <file name="protractor.js" type="protractor"> 25920 it('should alias index positions', function() { 25921 var elements = element.all(by.css('.example-init')); 25922 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 25923 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 25924 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 25925 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 25926 }); 25927 </file> 25928 </example> 25929 */ 25930var ngInitDirective = ngDirective({ 25931 priority: 450, 25932 compile: function() { 25933 return { 25934 pre: function(scope, element, attrs) { 25935 scope.$eval(attrs.ngInit); 25936 } 25937 }; 25938 } 25939}); 25940 25941/** 25942 * @ngdoc directive 25943 * @name ngList 25944 * 25945 * @description 25946 * Text input that converts between a delimited string and an array of strings. The default 25947 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 25948 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 25949 * 25950 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 25951 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 25952 * list item is respected. This implies that the user of the directive is responsible for 25953 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 25954 * tab or newline character. 25955 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 25956 * when joining the list items back together) and whitespace around each list item is stripped 25957 * before it is added to the model. 25958 * 25959 * ### Example with Validation 25960 * 25961 * <example name="ngList-directive" module="listExample"> 25962 * <file name="app.js"> 25963 * angular.module('listExample', []) 25964 * .controller('ExampleController', ['$scope', function($scope) { 25965 * $scope.names = ['morpheus', 'neo', 'trinity']; 25966 * }]); 25967 * </file> 25968 * <file name="index.html">
25969 * <form name="myForm" ng-controller="ExampleController"> 25970 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 25971 * <span role="alert"> 25972 * <span class="error" ng-show="myForm.namesInput.$error.required"> 25973 * Required!</span> 25974 * </span> 25975 * <br> 25976 * <tt>names = {{names}}</tt><br/> 25977 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 25978 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 25979 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25980 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25981 * </form> 25982 * </file> 25983 * <file name="protractor.js" type="protractor"> 25984 * var listInput = element(by.model('names')); 25985 * var names = element(by.exactBinding('names')); 25986 * var valid = element(by.binding('myForm.namesInput.$valid')); 25987 * var error = element(by.css('span.error')); 25988 * 25989 * it('should initialize to model', function() { 25990 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 25991 * expect(valid.getText()).toContain('true'); 25992 * expect(error.getCssValue('display')).toBe('none'); 25993 * }); 25994 * 25995 * it('should be invalid if empty', function() { 25996 * listInput.clear(); 25997 * listInput.sendKeys(''); 25998 * 25999 * expect(names.getText()).toContain(''); 26000 * expect(valid.getText()).toContain('false'); 26001 * expect(error.getCssValue('display')).not.toBe('none'); 26002 * }); 26003 * </file> 26004 * </example> 26005 * 26006 * ### Example - splitting on newline 26007 * <example name="ngList-directive-newlines"> 26008 * <file name="index.html"> 26009 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 26010 * <pre>{{ list | json }}</pre> 26011 * </file> 26012 * <file name="protractor.js" type="protractor"> 26013 * it("should split the text by newlines", function() { 26014 * var listInput = element(by.model('list')); 26015 * var output = element(by.binding('list | json')); 26016 * listInput.sendKeys('abc\ndef\nghi'); 26017 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 26018 * }); 26019 * </file> 26020 * </example> 26021 * 26022 * @element input 26023 * @param {string=} ngList optional delimiter that should be used to split the value. 26024 */ 26025var ngListDirective = function() { 26026 return { 26027 restrict: 'A', 26028 priority: 100, 26029 require: 'ngModel', 26030 link: function(scope, element, attr, ctrl) { 26031 // We want to control whitespace trimming so we use this convoluted approach 26032 // to access the ngList attribute, which doesn't pre-trim the attribute 26033 var ngList = element.attr(attr.$attr.ngList) || ', '; 26034 var trimValues = attr.ngTrim !== 'false'; 26035 var separator = trimValues ? trim(ngList) : ngList; 26036 26037 var parse = function(viewValue) { 26038 // If the viewValue is invalid (say required but empty) it will be `undefined` 26039 if (isUndefined(viewValue)) return; 26040 26041 var list = []; 26042 26043 if (viewValue) { 26044 forEach(viewValue.split(separator), function(value) { 26045 if (value) list.push(trimValues ? trim(value) : value); 26046 }); 26047 } 26048 26049 return list; 26050 }; 26051 26052 ctrl.$parsers.push(parse); 26053 ctrl.$formatters.push(function(value) { 26054 if (isArray(value)) { 26055 return value.join(ngList); 26056 } 26057 26058 return undefined; 26059 }); 26060 26061 // Override the standard $isEmpty because an empty array means the input is empty. 26062 ctrl.$isEmpty = function(value) { 26063 return !value || !value.length; 26064 }; 26065 } 26066 }; 26067}; 26068 26069/* global VALID_CLASS: true, 26070 INVALID_CLASS: true, 26071 PRISTINE_CLASS: true, 26072 DIRTY_CLASS: true, 26073 UNTOUCHED_CLASS: true, 26074 TOUCHED_CLASS: true, 26075*/ 26076 26077var VALID_CLASS = 'ng-valid', 26078 INVALID_CLASS = 'ng-invalid', 26079 PRISTINE_CLASS = 'ng-pristine', 26080 DIRTY_CLASS = 'ng-dirty', 26081 UNTOUCHED_CLASS = 'ng-untouched', 26082 TOUCHED_CLASS = 'ng-touched', 26083 PENDING_CLASS = 'ng-pending', 26084 EMPTY_CLASS = 'ng-empty', 26085 NOT_EMPTY_CLASS = 'ng-not-empty'; 26086 26087var ngModelMinErr = minErr('ngModel'); 26088 26089/** 26090 * @ngdoc type 26091 * @name ngModel.NgModelController 26092 * 26093 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 26094 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 26095 * is set. 26096 * @property {*} $modelValue The value in the model that the control is bound to. 26097 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 26098 the control reads value from the DOM. The functions are called in array order, each passing 26099 its return value through to the next. The last return value is forwarded to the 26100 {@link ngModel.NgModelController#$validators `$validators`} collection. 26101 26102Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 26103`$viewValue`}. 26104 26105Returning `undefined` from a parser means a parse error occurred. In that case, 26106no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 26107will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 26108is set to `true`. The parse error is stored in `ngModel.$error.parse`. 26109 26110 * 26111 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 26112 the model value changes. The functions are called in reverse array order, each passing the value through to the 26113 next. The last return value is used as the actual DOM value. 26114 Used to format / convert values for display in the control. 26115 * ```js 26116 * function formatter(value) { 26117 * if (value) { 26118 * return value.toUpperCase(); 26119 * } 26120 * } 26121 * ngModel.$formatters.push(formatter); 26122 * ``` 26123 * 26124 * @property {Object.<string, function>} $validators A collection of validators that are applied 26125 * whenever the model value changes. The key value within the object refers to the name of the 26126 * validator while the function refers to the validation operation. The validation operation is 26127 * provided with the model value as an argument and must return a true or false value depending 26128 * on the response of that validation. 26129 * 26130 * ```js 26131 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 26132 * var value = modelValue || viewValue; 26133 * return /[0-9]+/.test(value) && 26134 * /[a-z]+/.test(value) && 26135 * /[A-Z]+/.test(value) && 26136 * /\W+/.test(value); 26137 * }; 26138 * ``` 26139 * 26140 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 26141 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 26142 * is expected to return a promise when it is run during the model validation process. Once the promise 26143 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 26144 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 26145 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 26146 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 26147 * will only run once all synchronous validators have passed. 26148 * 26149 * Please note that if $http is used then it is important that the server returns a success HTTP response code 26150 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 26151 * 26152 * ```js 26153 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 26154 * var value = modelValue || viewValue; 26155 * 26156 * // Lookup user by username 26157 * return $http.get('/api/users/' + value). 26158 * then(function resolved() { 26159 * //username exists, this means validation fails 26160 * return $q.reject('exists'); 26161 * }, function rejected() { 26162 * //username does not exist, therefore this validation passes 26163 * return true; 26164 * }); 26165 * }; 26166 * ``` 26167 * 26168 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 26169 * view value has changed. It is called with no arguments, and its return value is ignored. 26170 * This can be used in place of additional $watches against the model value. 26171 * 26172 * @property {Object} $error An object hash with all failing validator ids as keys. 26173 * @property {Object} $pending An object hash with all pending validator ids as keys. 26174 * 26175 * @property {boolean} $untouched True if control has not lost focus yet. 26176 * @property {boolean} $touched True if control has lost focus. 26177 * @property {boolean} $pristine True if user has not interacted with the control yet. 26178 * @property {boolean} $dirty True if user has already interacted with the control. 26179 * @property {boolean} $valid True if there is no error. 26180 * @property {boolean}
26180 $invalid True if at least one error on the control. 26181 * @property {string} $name The name attribute of the control. 26182 * 26183 * @description 26184 * 26185 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 26186 * The controller contains services for data-binding, validation, CSS updates, and value formatting 26187 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 26188 * listening to DOM events. 26189 * Such DOM related logic should be provided by other directives which make use of 26190 * `NgModelController` for data-binding to control elements. 26191 * Angular provides this DOM logic for most {@link input `input`} elements. 26192 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 26193 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 26194 * 26195 * @example 26196 * ### Custom Control Example 26197 * This example shows how to use `NgModelController` with a custom control to achieve 26198 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 26199 * collaborate together to achieve the desired result. 26200 * 26201 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 26202 * contents be edited in place by the user. 26203 * 26204 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 26205 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 26206 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 26207 * that content using the `$sce` service. 26208 * 26209 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 26210 <file name="style.css"> 26211 [contenteditable] { 26212 border: 1px solid black; 26213 background-color: white; 26214 min-height: 20px; 26215 } 26216 26217 .ng-invalid { 26218 border: 1px solid red; 26219 } 26220 26221 </file> 26222 <file name="script.js"> 26223 angular.module('customControl', ['ngSanitize']). 26224 directive('contenteditable', ['$sce', function($sce) { 26225 return { 26226 restrict: 'A', // only activate on element attribute 26227 require: '?ngModel', // get a hold of NgModelController 26228 link: function(scope, element, attrs, ngModel) { 26229 if (!ngModel) return; // do nothing if no ng-model 26230 26231 // Specify how UI should be updated 26232 ngModel.$render = function() { 26233 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 26234 }; 26235 26236 // Listen for change events to enable binding 26237 element.on('blur keyup change', function() { 26238 scope.$evalAsync(read); 26239 }); 26240 read(); // initialize 26241 26242 // Write data to the model 26243 function read() { 26244 var html = element.html(); 26245 // When we clear the content editable the browser leaves a <br> behind 26246 // If strip-br attribute is provided then we strip this out 26247 if ( attrs.stripBr && html == '<br>' ) { 26248 html = ''; 26249 } 26250 ngModel.$setViewValue(html); 26251 } 26252 } 26253 }; 26254 }]); 26255 </file> 26256 <file name="index.html"> 26257 <form name="myForm"> 26258 <div contenteditable 26259 name="myWidget" ng-model="userContent" 26260 strip-br="true" 26261 required>Change me!</div> 26262 <span ng-show="myForm.myWidget.$error.required">Required!</span> 26263 <hr> 26264 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 26265 </form> 26266 </file> 26267 <file name="protractor.js" type="protractor"> 26268 it('should data-bind and become invalid', function() { 26269 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 26270 // SafariDriver can't handle contenteditable 26271 // and Firefox driver can't clear contenteditables very well 26272 return; 26273 } 26274 var contentEditable = element(by.css('[contenteditable]')); 26275 var content = 'Change me!'; 26276 26277 expect(contentEditable.getText()).toEqual(content); 26278 26279 contentEditable.clear(); 26280 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 26281 expect(contentEditable.getText()).toEqual(''); 26282 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 26283 }); 26284 </file> 26285 * </example> 26286 * 26287 * 26288 */ 26289var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 26290 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 26291 this.$viewValue = Number.NaN; 26292 this.$modelValue = Number.NaN; 26293 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 26294 this.$validators = {}; 26295 this.$asyncValidators = {}; 26296 this.$parsers = []; 26297 this.$formatters = []; 26298 this.$viewChangeListeners = []; 26299 this.$untouched = true; 26300 this.$touched = false;
26301 this.$pristine = true; 26302 this.$dirty = false; 26303 this.$valid = true; 26304 this.$invalid = false; 26305 this.$error = {}; // keep invalid keys here 26306 this.$$success = {}; // keep valid keys here 26307 this.$pending = undefined; // keep pending keys here 26308 this.$name = $interpolate($attr.name || '', false)($scope); 26309 this.$$parentForm = nullFormCtrl; 26310 26311 var parsedNgModel = $parse($attr.ngModel), 26312 parsedNgModelAssign = parsedNgModel.assign, 26313 ngModelGet = parsedNgModel, 26314 ngModelSet = parsedNgModelAssign, 26315 pendingDebounce = null, 26316 parserValid, 26317 ctrl = this; 26318 26319 this.$$setOptions = function(options) { 26320 ctrl.$options = options; 26321 if (options && options.getterSetter) { 26322 var invokeModelGetter = $parse($attr.ngModel + '()'), 26323 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 26324 26325 ngModelGet = function($scope) { 26326 var modelValue = parsedNgModel($scope); 26327 if (isFunction(modelValue)) { 26328 modelValue = invokeModelGetter($scope); 26329 } 26330 return modelValue; 26331 }; 26332 ngModelSet = function($scope, newValue) { 26333 if (isFunction(parsedNgModel($scope))) { 26334 invokeModelSetter($scope, {$$$p: newValue}); 26335 } else { 26336 parsedNgModelAssign($scope, newValue); 26337 } 26338 }; 26339 } else if (!parsedNgModel.assign) { 26340 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 26341 $attr.ngModel, startingTag($element)); 26342 } 26343 }; 26344 26345 /** 26346 * @ngdoc method 26347 * @name ngModel.NgModelController#$render 26348 * 26349 * @description 26350 * Called when the view needs to be updated. It is expected that the user of the ng-model 26351 * directive will implement this method. 26352 * 26353 * The `$render()` method is invoked in the following situations: 26354 * 26355 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 26356 * committed value then `$render()` is called to update the input control. 26357 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 26358 * the `$viewValue` are different from last time. 26359 * 26360 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 26361 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` 26362 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 26363 * invoked if you only change a property on the objects. 26364 */ 26365 this.$render = noop; 26366 26367 /** 26368 * @ngdoc method 26369 * @name ngModel.NgModelController#$isEmpty 26370 * 26371 * @description 26372 * This is called when we need to determine if the value of an input is empty. 26373 * 26374 * For instance, the required directive does this to work out if the input has data or not. 26375 * 26376 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 26377 * 26378 * You can override this for input directives whose concept of being empty is different from the 26379 * default. The `checkboxInputType` directive does this because in its case a value of `false` 26380 * implies empty. 26381 * 26382 * @param {*} value The value of the input to check for emptiness. 26383 * @returns {boolean} True if `value` is "empty". 26384 */ 26385 this.$isEmpty = function(value) { 26386 return isUndefined(value) || value === '' || value === null || value !== value; 26387 }; 26388 26389 this.$$updateEmptyClasses = function(value) { 26390 if (ctrl.$isEmpty(value)) { 26391 $animate.removeClass($element, NOT_EMPTY_CLASS); 26392 $animate.addClass($element, EMPTY_CLASS); 26393 } else { 26394 $animate.removeClass($element, EMPTY_CLASS); 26395 $animate.addClass($element, NOT_EMPTY_CLASS); 26396 } 26397 }; 26398 26399 26400 var currentValidationRunId = 0; 26401 26402 /** 26403 * @ngdoc method 26404 * @name ngModel.NgModelController#$setValidity 26405 * 26406 * @description 26407 * Change the validity state, and notify the form. 26408 * 26409 * This method can be called within $parsers/$formatters or a custom validation implementation. 26410 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 26411 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 26412 * 26413 * @param {string}
26413 validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 26414 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 26415 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 26416 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 26417 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 26418 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 26419 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 26420 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 26421 * Skipped is used by Angular when validators do not run because of parse errors and 26422 * when `$asyncValidators` do not run because any of the `$validators` failed. 26423 */ 26424 addSetValidityMethod({ 26425 ctrl: this, 26426 $element: $element, 26427 set: function(object, property) { 26428 object[property] = true; 26429 }, 26430 unset: function(object, property) { 26431 delete object[property]; 26432 }, 26433 $animate: $animate 26434 }); 26435 26436 /** 26437 * @ngdoc method 26438 * @name ngModel.NgModelController#$setPristine 26439 * 26440 * @description 26441 * Sets the control to its pristine state. 26442 * 26443 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 26444 * state (`ng-pristine` class). A model is considered to be pristine when the control 26445 * has not been changed from when first compiled. 26446 */ 26447 this.$setPristine = function() { 26448 ctrl.$dirty = false; 26449 ctrl.$pristine = true; 26450 $animate.removeClass($element, DIRTY_CLASS); 26451 $animate.addClass($element, PRISTINE_CLASS); 26452 }; 26453 26454 /** 26455 * @ngdoc method 26456 * @name ngModel.NgModelController#$setDirty 26457 * 26458 * @description 26459 * Sets the control to its dirty state. 26460 * 26461 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 26462 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 26463 * from when first compiled. 26464 */ 26465 this.$setDirty = function() { 26466 ctrl.$dirty = true; 26467 ctrl.$pristine = false; 26468 $animate.removeClass($element, PRISTINE_CLASS); 26469 $animate.addClass($element, DIRTY_CLASS); 26470 ctrl.$$parentForm.$setDirty(); 26471 }; 26472 26473 /** 26474 * @ngdoc method 26475 * @name ngModel.NgModelController#$setUntouched 26476 * 26477 * @description 26478 * Sets the control to its untouched state. 26479 * 26480 * This method can be called to remove the `ng-touched` class and set the control to its 26481 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 26482 * by default, however this function can be used to restore that state if the model has 26483 * already been touched by the user. 26484 */ 26485 this.$setUntouched = function() { 26486 ctrl.$touched = false; 26487 ctrl.$untouched = true; 26488 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 26489 }; 26490 26491 /** 26492 * @ngdoc method 26493 * @name ngModel.NgModelController#$setTouched 26494 * 26495 * @description 26496 * Sets the control to its touched state. 26497 * 26498 * This method can be called to remove the `ng-untouched` class and set the control to its 26499 * touched state (`ng-touched` class). A model is considered to be touched when the user has 26500 * first focused the control element and then shifted focus away from the control (blur event). 26501 */ 26502 this.$setTouched = function() { 26503 ctrl.$touched = true; 26504 ctrl.$untouched = false; 26505 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 26506 }; 26507 26508 /** 26509 * @ngdoc method 26510 * @name ngModel.NgModelController#$rollbackViewValue 26511 * 26512 * @description 26513 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 26514 * which may be caused by a pending debounced event or because the input is waiting for a some 26515 * future event. 26516 * 26517 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 26518 * depend on special events such as blur, you can have a situation where there is a period when 26519 * the `$viewValue` is out of sync with the ngModel's `$modelValue`. 26520 * 26521 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 26522 * and reset the input to the last committed view value. 26523 * 26524 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 26525 * programmatically before these debounced/future events have resolved/occurred, because Angular's 26526 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 26527 * 26528 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 26529 * input which may have such events pending. This is important in order to make sure that the 26530 * input field will be updated with the new model value and any pending operations are cancelled. 26531 * 26532 * <example name="ng-model-cancel-update" module="cancel-update-example"> 26533 * <file name="app.js">
26534 * angular.module('cancel-update-example', []) 26535 * 26536 * .controller('CancelUpdateController', ['$scope', function($scope) { 26537 * $scope.model = {}; 26538 * 26539 * $scope.setEmpty = function(e, value, rollback) { 26540 * if (e.keyCode == 27) { 26541 * e.preventDefault(); 26542 * if (rollback) { 26543 * $scope.myForm[value].$rollbackViewValue(); 26544 * } 26545 * $scope.model[value] = ''; 26546 * } 26547 * }; 26548 * }]); 26549 * </file> 26550 * <file name="index.html"> 26551 * <div ng-controller="CancelUpdateController"> 26552 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 26553 * empty them. Follow these steps and observe the difference:</p> 26554 * <ol> 26555 * <li>Type something in the input. You will see that the model is not yet updated</li> 26556 * <li>Press the Escape key. 26557 * <ol> 26558 * <li> In the first example, nothing happens, because the model is already '', and no 26559 * update is detected. If you blur the input, the model will be set to the current view. 26560 * </li> 26561 * <li> In the second example, the pending update is cancelled, and the input is set back 26562 * to the last committed view value (''). Blurring the input does nothing. 26563 * </li> 26564 * </ol> 26565 * </li> 26566 * </ol> 26567 * 26568 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 26569 * <div> 26570 * <p id="inputDescription1">Without $rollbackViewValue():</p> 26571 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 26572 * ng-keydown="setEmpty($event, 'value1')"> 26573 * value1: "{{ model.value1 }}" 26574 * </div> 26575 * 26576 * <div> 26577 * <p id="inputDescription2">With $rollbackViewValue():</p> 26578 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 26579 * ng-keydown="setEmpty($event, 'value2', true)"> 26580 * value2: "{{ model.value2 }}" 26581 * </div> 26582 * </form> 26583 * </div> 26584 * </file> 26585 <file name="style.css"> 26586 div { 26587 display: table-cell; 26588 } 26589 div:nth-child(1) { 26590 padding-right: 30px; 26591 } 26592 26593 </file> 26594 * </example> 26595 */ 26596 this.$rollbackViewValue = function() { 26597 $timeout.cancel(pendingDebounce); 26598 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 26599 ctrl.$render(); 26600 }; 26601 26602 /** 26603 * @ngdoc method 26604 * @name ngModel.NgModelController#$validate 26605 * 26606 * @description 26607 * Runs each of the registered validators (first synchronous validators and then 26608 * asynchronous validators). 26609 * If the validity changes to invalid, the model will be set to `undefined`, 26610 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 26611 * If the validity changes to valid, it will set the model to the last available valid 26612 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 26613 */ 26614 this.$validate = function() { 26615 // ignore $validate before model is initialized 26616 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 26617 return; 26618 } 26619 26620 var viewValue = ctrl.$$lastCommittedViewValue; 26621 // Note: we use the $$rawModelValue as $modelValue might have been 26622 // set to undefined during a view -> model update that found validation 26623 // errors. We can't parse the view here, since that could change 26624 // the model although neither viewValue nor the model on the scope changed 26625 var modelValue = ctrl.$$rawModelValue; 26626 26627 var prevValid = ctrl.$valid; 26628 var prevModelValue = ctrl.$modelValue; 26629 26630 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 26631 26632 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 26633 // If there was no change in validity, don't update the model 26634 // This prevents changing an invalid modelValue to undefined 26635 if (!allowInvalid && prevValid !== allValid) { 26636 // Note: Don't check ctrl.$valid here, as we could have 26637 // external validators (e.g. calculated on the server), 26638 // that just call $setValidity and need the model value 26639 // to calculate their validity. 26640 ctrl.$modelValue = allValid ? modelValue : undefined; 26641 26642 if (ctrl.$modelValue !== prevModelValue) { 26643 ctrl.$$writeModelToScope(); 26644 } 26645 } 26646 }); 26647 26648 }; 26649 26650 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 26651 currentValidationRunId++; 26652 var localValidationRunId = currentValidationRunId; 26653 26654 // check parser error 26655 if (!processParseErrors()) { 26656 validationDone(false); 26657 return; 26658 } 26659 if (!processSyncValidators()) { 26660 validationDone(false); 26661 return; 26662 } 26663 processAsyncValidators(); 26664 26665 function processParseErrors() { 26666 var errorKey = ctrl.$$parserName || 'parse'; 26667 if (isUndefined(parserValid)) {
26668 setValidity(errorKey, null); 26669 } else { 26670 if (!parserValid) { 26671 forEach(ctrl.$validators, function(v, name) { 26672 setValidity(name, null); 26673 }); 26674 forEach(ctrl.$asyncValidators, function(v, name) { 26675 setValidity(name, null); 26676 }); 26677 } 26678 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 26679 setValidity(errorKey, parserValid); 26680 return parserValid; 26681 } 26682 return true; 26683 } 26684 26685 function processSyncValidators() { 26686 var syncValidatorsValid = true; 26687 forEach(ctrl.$validators, function(validator, name) { 26688 var result = validator(modelValue, viewValue); 26689 syncValidatorsValid = syncValidatorsValid && result; 26690 setValidity(name, result); 26691 }); 26692 if (!syncValidatorsValid) { 26693 forEach(ctrl.$asyncValidators, function(v, name) { 26694 setValidity(name, null); 26695 }); 26696 return false; 26697 } 26698 return true; 26699 } 26700 26701 function processAsyncValidators() { 26702 var validatorPromises = []; 26703 var allValid = true; 26704 forEach(ctrl.$asyncValidators, function(validator, name) { 26705 var promise = validator(modelValue, viewValue); 26706 if (!isPromiseLike(promise)) { 26707 throw ngModelMinErr('nopromise', 26708 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 26709 } 26710 setValidity(name, undefined); 26711 validatorPromises.push(promise.then(function() { 26712 setValidity(name, true); 26713 }, function() { 26714 allValid = false; 26715 setValidity(name, false); 26716 })); 26717 }); 26718 if (!validatorPromises.length) { 26719 validationDone(true); 26720 } else { 26721 $q.all(validatorPromises).then(function() { 26722 validationDone(allValid); 26723 }, noop); 26724 } 26725 } 26726 26727 function setValidity(name, isValid) { 26728 if (localValidationRunId === currentValidationRunId) { 26729 ctrl.$setValidity(name, isValid); 26730 } 26731 } 26732 26733 function validationDone(allValid) { 26734 if (localValidationRunId === currentValidationRunId) { 26735 26736 doneCallback(allValid); 26737 } 26738 } 26739 }; 26740 26741 /** 26742 * @ngdoc method 26743 * @name ngModel.NgModelController#$commitViewValue 26744 * 26745 * @description 26746 * Commit a pending update to the `$modelValue`. 26747 * 26748 * Updates may be pending by a debounced event or because the input is waiting for a some future 26749 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 26750 * usually handles calling this in response to input events. 26751 */ 26752 this.$commitViewValue = function() { 26753 var viewValue = ctrl.$viewValue; 26754 26755 $timeout.cancel(pendingDebounce); 26756 26757 // If the view value has not changed then we should just exit, except in the case where there is 26758 // a native validator on the element. In this case the validation state may have changed even though 26759 // the viewValue has stayed empty. 26760 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 26761 return; 26762 } 26763 ctrl.$$updateEmptyClasses(viewValue); 26764 ctrl.$$lastCommittedViewValue = viewValue; 26765 26766 // change to dirty 26767 if (ctrl.$pristine) { 26768 this.$setDirty(); 26769 } 26770 this.$$parseAndValidate(); 26771 }; 26772 26773 this.$$parseAndValidate = function() { 26774 var viewValue = ctrl.$$lastCommittedViewValue; 26775 var modelValue = viewValue; 26776 parserValid = isUndefined(modelValue) ? undefined : true; 26777 26778 if (parserValid) { 26779 for (var i = 0; i < ctrl.$parsers.length; i++) { 26780 modelValue = ctrl.$parsers[i](modelValue); 26781 if (isUndefined(modelValue)) { 26782 parserValid = false; 26783 break; 26784 } 26785 } 26786 } 26787 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 26788 // ctrl.$modelValue has not been touched yet... 26789 ctrl.$modelValue = ngModelGet($scope); 26790 } 26791 var prevModelValue = ctrl.$modelValue; 26792 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 26793 ctrl.$$rawModelValue = modelValue; 26794 26795 if (allowInvalid) { 26796 ctrl.$modelValue = modelValue; 26797 writeToModelIfNeeded(); 26798 } 26799 26800 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 26801 // This can happen if e.g. $setViewValue is called from inside a parser 26802 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 26803 if (!allowInvalid) { 26804 // Note: Don't check ctrl.$valid here, as we could have 26805 // external validators (e.g. calculated on the server), 26806 // that just call $setValidity and need the model value 26807 // to calculate their validity. 26808 ctrl.$modelValue = allValid ? modelValue : undefined; 26809 writeToModelIfNeeded(); 26810 } 26811 }); 26812 26813 function writeToModelIfNeeded() { 26814 if (ctrl.$modelValue !== prevModelValue) { 26815 ctrl.$$writeModelToScope(); 26816 } 26817 } 26818 }; 26819 26820 this.$$writeModelToScope = function() { 26821 ngModelSet($scope, ctrl.$modelValue);
26822 forEach(ctrl.$viewChangeListeners, function(listener) { 26823 try { 26824 listener(); 26825 } catch (e) { 26826 $exceptionHandler(e); 26827 } 26828 }); 26829 }; 26830 26831 /** 26832 * @ngdoc method 26833 * @name ngModel.NgModelController#$setViewValue 26834 * 26835 * @description 26836 * Update the view value. 26837 * 26838 * This method should be called when a control wants to change the view value; typically, 26839 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 26840 * directive calls it when the value of the input changes and {@link ng.directive:select select} 26841 * calls it when an option is selected. 26842 * 26843 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 26844 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 26845 * value sent directly for processing, finally to be applied to `$modelValue` and then the 26846 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 26847 * in the `$viewChangeListeners` list, are called. 26848 * 26849 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 26850 * and the `default` trigger is not listed, all those actions will remain pending until one of the 26851 * `updateOn` events is triggered on the DOM element. 26852 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 26853 * directive is used with a custom debounce for this particular event. 26854 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 26855 * is specified, once the timer runs out. 26856 * 26857 * When used with standard inputs, the view value will always be a string (which is in some cases 26858 * parsed into another type, such as a `Date` object for `input[date]`.) 26859 * However, custom controls might also pass objects to this method. In this case, we should make 26860 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 26861 * perform a deep watch of objects, it only looks for a change of identity. If you only change 26862 * the property of the object then ngModel will not realize that the object has changed and 26863 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 26864 * not change properties of the copy once it has been passed to `$setViewValue`. 26865 * Otherwise you may cause the model value on the scope to change incorrectly. 26866 * 26867 * <div class="alert alert-info"> 26868 * In any case, the value passed to the method should always reflect the current value 26869 * of the control. For example, if you are calling `$setViewValue` for an input element, 26870 * you should pass the input DOM value. Otherwise, the control and the scope model become 26871 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 26872 * the control's DOM value in any way. If we want to change the control's DOM value 26873 * programmatically, we should update the `ngModel` scope expression. Its new value will be 26874 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 26875 * to update the DOM, and finally call `$validate` on it. 26876 * </div> 26877 * 26878 * @param {*} value value from the view. 26879 * @param {string} trigger Event that triggered the update. 26880 */ 26881 this.$setViewValue = function(value, trigger) { 26882 ctrl.$viewValue = value; 26883 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 26884 ctrl.$$debounceViewValueCommit(trigger); 26885 } 26886 }; 26887 26888 this.$$debounceViewValueCommit = function(trigger) { 26889 var debounceDelay = 0, 26890 options = ctrl.$options, 26891 debounce; 26892 26893 if (options && isDefined(options.debounce)) { 26894 debounce = options.debounce; 26895 if (isNumber(debounce)) { 26896 debounceDelay = debounce; 26897 } else if (isNumber(debounce[trigger])) { 26898 debounceDelay = debounce[trigger]; 26899 } else if (isNumber(debounce['default'])) { 26900 debounceDelay = debounce['default']; 26901 } 26902 } 26903 26904 $timeout.cancel(pendingDebounce); 26905 if (debounceDelay) { 26906 pendingDebounce = $timeout(function() { 26907 ctrl.$commitViewValue(); 26908 }, debounceDelay); 26909 } else if ($rootScope.$$phase) { 26910 ctrl.$commitViewValue(); 26911 } else { 26912 $scope.$apply(function() { 26913 ctrl.$commitViewValue(); 26914 }); 26915 } 26916 }; 26917 26918 // model -> value 26919 // Note: we cannot use a normal scope.$watch as we want to detect the following: 26920 // 1. scope value is 'a' 26921 // 2. user enters 'b' 26922 // 3. ng-change kicks in and reverts scope value to 'a' 26923 // ->
26923 scope value did not change since the last digest as 26924 // ng-change executes in apply phase 26925 // 4. view should be changed back to 'a' 26926 $scope.$watch(function ngModelWatch() { 26927 var modelValue = ngModelGet($scope); 26928 26929 // if scope model value and ngModel value are out of sync 26930 // TODO(perf): why not move this to the action fn? 26931 if (modelValue !== ctrl.$modelValue && 26932 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 26933 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 26934 ) { 26935 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 26936 parserValid = undefined; 26937 26938 var formatters = ctrl.$formatters, 26939 idx = formatters.length; 26940 26941 var viewValue = modelValue; 26942 while (idx--) { 26943 viewValue = formatters[idx](viewValue); 26944 } 26945 if (ctrl.$viewValue !== viewValue) { 26946 ctrl.$$updateEmptyClasses(viewValue); 26947 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 26948 ctrl.$render(); 26949 26950 ctrl.$$runValidators(modelValue, viewValue, noop); 26951 } 26952 } 26953 26954 return modelValue; 26955 }); 26956}]; 26957 26958 26959/** 26960 * @ngdoc directive 26961 * @name ngModel 26962 * 26963 * @element input 26964 * @priority 1 26965 * 26966 * @description 26967 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 26968 * property on the scope using {@link ngModel.NgModelController NgModelController}, 26969 * which is created and exposed by this directive. 26970 * 26971 * `ngModel` is responsible for: 26972 * 26973 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 26974 * require. 26975 * - Providing validation behavior (i.e. required, number, email, url). 26976 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 26977 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 26978 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 26979 * - Registering the control with its parent {@link ng.directive:form form}. 26980 * 26981 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 26982 * current scope. If the property doesn't already exist on this scope, it will be created 26983 * implicitly and added to the scope. 26984 * 26985 * For best practices on using `ngModel`, see: 26986 * 26987 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 26988 * 26989 * For basic examples, how to use `ngModel`, see: 26990 * 26991 * - {@link ng.directive:input input} 26992 * - {@link input[text] text} 26993 * - {@link input[checkbox] checkbox} 26994 * - {@link input[radio] radio} 26995 * - {@link input[number] number} 26996 * - {@link input[email] email} 26997 * - {@link input[url] url} 26998 * - {@link input[date] date} 26999 * - {@link input[datetime-local] datetime-local} 27000 * - {@link input[time] time} 27001 * - {@link input[month] month} 27002 * - {@link input[week] week} 27003 * - {@link ng.directive:select select} 27004 * - {@link ng.directive:textarea textarea} 27005 * 27006 * # Complex Models (objects or collections) 27007 * 27008 * By default, `ngModel` watches the model by reference, not value. This is important to know when 27009 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the 27010 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. 27011 * 27012 * The model must be assigned an entirely new object or collection before a re-rendering will occur. 27013 * 27014 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression 27015 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or 27016 * if the select is given the `multiple` attribute. 27017 * 27018 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the 27019 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still 27020 * not trigger a re-rendering of the model. 27021 * 27022 * # CSS classes 27023 * The following CSS classes are added and removed on the associated input/select/textarea element 27024 * depending on the validity of the model. 27025 * 27026 * - `ng-valid`: the model is valid
27027 * - `ng-invalid`: the model is invalid 27028 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 27029 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 27030 * - `ng-pristine`: the control hasn't been interacted with yet 27031 * - `ng-dirty`: the control has been interacted with 27032 * - `ng-touched`: the control has been blurred 27033 * - `ng-untouched`: the control hasn't been blurred 27034 * - `ng-pending`: any `$asyncValidators` are unfulfilled 27035 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 27036 * by the {@link ngModel.NgModelController#$isEmpty} method 27037 * - `ng-not-empty`: the view contains a non-empty value 27038 * 27039 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 27040 * 27041 * ## Animation Hooks 27042 * 27043 * Animations within models are triggered when any of the associated CSS classes are added and removed 27044 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 27045 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 27046 * The animations that are triggered within ngModel are similar to how they work in ngClass and 27047 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 27048 * 27049 * The following example shows a simple way to utilize CSS transitions to style an input element 27050 * that has been rendered as invalid after it has been validated: 27051 * 27052 * <pre> 27053 * //be sure to include ngAnimate as a module to hook into more 27054 * //advanced animations 27055 * .my-input { 27056 * transition:0.5s linear all; 27057 * background: white; 27058 * } 27059 * .my-input.ng-invalid { 27060 * background: red; 27061 * color:white; 27062 * } 27063 * </pre> 27064 * 27065 * @example 27066 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 27067 <file name="index.html"> 27068 <script> 27069 angular.module('inputExample', []) 27070 .controller('ExampleController', ['$scope', function($scope) { 27071 $scope.val = '1'; 27072 }]); 27073 </script> 27074 <style> 27075 .my-input { 27076 transition:all linear 0.5s; 27077 background: transparent; 27078 } 27079 .my-input.ng-invalid { 27080 color:white; 27081 background: red; 27082 } 27083 </style> 27084 <p id="inputDescription"> 27085 Update input to see transitions when valid/invalid. 27086 Integer is a valid value. 27087 </p>
27088 <form name="testForm" ng-controller="ExampleController"> 27089 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 27090 aria-describedby="inputDescription" /> 27091 </form> 27092 </file> 27093 * </example> 27094 * 27095 * ## Binding to a getter/setter 27096 * 27097 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 27098 * function that returns a representation of the model when called with zero arguments, and sets 27099 * the internal state of a model when called with an argument. It's sometimes useful to use this 27100 * for models that have an internal representation that's different from what the model exposes 27101 * to the view. 27102 * 27103 * <div class="alert alert-success"> 27104 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 27105 * frequently than other parts of your code. 27106 * </div> 27107 * 27108 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 27109 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 27110 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 27111 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 27112 * 27113 * The following example shows how to use `ngModel` with a getter/setter: 27114 * 27115 * @example 27116 * <example name="ngModel-getter-setter" module="getterSetterExample"> 27117 <file name="index.html">
27118 <div ng-controller="ExampleController"> 27119 <form name="userForm"> 27120 <label>Name: 27121 <input type="text" name="userName" 27122 ng-model="user.name" 27123 ng-model-options="{ getterSetter: true }" /> 27124 </label> 27125 </form> 27126 <pre>user.name = <span ng-bind="user.name()"></span></pre> 27127 </div> 27128 </file> 27129 <file name="app.js"> 27130 angular.module('getterSetterExample', []) 27131 .controller('ExampleController', ['$scope', function($scope) { 27132 var _name = 'Brian'; 27133 $scope.user = { 27134 name: function(newName) { 27135 // Note that newName can be undefined for two reasons: 27136 // 1. Because it is called as a getter and thus called with no arguments 27137 // 2. Because the property should actually be set to undefined. This happens e.g. if the 27138 // input is invalid 27139 return arguments.length ? (_name = newName) : _name; 27140 } 27141 }; 27142 }]); 27143 </file> 27144 * </example> 27145 */ 27146var ngModelDirective = ['$rootScope', function($rootScope) { 27147 return { 27148 restrict: 'A', 27149 require: ['ngModel', '^?form', '^?ngModelOptions'], 27150 controller: NgModelController, 27151 // Prelink needs to run before any input directive 27152 // so that we can set the NgModelOptions in NgModelController 27153 // before anyone else uses it. 27154 priority: 1, 27155 compile: function ngModelCompile(element) { 27156 // Setup initial state of the control 27157 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 27158 27159 return { 27160 pre: function ngModelPreLink(scope, element, attr, ctrls) { 27161 var modelCtrl = ctrls[0], 27162 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 27163 27164 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 27165 27166 // notify others, especially parent forms 27167 formCtrl.$addControl(modelCtrl); 27168 27169 attr.$observe('name', function(newValue) { 27170 if (modelCtrl.$name !== newValue) { 27171 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 27172 } 27173 }); 27174 27175 scope.$on('$destroy', function() { 27176 modelCtrl.$$parentForm.$removeControl(modelCtrl); 27177 }); 27178 }, 27179 post: function ngModelPostLink(scope, element, attr, ctrls) { 27180 var modelCtrl = ctrls[0]; 27181 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 27182 element.on(modelCtrl.$options.updateOn, function(ev) { 27183 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 27184 }); 27185 } 27186 27187 element.on('blur', function() { 27188 if (modelCtrl.$touched) return; 27189 27190 if ($rootScope.$$phase) { 27191 scope.$evalAsync(modelCtrl.$setTouched); 27192 } else { 27193 scope.$apply(modelCtrl.$setTouched); 27194 } 27195 }); 27196 } 27197 }; 27198 } 27199 }; 27200}]; 27201 27202var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 27203 27204/** 27205 * @ngdoc directive 27206 * @name ngModelOptions 27207 * 27208 * @description 27209 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 27210 * events that will trigger a model update and/or a debouncing delay so that the actual update only 27211 * takes place when a timer expires; this timer will be reset after another change takes place. 27212 * 27213 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 27214 * be different from the value in the actual model. This means that if you update the model you 27215 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 27216 * order to make sure it is synchronized with the model and that any debounced action is canceled. 27217 * 27218 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 27219 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 27220 * important because `form` controllers are published to the related scope under the name in their 27221 * `name` attribute. 27222 * 27223 * Any pending changes will take place immediately when an enclosing form is submitted via the 27224 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 27225 * to have access to the updated model. 27226 * 27227 * `ngModelOptions` has an effect on the element it's declared on and its desce
27227ndants. 27228 * 27229 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 27230 * - `updateOn`: string specifying which event should the input be bound to. You can set several 27231 * events using an space delimited list. There is a special event called `default` that 27232 * matches the default events belonging of the control. 27233 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 27234 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 27235 * custom value for each event. For example: 27236 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 27237 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 27238 * not validate correctly instead of the default behavior of setting the model to undefined. 27239 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 27240 `ngModel` as getters/setters. 27241 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 27242 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 27243 * continental US time zone abbreviations, but for general use, use a time zone offset, for 27244 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 27245 * If not specified, the timezone of the browser will be used. 27246 * 27247 * @example 27248 27249 The following example shows how to override immediate updates. Changes on the inputs within the 27250 form will update the model only when the control loses focus (blur event). If `escape` key is 27251 pressed while the input field is focused, the value is reset to the value in the current model. 27252 27253 <example name="ngModelOptions-directive-blur" module="optionsExample"> 27254 <file name="index.html">
27255 <div ng-controller="ExampleController"> 27256 <form name="userForm"> 27257 <label>Name: 27258 <input type="text" name="userName" 27259 ng-model="user.name" 27260 ng-model-options="{ updateOn: 'blur' }" 27261 ng-keyup="cancel($event)" /> 27262 </label><br /> 27263 <label>Other data: 27264 <input type="text" ng-model="user.data" /> 27265 </label><br /> 27266 </form> 27267 <pre>user.name = <span ng-bind="user.name"></span></pre> 27268 <pre>user.data = <span ng-bind="user.data"></span></pre> 27269 </div> 27270 </file> 27271 <file name="app.js"> 27272 angular.module('optionsExample', []) 27273 .controller('ExampleController', ['$scope', function($scope) { 27274 $scope.user = { name: 'John', data: '' }; 27275 27276 $scope.cancel = function(e) { 27277 if (e.keyCode == 27) { 27278 $scope.userForm.userName.$rollbackViewValue(); 27279 } 27280 }; 27281 }]); 27282 </file> 27283 <file name="protractor.js" type="protractor"> 27284 var model = element(by.binding('user.name')); 27285 var input = element(by.model('user.name')); 27286 var other = element(by.model('user.data')); 27287 27288 it('should allow custom events', function() { 27289 input.sendKeys(' Doe'); 27290 input.click(); 27291 expect(model.getText()).toEqual('John'); 27292 other.click(); 27293 expect(model.getText()).toEqual('John Doe'); 27294 }); 27295 27296 it('should $rollbackViewValue when model changes', function() { 27297 input.sendKeys(' Doe'); 27298 expect(input.getAttribute('value')).toEqual('John Doe'); 27299 input.sendKeys(protractor.Key.ESCAPE); 27300 expect(input.getAttribute('value')).toEqual('John'); 27301 other.click(); 27302 expect(model.getText()).toEqual('John'); 27303 }); 27304 </file> 27305 </example> 27306 27307 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 27308 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 27309 27310 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 27311 <file name="index.html">
27312 <div ng-controller="ExampleController"> 27313 <form name="userForm"> 27314 <label>Name: 27315 <input type="text" name="userName" 27316 ng-model="user.name" 27317 ng-model-options="{ debounce: 1000 }" /> 27318 </label> 27319 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 27320 <br /> 27321 </form> 27322 <pre>user.name = <span ng-bind="user.name"></span></pre> 27323 </div> 27324 </file> 27325 <file name="app.js"> 27326 angular.module('optionsExample', []) 27327 .controller('ExampleController', ['$scope', function($scope) { 27328 $scope.user = { name: 'Igor' }; 27329 }]); 27330 </file> 27331 </example> 27332 27333 This one shows how to bind to getter/setters: 27334 27335 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 27336 <file name="index.html"> 27337 <div ng-controller="ExampleController"> 27338 <form name="userForm"> 27339 <label>Name: 27340 <input type="text" name="userName" 27341 ng-model="user.name" 27342 ng-model-options="{ getterSetter: true }" /> 27343 </label> 27344 </form> 27345 <pre>user.name = <span ng-bind="user.name()"></span></pre> 27346 </div> 27347 </file> 27348 <file name="app.js"> 27349 angular.module('getterSetterExample', []) 27350 .controller('ExampleController', ['$scope', function($scope) { 27351 var _name = 'Brian'; 27352 $scope.user = { 27353 name: function(newName) { 27354 // Note that newName can be undefined for two reasons: 27355 // 1. Because it is called as a getter and thus called with no arguments 27356 // 2. Because the property should actually be set to undefined. This happens e.g. if the 27357 // input is invalid 27358 return arguments.length ? (_name = newName) : _name; 27359 } 27360 }; 27361 }]); 27362 </file> 27363 </example> 27364 */ 27365var ngModelOptionsDirective = function() { 27366 return { 27367 restrict: 'A', 27368 controller: ['$scope', '$attrs', function($scope, $attrs) { 27369 var that = this; 27370 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 27371 // Allow adding/overriding bound events 27372 if (isDefined(this.$options.updateOn)) { 27373 this.$options.updateOnDefault = false;
27374 // extract "default" pseudo-event from list of events that can trigger a model update 27375 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 27376 that.$options.updateOnDefault = true; 27377 return ' '; 27378 })); 27379 } else { 27380 this.$options.updateOnDefault = true; 27381 } 27382 }] 27383 }; 27384}; 27385 27386 27387 27388// helper methods 27389function addSetValidityMethod(context) { 27390 var ctrl = context.ctrl, 27391 $element = context.$element, 27392 classCache = {}, 27393 set = context.set, 27394 unset = context.unset, 27395 $animate = context.$animate; 27396 27397 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 27398 27399 ctrl.$setValidity = setValidity; 27400 27401 function setValidity(validationErrorKey, state, controller) { 27402 if (isUndefined(state)) { 27403 createAndSet('$pending', validationErrorKey, controller); 27404 } else { 27405 unsetAndCleanup('$pending', validationErrorKey, controller); 27406 } 27407 if (!isBoolean(state)) { 27408 unset(ctrl.$error, validationErrorKey, controller); 27409 unset(ctrl.$$success, validationErrorKey, controller); 27410 } else { 27411 if (state) { 27412 unset(ctrl.$error, validationErrorKey, controller); 27413 set(ctrl.$$success, validationErrorKey, controller); 27414 } else { 27415 set(ctrl.$error, validationErrorKey, controller); 27416 unset(ctrl.$$success, validationErrorKey, controller); 27417 } 27418 } 27419 if (ctrl.$pending) { 27420 cachedToggleClass(PENDING_CLASS, true); 27421 ctrl.$valid = ctrl.$invalid = undefined; 27422 toggleValidationCss('', null); 27423 } else { 27424 cachedToggleClass(PENDING_CLASS, false); 27425 ctrl.$valid = isObjectEmpty(ctrl.$error); 27426 ctrl.$invalid = !ctrl.$valid; 27427 toggleValidationCss('', ctrl.$valid); 27428 } 27429 27430 // re-read the state as the set/unset methods could have 27431 // combined state in ctrl.$error[validationError] (used for forms), 27432 // where setting/unsetting only increments/decrements the value, 27433 // and does not replace it. 27434 var combinedState; 27435 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 27436 combinedState = undefined; 27437 } else if (ctrl.$error[validationErrorKey]) { 27438 combinedState = false; 27439 } else if (ctrl.$$success[validationErrorKey]) { 27440 combinedState = true; 27441 } else { 27442 combinedState = null; 27443 } 27444
27445 toggleValidationCss(validationErrorKey, combinedState); 27446 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 27447 } 27448 27449 function createAndSet(name, value, controller) { 27450 if (!ctrl[name]) { 27451 ctrl[name] = {}; 27452 } 27453 set(ctrl[name], value, controller); 27454 } 27455 27456 function unsetAndCleanup(name, value, controller) { 27457 if (ctrl[name]) { 27458 unset(ctrl[name], value, controller); 27459 } 27460 if (isObjectEmpty(ctrl[name])) { 27461 ctrl[name] = undefined; 27462 } 27463 } 27464 27465 function cachedToggleClass(className, switchValue) { 27466 if (switchValue && !classCache[className]) { 27467 $animate.addClass($element, className); 27468 classCache[className] = true; 27469 } else if (!switchValue && classCache[className]) { 27470 $animate.removeClass($element, className); 27471 classCache[className] = false; 27472 } 27473 } 27474 27475 function toggleValidationCss(validationErrorKey, isValid) { 27476 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 27477 27478 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 27479 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 27480 } 27481} 27482 27483function isObjectEmpty(obj) { 27484 if (obj) { 27485 for (var prop in obj) { 27486 if (obj.hasOwnProperty(prop)) { 27487 return false; 27488 } 27489 } 27490 } 27491 return true; 27492} 27493 27494/** 27495 * @ngdoc directive 27496 * @name ngNonBindable 27497 * @restrict AC 27498 * @priority 1000 27499 * 27500 * @description 27501 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 27502 * DOM element. This is useful if the element contains what appears to be Angular directives and 27503 * bindings but which should be ignored by Angular. This could be the case if you have a site that 27504 * displays snippets of code, for instance. 27505 * 27506 * @element ANY 27507 * 27508 * @example 27509 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 27510 * but the one wrapped in `ngNonBindable` is left alone. 27511 * 27512 * @example 27513 <example> 27514 <file name="index.html"> 27515 <div>Normal: {{1 + 2}}</div> 27516 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 27517 </file> 27518 <file name="protractor.js" type="protractor"> 27519 it('should check ng-non-bindable', function() { 27520 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 27521 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 27522 }); 27523 </file> 27524 </example> 27525 */ 27526var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 27527 27528/* global jqLiteRemove */ 27529 27530var ngOptionsMinErr = minErr('ngOptions'); 27531 27532/** 27533 * @ngdoc directive 27534 * @name ngOptions 27535 * @restrict A 27536 * 27537 * @description 27538 * 27539 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 27540 * elements for the `<select>` element using the array or object obtained by evaluating the 27541 * `ngOptions` comprehension expression. 27542 * 27543 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 27544 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 27545 * increasing speed by not creating a new scope for each repeated instance, as well as providing 27546 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 27547 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 27548 * to a non-string value. This is because an option element can only be bound to string values at 27549 * present. 27550 * 27551 * When an item in the `<select>` menu is selected, the array element or object property 27552 * represented by the selected option will be bound to the model identified by the `ngModel` 27553 * directive. 27554 * 27555 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 27556 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 27557 * option. See example below for demonstration. 27558 * 27559 * ## Complex Models (objects or collections) 27560 * 27561 * By default, `ngModel` watches the model by reference, not value. This is important to know when 27562 * binding the select to a model that is an object or a collection. 27563 * 27564 * One issue occurs if you want to preselect an option. For example, if you set 27565 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 27566 * because the objects are not identical. So by default, you should always reference the item in y
27566our collection 27567 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 27568 * 27569 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 27570 * of the item not by reference, but by the result of the `track by` expression. For example, if your 27571 * collection items have an id property, you would `track by item.id`. 27572 * 27573 * A different issue with objects or collections is that ngModel won't detect if an object property or 27574 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 27575 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 27576 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 27577 * has not changed identity, but only a property on the object or an item in the collection changes. 27578 * 27579 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 27580 * if the model is an array). This means that changing a property deeper than the first level inside the 27581 * object/collection will not trigger a re-rendering. 27582 * 27583 * ## `select` **`as`** 27584 * 27585 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 27586 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 27587 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 27588 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 27589 * 27590 * 27591 * ### `select` **`as`** and **`track by`** 27592 * 27593 * <div class="alert alert-warning"> 27594 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 27595 * </div> 27596 * 27597 * Given this array of items on the $scope: 27598 * 27599 * ```js 27600 * $scope.items = [{ 27601 * id: 1, 27602 * label: 'aLabel', 27603 * subItem: { name: 'aSubItem' } 27604 * }, { 27605 * id: 2, 27606 * label: 'bLabel', 27607 * subItem: { name: 'bSubItem' } 27608 * }]; 27609 * ``` 27610 * 27611 * This will work: 27612 * 27613 * ```html 27614 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 27615 * ``` 27616 * ```js 27617 * $scope.selected = $scope.items[0]; 27618 * ``` 27619 * 27620 * but this will not work: 27621 * 27622 * ```html 27623 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 27624 * ``` 27625 * ```js 27626 * $scope.selected = $scope.items[0].subItem; 27627 * ``` 27628 * 27629 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 27630 * `items` array. Because the selected option has been set programmatically in the controller, the 27631 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 27632 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 27633 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 27634 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 27635 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 27636 * 27637 * 27638 * @param {string} ngModel Assignable angular expression to data-bind to. 27639 * @param {string=} name Property name of the form under which the control is published. 27640 * @param {string=} required The control is considered valid only if value is entered. 27641 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27642 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27643 * `required` when you want to data-bind to the `required` attribute. 27644 * @param {comprehension_expression=} ngOptions in one of the following forms: 27645 * 27646 * * for array data sources: 27647 * * `label` **`for`** `value` **`in`** `array` 27648 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 27649 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 27650 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 27651 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 27652 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 27653 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 27654 * (for including a filter with `track by`) 27655 * * for object data sources:
27656 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 27657 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 27658 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 27659 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 27660 * * `select` **`as`** `label` **`group by`** `group` 27661 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 27662 * * `select` **`as`** `label` **`disable when`** `disable` 27663 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 27664 * 27665 * Where: 27666 * 27667 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 27668 * * `value`: local variable which will refer to each item in the `array` or each property value 27669 * of `object` during iteration. 27670 * * `key`: local variable which will refer to a property name in `object` during iteration. 27671 * * `label`: The result of this expression will be the label for `<option>` element. The 27672 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 27673 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 27674 * element. If not specified, `select` expression will default to `value`. 27675 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 27676 * DOM element. 27677 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 27678 * element. Return `true` to disable. 27679 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 27680 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 27681 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 27682 * even when the options are recreated (e.g. reloaded from the server). 27683 * 27684 * @example 27685 <example module="selectExample"> 27686 <file name="index.html"> 27687 <script> 27688 angular.module('selectExample', []) 27689 .controller('ExampleController', ['$scope', function($scope) { 27690 $scope.colors = [ 27691 {name:'black', shade:'dark'}, 27692 {name:'white', shade:'light', notAnOption: true}, 27693 {name:'red', shade:'dark'}, 27694 {name:'blue', shade:'dark', notAnOption: true}, 27695 {name:'yellow', shade:'light', notAnOption: false} 27696 ]; 27697 $scope.myColor = $scope.colors[2]; // red 27698 }]); 27699 </script>
27700 <div ng-controller="ExampleController"> 27701 <ul> 27702 <li ng-repeat="color in colors"> 27703 <label>Name: <input ng-model="color.name"></label> 27704 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 27705 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 27706 </li> 27707 <li> 27708 <button ng-click="colors.push({})">add</button> 27709 </li> 27710 </ul> 27711 <hr/> 27712 <label>Color (null not allowed): 27713 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 27714 </label><br/> 27715 <label>Color (null allowed): 27716 <span class="nullable"> 27717 <select ng-model="myColor" ng-options="color.name for color in colors"> 27718 <option value="">-- choose color --</option> 27719 </select> 27720 </span></label><br/> 27721 27722 <label>Color grouped by shade: 27723 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 27724 </select> 27725 </label><br/> 27726 27727 <label>Color grouped by shade, with some disabled: 27728 <select ng-model="myColor" 27729 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 27730 </select> 27731 </label><br/> 27732 27733 27734 27735 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 27736 <br/> 27737 <hr/> 27738 Currently selected: {{ {selected_color:myColor} }} 27739 <div style="border:solid 1px black; height:20px" 27740 ng-style="{'background-color':myColor.name}"> 27741 </div> 27742 </div> 27743 </file> 27744 <file name="protractor.js" type="protractor"> 27745 it('should check ng-options', function() { 27746 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 27747 element.all(by.model('myColor')).first().click(); 27748 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 27749 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 27750 element(by.css('.nullable select[ng-model="myColor"]')).click(); 27751 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 27752 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 27753 }); 27754 </file> 27755 </example> 27756 */ 27757 27758// jshint maxlen: false 27759// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 27760var 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]+?))?$/; 27761 // 1: value expression (valueFn) 27762 // 2: label expression (displayFn) 27763 // 3: group by expression (groupByFn) 27764 // 4: disable when expression (disableWhenFn) 27765 // 5: array item variable name 27766 // 6: object item key variable name 27767 // 7: object item value variable name 27768 // 8: collection expression 27769 // 9: track by expression 27770// jshint maxlen: 100 27771 27772 27773var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) { 27774 27775 function parseOptionsExpression(optionsExp, selectElement, scope) { 27776 27777 var match = optionsExp.match(NG_OPTIONS_REGEXP); 27778 if (!(match)) { 27779 throw ngOptionsMinErr('iexp', 27780 "Expected expression in form of " + 27781 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 27782 " but got '{0}'. Element: {1}", 27783 optionsExp, startingTag(selectElement)); 27784 } 27785 27786 // Extract the parts from the ngOptions expression 27787 27788 // The variable name for the value of the item in the collection 27789 var valueName = match[5] || match[7]; 27790 // The variable name for the key of the item in the collection 27791 var keyName = match[6]; 27792 27793 // An expression that generates the viewValue for an option if there is a label expression 27794 var selectAs = / as /.test(match[0]) && match[1]; 27795 // An expression that is used to track the id of each object in the options collection 27796 var trackBy = match[9]; 27797 // An expression that generates the viewValue for an option if there is no label expression 27798 var valueFn = $parse(match[2] ? match[1] : valueName); 27799 var selectAsFn = selectAs && $parse(selectAs); 27800 var viewValueFn = selectAsFn || valueFn; 27801 var trackByFn = trackBy && $parse(trackBy); 27802 27803 // Get the value by which we are going to track the option 27804 // if we have a trackFn then use that (passing scope and locals) 27805 // otherwise just hash the given viewValue 27806 var getTrackByValueFn = trackBy ? 27807 function(value, locals) { return trackByFn(scope, locals); } : 27808 function getHashOfValue(value) { return hashKey(value); }; 27809 var getTrackByValue = function(value, key) {
vendor: 5,803 bytes, lines 27810-27966
27810 return getTrackByValueFn(value, getLocals(value, key)); 27811 }; 27812 27813 var displayFn = $parse(match[2] || match[1]); 27814 var groupByFn = $parse(match[3] || ''); 27815 var disableWhenFn = $parse(match[4] || ''); 27816 var valuesFn = $parse(match[8]); 27817 27818 var locals = {}; 27819 var getLocals = keyName ? function(value, key) { 27820 locals[keyName] = key; 27821 locals[valueName] = value; 27822 return locals; 27823 } : function(value) { 27824 locals[valueName] = value; 27825 return locals; 27826 }; 27827 27828 27829 function Option(selectValue, viewValue, label, group, disabled) { 27830 this.selectValue = selectValue; 27831 this.viewValue = viewValue; 27832 this.label = label; 27833 this.group = group; 27834 this.disabled = disabled; 27835 } 27836 27837 function getOptionValuesKeys(optionValues) { 27838 var optionValuesKeys; 27839 27840 if (!keyName && isArrayLike(optionValues)) { 27841 optionValuesKeys = optionValues; 27842 } else { 27843 // if object, extract keys, in enumeration order, unsorted 27844 optionValuesKeys = []; 27845 for (var itemKey in optionValues) { 27846 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 27847 optionValuesKeys.push(itemKey); 27848 } 27849 } 27850 } 27851 return optionValuesKeys; 27852 } 27853 27854 return { 27855 trackBy: trackBy, 27856 getTrackByValue: getTrackByValue, 27857 getWatchables: $parse(valuesFn, function(optionValues) { 27858 // Create a collection of things that we would like to watch (watchedArray) 27859 // so that they can all be watched using a single $watchCollection 27860 // that only runs the handler once if anything changes 27861 var watchedArray = []; 27862 optionValues = optionValues || []; 27863 27864 var optionValuesKeys = getOptionValuesKeys(optionValues); 27865 var optionValuesLength = optionValuesKeys.length; 27866 for (var index = 0; index < optionValuesLength; index++) { 27867 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 27868 var value = optionValues[key]; 27869 27870 var locals = getLocals(value, key); 27871 var selectValue = getTrackByValueFn(value, locals); 27872 watchedArray.push(selectValue); 27873 27874 // Only need to watch the displayFn if there is a specific label expression 27875 if (match[2] || match[1]) { 27876 var label = displayFn(scope, locals); 27877 watchedArray.push(label); 27878 } 27879 27880 // Only need to watch the disableWhenFn if there is a specific disable expression 27881 if (match[4]) { 27882 var disableWhen = disableWhenFn(scope, locals); 27883 watchedArray.push(disableWhen); 27884 } 27885 } 27886 return watchedArray; 27887 }), 27888 27889 getOptions: function() { 27890 27891 var optionItems = []; 27892 var selectValueMap = {}; 27893 27894 // The option values were already computed in the `getWatchables` fn, 27895 // which must have been called to trigger `getOptions` 27896 var optionValues = valuesFn(scope) || []; 27897 var optionValuesKeys = getOptionValuesKeys(optionValues); 27898 var optionValuesLength = optionValuesKeys.length; 27899 27900 for (var index = 0; index < optionValuesLength; index++) { 27901 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 27902 var value = optionValues[key]; 27903 var locals = getLocals(value, key); 27904 var viewValue = viewValueFn(scope, locals); 27905 var selectValue = getTrackByValueFn(viewValue, locals); 27906 var label = displayFn(scope, locals); 27907 var group = groupByFn(scope, locals); 27908 var disabled = disableWhenFn(scope, locals); 27909 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 27910 27911 optionItems.push(optionItem); 27912 selectValueMap[selectValue] = optionItem; 27913 } 27914 27915 return { 27916 items: optionItems, 27917 selectValueMap: selectValueMap, 27918 getOptionFromViewValue: function(value) { 27919 return selectValueMap[getTrackByValue(value)]; 27920 }, 27921 getViewValueFromOption: function(option) { 27922 // If the viewValue could be an object that may be mutated by the application, 27923 // we need to make a copy and not return the reference to the value on the option. 27924 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 27925 } 27926 }; 27927 } 27928 }; 27929 } 27930 27931 27932 // we can't just jqLite('<option>') since jqLite is not smart enough 27933 // to create it in <select> and IE barfs otherwise. 27934 var optionTemplate = window.document.createElement('option'), 27935 optGroupTemplate = window.document.createElement('optgroup'); 27936 27937 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 27938 27939 var selectCtrl = ctrls[0]; 27940 var ngModelCtrl = ctrls[1]; 27941 var multiple = attr.multiple; 27942 27943 // The emptyOption allows the application developer to provide their own custom "empty" 27944 // option when the viewValue does not match any of the option values. 27945 var emptyOption; 27946 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 27947 if (children[i].value === '') { 27948 emptyOption = children.eq(i); 27949 break; 27950 } 27951 } 27952 27953 var providedEmptyOption = !!emptyOption; 27954 27955 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 27956 unknownOption.val('?'); 27957 27958 var options; 27959 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 27960 // This stores the newly created options before they are appended to the select. 27961 // Since the contents are removed from the fragment when it is appended, 27962 // we only need to create it once. 27963 var listFragment = $document[0].createDocumentFragment(); 27964 27965 var renderEmptyOption = function() { 27966 if (!providedEmptyOption) {
27967 selectElement.prepend(emptyOption); 27968 } 27969 selectElement.val(''); 27970 emptyOption.prop('selected', true); // needed for IE 27971 emptyOption.attr('selected', true); 27972 }; 27973 27974 var removeEmptyOption = function() { 27975 if (!providedEmptyOption) { 27976 emptyOption.remove(); 27977 } 27978 }; 27979 27980 27981 var renderUnknownOption = function() { 27982 selectElement.prepend(unknownOption); 27983 selectElement.val('?'); 27984 unknownOption.prop('selected', true); // needed for IE 27985 unknownOption.attr('selected', true); 27986 }; 27987 27988 var removeUnknownOption = function() { 27989 unknownOption.remove(); 27990 }; 27991 27992 // Update the controller methods for multiple selectable options 27993 if (!multiple) { 27994 27995 selectCtrl.writeValue = function writeNgOptionsValue(value) { 27996 var option = options.getOptionFromViewValue(value); 27997 27998 if (option) { 27999 // Don't update the option when it is already selected. 28000 // For example, the browser will select the first option by default. In that case, 28001 // most properties are set automatically - except the `selected` attribute, which we 28002 // set always 28003 28004 if (selectElement[0].value !== option.selectValue) { 28005 removeUnknownOption(); 28006 removeEmptyOption(); 28007 28008 selectElement[0].value = option.selectValue; 28009 option.element.selected = true; 28010 } 28011 28012 option.element.setAttribute('selected', 'selected'); 28013 } else { 28014 if (value === null || providedEmptyOption) { 28015 removeUnknownOption(); 28016 renderEmptyOption(); 28017 } else { 28018 removeEmptyOption(); 28019 renderUnknownOption(); 28020 } 28021 } 28022 }; 28023 28024 selectCtrl.readValue = function readNgOptionsValue() { 28025 28026 var selectedOption = options.selectValueMap[selectElement.val()]; 28027 28028 if (selectedOption && !selectedOption.disabled) { 28029 removeEmptyOption(); 28030 removeUnknownOption(); 28031 return options.getViewValueFromOption(selectedOption); 28032 } 28033 return null; 28034 }; 28035 28036 // If we are using `track by` then we must watch the tracked value on the model 28037 // since ngModel only watches for object identity change 28038 if (ngOptions.trackBy) { 28039 scope.$watch( 28040 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 28041 function() { ngModelCtrl.$render(); } 28042 ); 28043 } 28044 28045 } else { 28046 28047 ngModelCtrl.$isEmpty = function(value) { 28048 return !value || value.length === 0; 28049 }; 28050 28051 28052 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 28053 options.items.forEach(function(option) { 28054 option.element.selected = false; 28055 }); 28056 28057 if (value) { 28058 value.forEach(function(item) { 28059 var option = options.getOptionFromViewValue(item); 28060 if (option) option.element.selected = true; 28061 }); 28062 } 28063 }; 28064 28065 28066 selectCtrl.readValue = function readNgOptionsMultiple() { 28067 var selectedValues = selectElement.val() || [], 28068 selections = []; 28069 28070 forEach(selectedValues, function(value) { 28071 var option = options.selectValueMap[value]; 28072 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 28073 }); 28074 28075 return selections; 28076 }; 28077 28078 // If we are using `track by` then we must watch these tracked values on the model 28079 // since ngModel only watches for object identity change 28080 if (ngOptions.trackBy) { 28081 28082 scope.$watchCollection(function() { 28083 if (isArray(ngModelCtrl.$viewValue)) { 28084 return ngModelCtrl.$viewValue.map(function(value) { 28085 return ngOptions.getTrackByValue(value); 28086 }); 28087 } 28088 }, function() { 28089 ngModelCtrl.$render(); 28090 }); 28091 28092 } 28093 } 28094 28095 28096 if (providedEmptyOption) { 28097 28098 // we need to remove it before calling selectElement.empty() because otherwise IE will 28099 // remove the label from the element. wtf? 28100 emptyOption.remove(); 28101 28102 // compile the element since there might be bindings in it 28103 $compile(emptyOption)(scope); 28104 28105 // remove the class, which is added automatically because we recompile the element and it 28106 // becomes the compilation root 28107 emptyOption.removeClass('ng-scope'); 28108 } else { 28109 emptyOption = jqLite(optionTemplate.cloneNode(false)); 28110 } 28111 28112 selectElement.empty(); 28113 28114 // We need to do this here to ensure that the options object is defined 28115 // when we first hit it in writeNgOptionsValue 28116 updateOptions(); 28117 28118 // We will re-render the option elements if the option values or labels change 28119 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 28120 28121 // ------------------------------------------------------------------ // 28122 28123 function addOptionElement(option, parent) { 28124 var optionElement = optionTemplate.cloneNode(false); 28125 parent.appendChild(optionElement); 28126 updateOptionElement(option, optionElement); 28127 } 28128 28129 28130 function updateOptionElement(option, element) { 28131 option.element = element; 28132 element.disabled = option.disabled; 28133 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 28134 // selects in certain circumstances when multiple selects are next to each other and display 28135 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 28136 // See https://github.com/angular/angular.js/issues/11314 for more info. 28137 // This is unfortunately untestable with unit / e2e tests 28138 if (option.label !== element.label) { 28139 element.label = option.label; 28140 element.textContent = option.label; 28141 } 28142 if (option.value !== element.value) element.value = option.selectValue; 28143 } 28144 28145 function updateOptions() { 28146 var previousValue = options && selectCtrl.readValue(); 28147 28148 // We must remove all current options, but cannot simply set innerHTML = null 28149 // since the providedEmptyOption might have an ngIf on it that inserts comments which we 28150 // must preserve. 28151 // Instead, iterate over the current option elements and remove them or their optgroup 28152 // parents 28153 if (options) { 28154 28155 for (var i = options.items.length - 1; i >= 0; i--) { 28156 var option = options.items[i]; 28157 if (option.group) { 28158 jqLiteRemove(option.element.parentNode); 28159 } else { 28160 jqLiteRemove(option.element); 28161 } 28162 } 28163 } 28164 28165 options = ngOptions.getOptions(); 28166 28167 var groupElementMap = {}; 28168 28169 // Ensure that the empty option is always there if it was explicitly provided 28170 if (providedEmptyOption) {
vendor: 4,500 bytes, lines 28171-28288
28171 selectElement.prepend(emptyOption); 28172 } 28173 28174 options.items.forEach(function addOption(option) { 28175 var groupElement; 28176 28177 if (isDefined(option.group)) { 28178 28179 // This option is to live in a group 28180 // See if we have already created this group 28181 groupElement = groupElementMap[option.group]; 28182 28183 if (!groupElement) { 28184 28185 groupElement = optGroupTemplate.cloneNode(false); 28186 listFragment.appendChild(groupElement); 28187 28188 // Update the label on the group element 28189 groupElement.label = option.group; 28190 28191 // Store it for use later 28192 groupElementMap[option.group] = groupElement; 28193 } 28194 28195 addOptionElement(option, groupElement); 28196 28197 } else { 28198 28199 // This option is not in a group 28200 addOptionElement(option, listFragment); 28201 } 28202 }); 28203 28204 selectElement[0].appendChild(listFragment); 28205 28206 ngModelCtrl.$render(); 28207 28208 // Check to see if the value has changed due to the update to the options 28209 if (!ngModelCtrl.$isEmpty(previousValue)) { 28210 var nextValue = selectCtrl.readValue(); 28211 var isNotPrimitive = ngOptions.trackBy || multiple; 28212 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 28213 ngModelCtrl.$setViewValue(nextValue); 28214 ngModelCtrl.$render(); 28215 } 28216 } 28217 28218 } 28219 } 28220 28221 return { 28222 restrict: 'A', 28223 terminal: true, 28224 require: ['select', 'ngModel'], 28225 link: { 28226 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 28227 // Deactivate the SelectController.register method to prevent 28228 // option directives from accidentally registering themselves 28229 // (and unwanted $destroy handlers etc.) 28230 ctrls[0].registerOption = noop; 28231 }, 28232 post: ngOptionsPostLink 28233 } 28234 }; 28235}]; 28236 28237/** 28238 * @ngdoc directive 28239 * @name ngPluralize 28240 * @restrict EA 28241 * 28242 * @description 28243 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 28244 * These rules are bundled with angular.js, but can be overridden 28245 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 28246 * by specifying the mappings between 28247 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 28248 * and the strings to be displayed. 28249 * 28250 * # Plural categories and explicit number rules 28251 * There are two 28252 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 28253 * in Angular's default en-US locale: "one" and "other". 28254 * 28255 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 28256 * any number that is not 1), an explicit number rule can only match one number. For example, the 28257 * explicit number rule for "3" matches the number 3. There are examples of plural categories 28258 * and explicit number rules throughout the rest of this documentation. 28259 * 28260 * # Configuring ngPluralize 28261 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 28262 * You can also provide an optional attribute, `offset`. 28263 * 28264 * The value of the `count` attribute can be either a string or an {@link guide/expression 28265 * Angular expression}; these are evaluated on the current scope for its bound value. 28266 * 28267 * The `when` attribute specifies the mappings between plural categories and the actual 28268 * string to be displayed. The value of the attribute should be a JSON object. 28269 * 28270 * The following example shows how to configure ngPluralize: 28271 * 28272 * ```html 28273 * <ng-pluralize count="personCount" 28274 when="{'0': 'Nobody is viewing.', 28275 * 'one': '1 person is viewing.', 28276 * 'other': '{} people are viewing.'}"> 28277 * </ng-pluralize> 28278 *``` 28279 * 28280 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 28281 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 28282 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 28283 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 28284 * show "a dozen people are viewing". 28285 * 28286 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 28287 * into pluralized strings. In the previous example, Angular will replace `{}` with 28288 * <span ng-non-bindable>`{{personCount}}
28288`</span>. The closed braces `{}` is a placeholder 28289 * for <span ng-non-bindable>{{numberExpression}}</span>. 28290 * 28291 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 28292 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 28293 * 28294 * # Configuring ngPluralize with offset 28295 * The `offset` attribute allows further customization of pluralized text, which can result in 28296 * a better user experience. For example, instead of the message "4 people are viewing this document", 28297 * you might display "John, Kate and 2 others are viewing this document". 28298 * The offset attribute allows you to offset a number by any desired value. 28299 * Let's take a look at an example: 28300 * 28301 * ```html 28302 * <ng-pluralize count="personCount" offset=2 28303 * when="{'0': 'Nobody is viewing.', 28304 * '1': '{{person1}} is viewing.', 28305 * '2': '{{person1}} and {{person2}} are viewing.', 28306 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 28307 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 28308 * </ng-pluralize> 28309 * ``` 28310 * 28311 * Notice that we are still using two plural categories(one, other), but we added 28312 * three explicit number rules 0, 1 and 2. 28313 * When one person, perhaps John, views the document, "John is viewing" will be shown. 28314 * When three people view the document, no explicit number rule is found, so 28315 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 28316 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 28317 * is shown. 28318 * 28319 * Note that when you specify offsets, you must provide explicit number rules for 28320 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 28321 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 28322 * plural categories "one" and "other". 28323 * 28324 * @param {string|expression} count The variable to be bound to. 28325 * @param {string} when The mapping between plural category to its corresponding strings. 28326 * @param {number=} offset Offset to deduct from the total number. 28327 * 28328 * @example 28329 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 28329-28443
28329e"> 28330 <file name="index.html"> 28331 <script> 28332 angular.module('pluralizeExample', []) 28333 .controller('ExampleController', ['$scope', function($scope) { 28334 $scope.person1 = 'Igor'; 28335 $scope.person2 = 'Misko'; 28336 $scope.personCount = 1; 28337 }]); 28338 </script> 28339 <div ng-controller="ExampleController"> 28340 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 28341 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 28342 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 28343 28344 <!--- Example with simple pluralization rules for en locale ---> 28345 Without Offset: 28346 <ng-pluralize count="personCount" 28347 when="{'0': 'Nobody is viewing.', 28348 'one': '1 person is viewing.', 28349 'other': '{} people are viewing.'}"> 28350 </ng-pluralize><br> 28351 28352 <!--- Example with offset ---> 28353 With Offset(2): 28354 <ng-pluralize count="personCount" offset=2 28355 when="{'0': 'Nobody is viewing.', 28356 '1': '{{person1}} is viewing.', 28357 '2': '{{person1}} and {{person2}} are viewing.', 28358 'one': '{{person1}}, {{person2}} and one other person are viewing.', 28359 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 28360 </ng-pluralize> 28361 </div> 28362 </file> 28363 <file name="protractor.js" type="protractor"> 28364 it('should show correct pluralized string', function() { 28365 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 28366 var withOffset = element.all(by.css('ng-pluralize')).get(1); 28367 var countInput = element(by.model('personCount')); 28368 28369 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 28370 expect(withOffset.getText()).toEqual('Igor is viewing.'); 28371 28372 countInput.clear(); 28373 countInput.sendKeys('0'); 28374 28375 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 28376 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 28377 28378 countInput.clear(); 28379 countInput.sendKeys('2'); 28380 28381 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 28382 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 28383 28384 countInput.clear(); 28385 countInput.sendKeys('3'); 28386 28387 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 28388 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 28389 28390 countInput.clear(); 28391 countInput.sendKeys('4'); 28392 28393 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 28394 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 28395 }); 28396 it('should show data-bound names', function() { 28397 var withOffset = element.all(by.css('ng-pluralize')).get(1); 28398 var personCount = element(by.model('personCount')); 28399 var person1 = element(by.model('person1')); 28400 var person2 = element(by.model('person2')); 28401 personCount.clear(); 28402 personCount.sendKeys('4'); 28403 person1.clear(); 28404 person1.sendKeys('Di'); 28405 person2.clear(); 28406 person2.sendKeys('Vojta'); 28407 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 28408 }); 28409 </file> 28410 </example> 28411 */ 28412var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 28413 var BRACE = /{}/g, 28414 IS_WHEN = /^when(Minus)?(.+)$/; 28415 28416 return { 28417 link: function(scope, element, attr) { 28418 var numberExp = attr.count, 28419 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 28420 offset = attr.offset || 0, 28421 whens = scope.$eval(whenExp) || {}, 28422 whensExpFns = {}, 28423 startSymbol = $interpolate.startSymbol(), 28424 endSymbol = $interpolate.endSymbol(), 28425 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 28426 watchRemover = angular.noop, 28427 lastCount; 28428 28429 forEach(attr, function(expression, attributeName) { 28430 var tmpMatch = IS_WHEN.exec(attributeName); 28431 if (tmpMatch) { 28432 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 28433 whens[whenKey] = element.attr(attr.$attr[attributeName]); 28434 } 28435 }); 28436 forEach(whens, function(expression, key) { 28437 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 28438 28439 }); 28440 28441 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 28442 var count = parseFloat(newVal); 28443 var countIsNaN = isN
vendor: 9,019 bytes, lines 28443-28651
28443aN(count); 28444 28445 if (!countIsNaN && !(count in whens)) { 28446 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 28447 // Otherwise, check it against pluralization rules in $locale service. 28448 count = $locale.pluralCat(count - offset); 28449 } 28450 28451 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 28452 // In JS `NaN !== NaN`, so we have to explicitly check. 28453 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 28454 watchRemover(); 28455 var whenExpFn = whensExpFns[count]; 28456 if (isUndefined(whenExpFn)) { 28457 if (newVal != null) { 28458 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 28459 } 28460 watchRemover = noop; 28461 updateElementText(); 28462 } else { 28463 watchRemover = scope.$watch(whenExpFn, updateElementText); 28464 } 28465 lastCount = count; 28466 } 28467 }); 28468 28469 function updateElementText(newText) { 28470 element.text(newText || ''); 28471 } 28472 } 28473 }; 28474}]; 28475 28476/** 28477 * @ngdoc directive 28478 * @name ngRepeat 28479 * @multiElement 28480 * 28481 * @description 28482 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 28483 * instance gets its own scope, where the given loop variable is set to the current collection item, 28484 * and `$index` is set to the item index or key. 28485 * 28486 * Special properties are exposed on the local scope of each template instance, including: 28487 * 28488 * | Variable | Type | Details | 28489 * |-----------|-----------------|-----------------------------------------------------------------------------| 28490 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 28491 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 28492 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 28493 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 28494 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 28495 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 28496 * 28497 * <div class="alert alert-info"> 28498 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 28499 * This may be useful when, for instance, nesting ngRepeats. 28500 * </div> 28501 * 28502 * 28503 * # Iterating over object properties 28504 * 28505 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 28506 * syntax: 28507 * 28508 * ```js 28509 * <div ng-repeat="(key, value) in myObj"> ... </div> 28510 * ``` 28511 * 28512 * However, there are a limitations compared to array iteration: 28513 * 28514 * - The JavaScript specification does not define the order of keys 28515 * returned for an object, so Angular relies on the order returned by the browser 28516 * when running `for key in myObj`. Browsers generally follow the strategy of providing 28517 * keys in the order in which they were defined, although there are exceptions when keys are deleted 28518 * and reinstated. See the 28519 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 28520 * 28521 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because 28522 * it's a prefix used by Angular for public (`$`) and private (`$$`) properties. 28523 * 28524 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with 28525 * objects, and will throw if used with one. 28526 * 28527 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array 28528 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 28529 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 28530 * or implement a `$watch` on the object yourself. 28531 * 28532 * 28533 * # Tracking and Duplicates 28534 * 28535 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 28536 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM: 28537 * 28538 * * When an item is added, a new instance of the template is added to the DOM. 28539 * * When an item is removed, its template instance is removed from the DOM. 28540 * * When items are reordered, their respective templates are reordered in the DOM. 28541 * 28542 * To minimize creation of DOM elements, `ngRepeat` uses a function 28543 * to "keep track" of all items in the collection and their corresponding DOM elements. 28544 * For example, if an item is added to the collection, ngRepeat will know that all other items 28545 * already have DOM elements, and will not re-render them. 28546 * 28547 * The default tracking function (which tracks items by their identity) does not allow 28548 * duplicate items in arrays. This is because when there are duplicates, it is not possible 28549 * to maintain a one-to-one mapping between collection items and DOM elements. 28550 * 28551 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 28552 * with your own using the `track by` expression. 28553 * 28554 * For example, you may track items by the index of each item in the collection, using the 28555 * special scope property `$index`: 28556 * ```html 28557 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 28558 * {{n}} 28559 * </div> 28560 * ``` 28561 * 28562 * You may also use arbitrary expressions in `track by`, including references to custom functions 28563 * on the scope: 28564 * ```html 28565 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 28566 * {{n}} 28567 * </div> 28568 * ``` 28569 * 28570 * <div class="alert alert-success"> 28571 * If you are working with objects that have an identifier property, you should track 28572 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 28573 * will not have to rebuild the DOM elements for items it has already rendered, even if the 28574 * JavaScript objects in the collection have been substituted for new ones. For large collections, 28575 * this significantly improves rendering performance. If you don't have a unique identifier, 28576 * `track by $index` can also provide a performance boost. 28577 * </div> 28578 * ```html 28579 * <div ng-repeat="model in collection track by model.id"> 28580 * {{model.name}} 28581 * </div> 28582 * ``` 28583 * 28584 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 28585 * `$id` function, which tracks items by their identity: 28586 * ```html 28587 * <div ng-repeat="obj in collection track by $id(obj)"> 28588 * {{obj.prop}} 28589 * </div> 28590 * ``` 28591 * 28592 * <div class="alert alert-warning"> 28593 * **Note:** `track by` must always be the last expression: 28594 * </div> 28595 * ``` 28596 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 28597 * {{model.name}} 28598 * </div> 28599 * ``` 28600 * 28601 * # Special repeat start and end points 28602 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 28603 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 28604 * 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) 28605 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 28606 * 28607 * The example below makes use of this feature: 28608 * ```html 28609 * <header ng-repeat-start="item in items"> 28610 * Header {{ item }} 28611 * </header> 28612 * <div class="body"> 28613 * Body {{ item }} 28614 * </div> 28615 * <footer ng-repeat-end> 28616 * Footer {{ item }} 28617 * </footer> 28618 * ``` 28619 * 28620 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 28621 * ```html 28622 * <header> 28623 * Header A 28624 * </header> 28625 * <div class="body"> 28626 * Body A 28627 * </div> 28628 * <footer> 28629 * Footer A 28630 * </footer> 28631 * <header> 28632 * Header B 28633 * </header> 28634 * <div class="body"> 28635 * Body B 28636 * </div> 28637 * <footer> 28638 * Footer B 28639 * </footer> 28640 * ``` 28641 * 28642 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 28643 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 28644 * 28645 * @animations 28646 * | Animation | Occurs | 28647 * |----------------------------------|-------------------------------------| 28648 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | 28649 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | 28650 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | 28651 *
28652 * See the example below for defining CSS animations with ngRepeat. 28653 * 28654 * @element ANY 28655 * @scope 28656 * @priority 1000 28657 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 28658 * formats are currently supported: 28659 * 28660 * * `variable in expression` – where variable is the user defined loop variable and `expression` 28661 * is a scope expression giving the collection to enumerate. 28662 * 28663 * For example: `album in artist.albums`. 28664 * 28665 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, 28666 * and `expression` is the scope expression giving the collection to enumerate. 28667 * 28668 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 28669 * 28670 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression 28671 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 28672 * is specified, ng-repeat associates elements by identity. It is an error to have 28673 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 28674 * mapped to the same DOM element, which is not possible.) 28675 * 28676 * Note that the tracking expression must come last, after any filters, and the alias expression. 28677 * 28678 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 28679 * will be associated by item identity in the array. 28680 * 28681 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 28682 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 28683 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 28684 * element in the same way in the DOM. 28685 * 28686 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 28687 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 28688 * property is same. 28689 * 28690 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 28691 * to items in conjunction with a tracking expression. 28692 * 28693 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the 28694 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 28695 * when a filter is active on the repeater, but the filtered result set is empty. 28696 * 28697 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 28698 * the items have been processed through the filter. 28699 * 28700 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end 28701 * (and not as operator, inside an expression). 28702 * 28703 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 28704 * 28705 * @example 28706 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed 28707 * results by name. New (entering) and removed (leaving) items are animated. 28708 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true"> 28709 <file name="index.html"> 28710 <div ng-controller="repeatController"> 28711 I have {{friends.length}} friends. They are: 28712 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 28713 <ul class="example-animate-container"> 28714 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 28715 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 28716 </li> 28717 <li class="animate-repeat" ng-if="results.length == 0"> 28718 <strong>No results found...</strong> 28719 </li> 28720 </ul> 28721 </div> 28722 </file> 28723 <file name="script.js"> 28724 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 28725 $scope.friends = [ 28726 {name:'John', age:25, gender:'boy'}, 28727 {name:'Jessie', age:30, gender:'girl'}, 28728 {name:'Johanna', age:28, gender:'girl'}, 28729 {name:'Joy', age:15, gender:'girl'}, 28730 {name:'Mary', age:28, gender:'girl'}, 28731 {name:'Peter', age:95, gender:'boy'}, 28732 {name:'Sebastian', age:50, gender:'boy'}, 28733 {name:'Erika', age:27, gender:'girl'}, 28734 {name:'Patrick', age:40, gender:'boy'}, 28735 {name:'Samantha', age:60, gender:'girl'} 28736 ]; 28737 }); 28738 </file> 28739 <file name="animations.css"> 28740 .example-animate-container { 28741 background:white; 28742 border:1px solid black; 28743 list-style:none; 28744 margin:0; 28745 padding:0 10px; 28746 } 28747 28748 .animate-repeat { 28749 line-height:30px; 28750 list-style:none; 28751 box-sizing:border-box; 28752 } 28753 28754 .animate-repeat.ng-move, 28755 .animate-repeat.ng-enter, 28756 .animate-repeat.ng-leave { 28757 transition:all linear 0.5s; 28758 } 28759 28760 .animate-repeat.ng-leave.ng-leave-active, 28761 .animate-repeat.ng-move, 28762 .animate-repeat.ng-enter { 28763 opacity:0; 28764 max-height:0; 28765 } 28766 28767 .animate-repeat.ng-leave,
28768 .animate-repeat.ng-move.ng-move-active, 28769 .animate-repeat.ng-enter.ng-enter-active { 28770 opacity:1; 28771 max-height:30px; 28772 } 28773 </file> 28774 <file name="protractor.js" type="protractor"> 28775 var friends = element.all(by.repeater('friend in friends')); 28776 28777 it('should render initial data set', function() { 28778 expect(friends.count()).toBe(10); 28779 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 28780 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 28781 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 28782 expect(element(by.binding('friends.length')).getText()) 28783 .toMatch("I have 10 friends. They are:"); 28784 }); 28785 28786 it('should update repeater when filter predicate changes', function() { 28787 expect(friends.count()).toBe(10); 28788 28789 element(by.model('q')).sendKeys('ma'); 28790 28791 expect(friends.count()).toBe(2); 28792 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 28793 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 28794 }); 28795 </file> 28796 </example> 28797 */ 28798var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { 28799 var NG_REMOVED = '$$NG_REMOVED'; 28800 var ngRepeatMinErr = minErr('ngRepeat'); 28801 28802 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 28803 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 28804 scope[valueIdentifier] = value; 28805 if (keyIdentifier) scope[keyIdentifier] = key; 28806 scope.$index = index; 28807 scope.$first = (index === 0); 28808 scope.$last = (index === (arrayLength - 1)); 28809 scope.$middle = !(scope.$first || scope.$last); 28810 // jshint bitwise: false 28811 scope.$odd = !(scope.$even = (index&1) === 0); 28812 // jshint bitwise: true 28813 }; 28814 28815 var getBlockStart = function(block) { 28816 return block.clone[0]; 28817 }; 28818 28819 var getBlockEnd = function(block) { 28820 return block.clone[block.clone.length - 1]; 28821 }; 28822 28823 28824 return { 28825 restrict: 'A', 28826 multiElement: true, 28827 transclude: 'element', 28828 priority: 1000, 28829 terminal: true, 28830 $$tlb: true, 28831 compile: function ngRepeatCompile($element, $attr) { 28832 var expression = $attr.ngRepeat; 28833 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); 28834 28835 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*$/); 28836 28837 if (!match) { 28838 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 28839 expression); 28840 } 28841 28842 var lhs = match[1]; 28843 var rhs = match[2]; 28844 var aliasAs = match[3]; 28845 var trackByExp = match[4]; 28846 28847 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 28848 28849 if (!match) { 28850 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 28851 lhs); 28852 } 28853 var valueIdentifier = match[3] || match[1]; 28854 var keyIdentifier = match[2]; 28855 28856 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 28857 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 28858 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", 28859 aliasAs); 28860 } 28861 28862 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 28863 var hashFnLocals = {$id: hashKey}; 28864 28865 if (trackByExp) { 28866 trackByExpGetter = $parse(trackByExp); 28867 } else { 28868 trackByIdArrayFn = function(key, value) { 28869 return hashKey(value); 28870 }; 28871 trackByIdObjFn = function(key) { 28872 return key; 28873 }; 28874 } 28875 28876 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 28877 28878 if (trackByExpGetter) { 28879 trackByIdExpFn = function(key, value, index) {
vendor: 11,339 bytes, lines 28880-29131
28880 // assign key, value, and $index to the locals so that they can be used in hash functions 28881 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 28882 hashFnLocals[valueIdentifier] = value; 28883 hashFnLocals.$index = index; 28884 return trackByExpGetter($scope, hashFnLocals); 28885 }; 28886 } 28887 28888 // Store a list of elements from previous run. This is a hash where key is the item from the 28889 // iterator, and the value is objects with following properties. 28890 // - scope: bound scope 28891 // - element: previous element. 28892 // - index: position 28893 // 28894 // We are using no-proto object so that we don't need to guard against inherited props via 28895 // hasOwnProperty. 28896 var lastBlockMap = createMap(); 28897 28898 //watch props 28899 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 28900 var index, length, 28901 previousNode = $element[0], // node that cloned nodes should be inserted after 28902 // initialized to the comment node anchor 28903 nextNode, 28904 // Same as lastBlockMap but it has the current state. It will become the 28905 // lastBlockMap on the next iteration. 28906 nextBlockMap = createMap(), 28907 collectionLength, 28908 key, value, // key/value of iteration 28909 trackById, 28910 trackByIdFn, 28911 collectionKeys, 28912 block, // last object information {scope, element, id} 28913 nextBlockOrder, 28914 elementsToRemove; 28915 28916 if (aliasAs) { 28917 $scope[aliasAs] = collection; 28918 } 28919 28920 if (isArrayLike(collection)) { 28921 collectionKeys = collection; 28922 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 28923 } else { 28924 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 28925 // if object, extract keys, in enumeration order, unsorted 28926 collectionKeys = []; 28927 for (var itemKey in collection) { 28928 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 28929 collectionKeys.push(itemKey); 28930 } 28931 } 28932 } 28933 28934 collectionLength = collectionKeys.length; 28935 nextBlockOrder = new Array(collectionLength); 28936 28937 // locate existing items 28938 for (index = 0; index < collectionLength; index++) { 28939 key = (collection === collectionKeys) ? index : collectionKeys[index]; 28940 value = collection[key]; 28941 trackById = trackByIdFn(key, value, index); 28942 if (lastBlockMap[trackById]) { 28943 // found previously seen block 28944 block = lastBlockMap[trackById]; 28945 delete lastBlockMap[trackById]; 28946 nextBlockMap[trackById] = block; 28947 nextBlockOrder[index] = block; 28948 } else if (nextBlockMap[trackById]) { 28949 // if collision detected. restore lastBlockMap and throw an error 28950 forEach(nextBlockOrder, function(block) { 28951 if (block && block.scope) lastBlockMap[block.id] = block; 28952 }); 28953 throw ngRepeatMinErr('dupes', 28954 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 28955 expression, trackById, value); 28956 } else { 28957 // new never before seen block 28958 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 28959 nextBlockMap[trackById] = true; 28960 } 28961 } 28962 28963 // remove leftover items 28964 for (var blockKey in lastBlockMap) { 28965 block = lastBlockMap[blockKey]; 28966 elementsToRemove = getBlockNodes(block.clone); 28967 $animate.leave(elementsToRemove); 28968 if (elementsToRemove[0].parentNode) { 28969 // if the element was not removed yet because of pending animation, mark it as deleted 28970 // so that we can ignore it later 28971 for (index = 0, length = elementsToRemove.length; index < length; index++) { 28972 elementsToRemove[index][NG_REMOVED] = true; 28973 } 28974 } 28975 block.scope.$destroy(); 28976 } 28977 28978 // we are not using forEach for perf reasons (trying to avoid #call) 28979 for (index = 0; index < collectionLength; index++) { 28980 key = (collection === collectionKeys) ? index : collectionKeys[index]; 28981 value = collection[key]; 28982 block = nextBlockOrder[index]; 28983 28984 if (block.scope) { 28985 // if we have already seen this object, then we need to reuse the 28986 // associated scope/element 28987 28988 nextNode = previousNode; 28989 28990 // skip nodes that are already pending removal via leave animation 28991 do { 28992 nextNode = nextNode.nextSibling; 28993 } while (nextNode && nextNode[NG_REMOVED]); 28994 28995 if (getBlockStart(block) != nextNode) { 28996 // existing item which got moved 28997 $animate.move(getBlockNodes(block.clone), null, previousNode); 28998 } 28999 previousNode = getBlockEnd(block); 29000 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 29001 } else { 29002 // new item which we don't know about 29003 $transclude(function ngRepeatTransclude(clone, scope) { 29004 block.scope = scope; 29005 // http://jsperf.com/clone-vs-createcomment 29006 var endNode = ngRepeatEndComment.cloneNode(false); 29007 clone[clone.length++] = endNode; 29008 29009 $animate.enter(clone, null, previousNode); 29010 previousNode = endNode; 29011 // Note: We only need the first/last node of the cloned nodes. 29012 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 29013 // by a directive with templateUrl when its template arrives. 29014 block.clone = clone; 29015 nextBlockMap[block.id] = block; 29016 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 29017 }); 29018 } 29019 } 29020 lastBlockMap = nextBlockMap; 29021 }); 29022 }; 29023 } 29024 }; 29025}]; 29026 29027var NG_HIDE_CLASS = 'ng-hide'; 29028var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 29029/** 29030 * @ngdoc directive 29031 * @name ngShow 29032 * @multiElement 29033 * 29034 * @description 29035 * The `ngShow` directive shows or hides the given HTML element based on the expression 29036 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 29037 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 29038 * in AngularJS and sets the display style to none (using an !important flag). 29039 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 29040 * 29041 * ```html 29042 * <!-- when $scope.myValue is truthy (element is visible) --> 29043 * <div ng-show="myValue"></div> 29044 * 29045 * <!-- when $scope.myValue is falsy (element is hidden) --> 29046 * <div ng-show="myValue" class="ng-hide"></div> 29047 * ``` 29048 * 29049 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 29050 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 29051 * from the element causing the element not to appear hidden. 29052 * 29053 * ## Why is !important used? 29054 * 29055 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 29056 * can be easily overridden by heavier selectors. For example, something as simple 29057 * as changing the display style on a HTML list item would make hidden elements appear visible. 29058 * This also becomes a bigger issue when dealing with CSS frameworks. 29059 * 29060 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 29061 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 29062 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 29063 * 29064 * ### Overriding `.ng-hide` 29065 * 29066 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 29067 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 29068 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 29069 * with extra animation classes that can be added. 29070 * 29071 * ```css 29072 * .ng-hide:not(.ng-hide-animate) { 29073 * /* this is just another form of hiding an element */ 29074 * display: block!important; 29075 * position: absolute; 29076 * top: -9999px; 29077 * left: -9999px; 29078 * } 29079 * ``` 29080 * 29081 * By default you don't need to override in CSS anything and the animations will work around the display style. 29082 * 29083 * ## A note about animations with `ngShow` 29084 * 29085 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 29086 * is true and false. This system works like the animation system present with ngClass except that 29087 * you must also include the !important flag to override the display property 29088 * so that you can perform an animation when the element is hidden during the time of the animation. 29089 * 29090 * ```css 29091 * // 29092 * //a working example can be found at the bottom of this page 29093 * // 29094 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 29095 * /* this is required as of 1.3x to properly 29096 * apply all styling in a show/hide animation */ 29097 * transition: 0s linear all; 29098 * } 29099 * 29100 * .my-element.ng-hide-add-active, 29101 * .my-element.ng-hide-remove-active { 29102 * /* the transition is defined in the active class */ 29103 * transition: 1s linear all; 29104 * } 29105 * 29106 * .my-element.ng-hide-add { ... } 29107 * .my-element.ng-hide-add.ng-hide-add-active { ... } 29108 * .my-element.ng-hide-remove { ... } 29109 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 29110 * ``` 29111 * 29112 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display 29113 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 29114 * 29115 * @animations 29116 * | Animation | Occurs | 29117 * |----------------------------------|-------------------------------------| 29118 * | {@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 | 29119 * | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngShow` expression evaluates to a truthy value and just before contents are set to visible | 29120 * 29121 * @element ANY 29122 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 29123 * then the element is shown or hidden respectively. 29124 * 29125 * @example 29126 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 29127 <file name="index.html"> 29128 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 29129 <div> 29130 Show: 29131 <div class="check-element animate-show" ng-show="checked">
29132 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 29133 </div> 29134 </div> 29135 <div> 29136 Hide: 29137 <div class="check-element animate-show" ng-hide="checked"> 29138 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 29139 </div> 29140 </div> 29141 </file> 29142 <file name="glyphicons.css"> 29143 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 29144 </file> 29145 <file name="animations.css"> 29146 .animate-show { 29147 line-height: 20px; 29148 opacity: 1; 29149 padding: 10px; 29150 border: 1px solid black; 29151 background: white; 29152 } 29153 29154 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 29155 transition: all linear 0.5s; 29156 } 29157 29158 .animate-show.ng-hide { 29159 line-height: 0; 29160 opacity: 0; 29161 padding: 0 10px; 29162 } 29163 29164 .check-element { 29165 padding: 10px; 29166 border: 1px solid black; 29167 background: white; 29168 } 29169 </file> 29170 <file name="protractor.js" type="protractor"> 29171 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 29172 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 29173 29174 it('should check ng-show / ng-hide', function() { 29175 expect(thumbsUp.isDisplayed()).toBeFalsy(); 29176 expect(thumbsDown.isDisplayed()).toBeTruthy(); 29177 29178 element(by.model('checked')).click(); 29179 29180 expect(thumbsUp.isDisplayed()).toBeTruthy(); 29181 expect(thumbsDown.isDisplayed()).toBeFalsy(); 29182 }); 29183 </file> 29184 </example> 29185 */ 29186var ngShowDirective = ['$animate', function($animate) { 29187 return { 29188 restrict: 'A', 29189 multiElement: true, 29190 link: function(scope, element, attr) { 29191 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 29192 // we're adding a temporary, animation-specific class for ng-hide since this way 29193 // we can control when the element is actually displayed on screen without having 29194 // to have a global/greedy CSS selector that breaks when other animations are run. 29195 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 29196 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 29197 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 29198 }); 29199 }); 29200 } 29201 }; 29202}]; 29203 29204 29205/** 29206 * @ngdoc directive 29207 * @name ngHide 29208 * @multiElement 29209 * 29210 * @description 29211 * The `ngHide` directive shows or hides the given HTML element based on the expression 29212 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 29213 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 29214 * in AngularJS and sets the display style to none (using an !important flag). 29215 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 29216 * 29217 * ```html 29218 * <!-- when $scope.myValue is truthy (element is hidden) --> 29219 * <div ng-hide="myValue" class="ng-hide"></div> 29220 * 29221 * <!-- when $scope.myValue is falsy (element is visible) --> 29222 * <div ng-hide="myValue"></div> 29223 * ``` 29224 * 29225 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 29226 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 29227 * from the element causing the element not to appear hidden. 29228 * 29229 * ## Why is !important used? 29230 * 29231 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 29232 * can be easily overridden by heavier selectors. For example, something as simple 29233 * as changing the display style on a HTML list item would make hidden elements appear visible. 29234 * This also becomes a bigger issue when dealing with CSS frameworks. 29235 * 29236 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 29237 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 29238 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 29239 * 29240 * ### Overriding `.ng-hide` 29241 * 29242 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 29243 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 29244 * class in CSS: 29245 * 29246 * ```css 29247 * .ng-hide { 29248 * /* this is just another form of hiding an element */ 29249 * display: block!important; 29250 * position: absolute; 29251 * top: -9999px; 29252 * left: -9999px; 29253 * } 29254 * ``` 29255 * 29256 * By default you don't need to override in CSS anything and the animations will work around the display style. 29257 * 29258 * ## A note about animations with `ngHide` 29259 * 29260 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 29261 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 29262 * CSS class is added and removed for you instead of your own CSS class. 29263 * 29264 * ```css 29265 * // 29266 * //a working example can be found at the bottom of this page 29267 * // 29268 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 29269 * transition: 0.5s linear all; 29270 * } 29271 * 29272 * .my-element.ng-hide-add { ... } 29273 * .my-element.ng-hide-add.ng-hide-add-active { ... } 29274 * .my-element.ng-hide-remove { ... } 29275 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 29276 * ``` 29277 * 29278 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display 29279 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 29280 * 29281 * @animations 29282 * | Animation | Occurs | 29283 * |----------------------------------|-------------------------------------| 29284 * | {@link $animate#addClass addClass} `.ng-hide` | after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden | 29285 * | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible | 29286 * 29287 * 29288 * @element ANY 29289 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 29290 * the element is shown or hidden respectively. 29291 * 29292 * @example 29293 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 29294 <file name="index.html"> 29295 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 29296 <div> 29297 Show: 29298 <div class="check-element animate-hide" ng-show="checked">
29299 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 29300 </div> 29301 </div> 29302 <div> 29303 Hide: 29304 <div class="check-element animate-hide" ng-hide="checked"> 29305 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 29306 </div> 29307 </div> 29308 </file> 29309 <file name="glyphicons.css"> 29310 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 29311 </file> 29312 <file name="animations.css"> 29313 .animate-hide { 29314 transition: all linear 0.5s; 29315 line-height: 20px; 29316 opacity: 1; 29317 padding: 10px; 29318 border: 1px solid black; 29319 background: white; 29320 } 29321 29322 .animate-hide.ng-hide { 29323 line-height: 0; 29324 opacity: 0; 29325 padding: 0 10px; 29326 } 29327 29328 .check-element { 29329 padding: 10px; 29330 border: 1px solid black; 29331 background: white; 29332 } 29333 </file> 29334 <file name="protractor.js" type="protractor"> 29335 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 29336 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 29337 29338 it('should check ng-show / ng-hide', function() { 29339 expect(thumbsUp.isDisplayed()).toBeFalsy(); 29340 expect(thumbsDown.isDisplayed()).toBeTruthy(); 29341 29342 element(by.model('checked')).click(); 29343 29344 expect(thumbsUp.isDisplayed()).toBeTruthy(); 29345 expect(thumbsDown.isDisplayed()).toBeFalsy(); 29346 }); 29347 </file> 29348 </example> 29349 */ 29350var ngHideDirective = ['$animate', function($animate) { 29351 return { 29352 restrict: 'A', 29353 multiElement: true, 29354 link: function(scope, element, attr) { 29355 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 29356 // The comment inside of the ngShowDirective explains why we add and 29357 // remove a temporary class for the show/hide animation 29358 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 29359 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 29360 }); 29361 }); 29362 } 29363 }; 29364}]; 29365 29366/** 29367 * @ngdoc directive 29368 * @name ngStyle 29369 * @restrict AC 29370 * 29371 * @description 29372 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 29373 * 29374 * @element ANY 29375 * @param {expression} ngStyle 29376 * 29377 * {@link guide/expression Expression} which evals to an 29378 * object whose keys are CSS style names and values are corresponding values for those CSS 29379 * keys. 29380 * 29381 * Since some CSS style names are not valid keys for an object, they must be quoted. 29382 * See the 'background-color' style in the example below. 29383 * 29384 * @example 29385 <example> 29386 <file name="index.html"> 29387 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 29388 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 29389 <input type="button" value="clear" ng-click="myStyle={}"> 29390 <br/> 29391 <span ng-style="myStyle">Sample Text</span> 29392 <pre>myStyle={{myStyle}}</pre> 29393 </file> 29394 <file name="style.css"> 29395 span { 29396 color: black; 29397 } 29398 </file> 29399 <file name="protractor.js" type="protractor"> 29400 var colorSpan = element(by.css('span')); 29401 29402 it('should check ng-style', function() { 29403 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 29404 element(by.css('input[value=\'set color\']')).click(); 29405 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 29406 element(by.css('input[value=clear]')).click(); 29407 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 29408 }); 29409 </file> 29410 </example> 29411 */ 29412var ngStyleDirective = ngDirective(function(scope, element, attr) { 29413 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 29414 if (oldStyles && (newStyles !== oldStyles)) { 29415 forEach(oldStyles, function(val, style) { element.css(style, '');}); 29416 } 29417 if (newStyles) element.css(newStyles); 29418 }, true); 29419}); 29420 29421/** 29422 * @ngdoc directive 29423 * @name ngSwitch 29424 * @restrict EA 29425 * 29426 * @description 29427 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 29428 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 29429 * as specified in the template. 29430 * 29431 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 29432 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 29433 * matches the value obtained from the evaluated expression. In other words, you define a container element 29434 * (where you place the directive), place an expression on the **`on="..."` attribute** 29435 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 29436 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 29437 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 29438 * attribute is displayed. 29439 * 29440 * <div class="alert alert-info"> 29441 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 29442 * as literal string values to match against. 29443 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
29443omeVal"` not against the 29444 * value of the expression `$scope.someVal`. 29445 * </div> 29446 29447 * @animations 29448 * | Animation | Occurs | 29449 * |----------------------------------|-------------------------------------| 29450 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | 29451 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | 29452 * 29453 * @usage 29454 * 29455 * ``` 29456 * <ANY ng-switch="expression"> 29457 * <ANY ng-switch-when="matchValue1">...</ANY> 29458 * <ANY ng-switch-when="matchValue2">...</ANY> 29459 * <ANY ng-switch-default>...</ANY> 29460 * </ANY> 29461 * ``` 29462 * 29463 * 29464 * @scope 29465 * @priority 1200 29466 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 29467 * On child elements add: 29468 * 29469 * * `ngSwitchWhen`: the case statement to match against. If match then this 29470 * case will be displayed. If the same match appears multiple times, all the 29471 * elements will be displayed. 29472 * * `ngSwitchDefault`: the default case when no other case match. If there 29473 * are multiple default cases, all of them will be displayed when no other 29474 * case match. 29475 * 29476 * 29477 * @example 29478 <example module="switchExample" deps="angular-animate.js" animations="true"> 29479 <file name="index.html">
29480 <div ng-controller="ExampleController"> 29481 <select ng-model="selection" ng-options="item for item in items"> 29482 </select> 29483 <code>selection={{selection}}</code> 29484 <hr/> 29485 <div class="animate-switch-container" 29486 ng-switch on="selection"> 29487 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 29488 <div class="animate-switch" ng-switch-when="home">Home Span</div> 29489 <div class="animate-switch" ng-switch-default>default</div> 29490 </div> 29491 </div> 29492 </file> 29493 <file name="script.js"> 29494 angular.module('switchExample', ['ngAnimate']) 29495 .controller('ExampleController', ['$scope', function($scope) { 29496 $scope.items = ['settings', 'home', 'other']; 29497 $scope.selection = $scope.items[0]; 29498 }]); 29499 </file> 29500 <file name="animations.css"> 29501 .animate-switch-container { 29502 position:relative; 29503 background:white; 29504 border:1px solid black; 29505 height:40px; 29506 overflow:hidden; 29507 } 29508 29509 .animate-switch { 29510 padding:10px; 29511 } 29512 29513 .animate-switch.ng-animate { 29514 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 29515 29516 position:absolute; 29517 top:0; 29518 left:0; 29519 right:0; 29520 bottom:0; 29521 } 29522 29523 .animate-switch.ng-leave.ng-leave-active, 29524 .animate-switch.ng-enter { 29525 top:-50px; 29526 } 29527 .animate-switch.ng-leave, 29528 .animate-switch.ng-enter.ng-enter-active { 29529 top:0; 29530 } 29531 </file> 29532 <file name="protractor.js" type="protractor"> 29533 var switchElem = element(by.css('[ng-switch]')); 29534 var select = element(by.model('selection')); 29535 29536 it('should start in settings', function() { 29537 expect(switchElem.getText()).toMatch(/Settings Div/); 29538 }); 29539 it('should change to home', function() { 29540 select.all(by.css('option')).get(1).click(); 29541 expect(switchElem.getText()).toMatch(/Home Span/); 29542 }); 29543 it('should select default', function() { 29544 select.all(by.css('option')).get(2).click(); 29545 expect(switchElem.getText()).toMatch(/default/); 29546 }); 29547 </file> 29548 </example> 29549 */ 29550var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { 29551 return { 29552 require: 'ngSwitch', 29553 29554 // asks for $scope to fool the BC controller module 29555 controller: ['$scope', function ngSwitchController() { 29556 this.cases = {}; 29557 }], 29558 link: function(scope, element, attr, ngSwitchController) { 29559 var watchExpr = attr.ngSwitch || attr.on, 29560 selectedTranscludes = [], 29561 selectedElements = [], 29562 previousLeaveAnimations = [], 29563 selectedScopes = []; 29564 29565 var spliceFactory = function(array, index) { 29566 return function() { array.splice(index, 1); }; 29567 }; 29568 29569 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 29570 var i, ii; 29571 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 29572 $animate.cancel(previousLeaveAnimations[i]); 29573 } 29574 previousLeaveAnimations.length = 0; 29575 29576 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 29577 var selected = getBlockNodes(selectedElements[i].clone); 29578 selectedScopes[i].$destroy(); 29579 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 29580 promise.then(spliceFactory(previousLeaveAnimations, i)); 29581 } 29582 29583 selectedElements.length = 0; 29584 selectedScopes.length = 0; 29585 29586 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 29587 forEach(selectedTranscludes, function(selectedTransclude) { 29588 selectedTransclude.transclude(function(caseElement, selectedScope) { 29589 selectedScopes.push(selectedScope); 29590 var anchor = selectedTransclude.element; 29591 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); 29592 var block = { clone: caseElement }; 29593 29594 selectedElements.push(block); 29595 $animate.enter(caseElement, anchor.parent(), anchor); 29596 }); 29597 }); 29598 } 29599 }); 29600 } 29601 }; 29602}]; 29603 29604var ngSwitchWhenDirective = ngDirective({ 29605 transclude: 'element', 29606 priority: 1200, 29607 require: '^ngSwitch', 29608 multiElement: true, 29609 link: function(scope, element, attrs, ctrl, $transclude) { 29610 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 29611 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 29612 } 29613}); 29614 29615var ngSwitchDefaultDirective = ngDirective({ 29616 transclude: 'element', 29617 priority: 1200, 29618 require: '^ngSwitch', 29619 multiElement: true, 29620 link: function(scope, element, attr, ctrl, $transclude) { 29621 ctrl.cases['?'] = (ctrl.cases['?'] || []); 29622 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 29623 } 29624}); 29625 29626/** 29627 * @ngdoc directive 29628 * @name ngTransclude 29629 * @restrict EAC 29630 * 29631 * @description 29632 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 29633 * 29634 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 29635 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 29636 * 29637 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 29638 * content of this element will be removed before the transcluded content is inserted. 29639 * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case 29640 * that no transcluded content is provided. 29641 * 29642 * @element ANY 29643 * 29644 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 29645 * or its value is the same as the name of the attribute then the default slot is used. 29646 * 29647 * @example 29648 * ### Basic transclusion 29649 * This example demonstrates basic transclusion of content into a component directive. 29650 * <example name="simpleTranscludeExample" module="transcludeExample"> 29651 * <file name="index.html"> 29652 * <script> 29653 * angular.module('transcludeExample', []) 29654 * .directive('pane', function(){ 29655 * return { 29656 * restrict: 'E', 29657 * transclude: true, 29658 * scope: { title:'@' }, 29659 * template: '<div style="border: 1px solid black;">
29659' + 29660 * '<div style="background-color: gray">{{title}}</div>' + 29661 * '<ng-transclude></ng-transclude>' + 29662 * '</div>' 29663 * }; 29664 * }) 29665 * .controller('ExampleController', ['$scope', function($scope) { 29666 * $scope.title = 'Lorem Ipsum'; 29667 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 29668 * }]); 29669 * </script> 29670 * <div ng-controller="ExampleController"> 29671 * <input ng-model="title" aria-label="title"> <br/> 29672 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 29673 * <pane title="{{title}}">{{text}}</pane> 29674 * </div> 29675 * </file> 29676 * <file name="protractor.js" type="protractor"> 29677 * it('should have transcluded', function() { 29678 * var titleElement = element(by.model('title')); 29679 * titleElement.clear(); 29680 * titleElement.sendKeys('TITLE'); 29681 * var textElement = element(by.model('text')); 29682 * textElement.clear(); 29683 * textElement.sendKeys('TEXT'); 29684 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 29685 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 29686 * }); 29687 * </file> 29688 * </example> 29689 * 29690 * @example 29691 * ### Transclude fallback content 29692 * This example shows how to use `NgTransclude` with fallback content, that 29693 * is displayed if no transcluded content is provided. 29694 * 29695 * <example module="transcludeFallbackContentExample"> 29696 * <file name="index.html"> 29697 * <script> 29698 * angular.module('transcludeFallbackContentExample', []) 29699 * .directive('myButton', function(){ 29700 * return { 29701 * restrict: 'E', 29702 * transclude: true, 29703 * scope: true, 29704 * template: '<button style="cursor: pointer;">' + 29705 * '<ng-transclude>' + 29706 * '<b style="color: red;">Button1</b>' + 29707 * '</ng-transclude>' + 29708 * '</button>' 29709 * }; 29710 * }); 29711 * </script> 29712 * <!-- fallback button content --> 29713 * <my-button id="fallback"></my-button> 29714 * <!-- modified button content --> 29715 * <my-button id="modified"> 29716 * <i style="color: green;">Button2</i> 29717 * </my-button> 29718 * </file> 29719 * <file name="protractor.js" type="protractor"> 29720 * it('should have different transclude element content', function() { 29721 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 29722 * expect(element(by.id('modified')).getText()).toBe('Button2'); 29723 * }); 29724 * </file> 29725 * </example> 29726 * 29727 * @example 29728 * ### Multi-slot transclusion 29729 * This example demonstrates using multi-slot transclusion in a component directive. 29730 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 29731 * <file name="index.html"> 29732 * <style> 29733 * .title, .footer { 29734 * background-color: gray 29735 * } 29736 * </style>
29737 * <div ng-controller="ExampleController"> 29738 * <input ng-model="title" aria-label="title"> <br/> 29739 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 29740 * <pane> 29741 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 29742 * <pane-body><p>{{text}}</p></pane-body> 29743 * </pane> 29744 * </div> 29745 * </file> 29746 * <file name="app.js"> 29747 * angular.module('multiSlotTranscludeExample', []) 29748 * .directive('pane', function(){ 29749 * return { 29750 * restrict: 'E', 29751 * transclude: { 29752 * 'title': '?paneTitle', 29753 * 'body': 'paneBody', 29754 * 'footer': '?paneFooter' 29755 * }, 29756 * template: '<div style="border: 1px solid black;">' + 29757 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 29758 * '<div ng-transclude="body"></div>' + 29759 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 29760 * '</div>' 29761 * }; 29762 * }) 29763 * .controller('ExampleController', ['$scope', function($scope) { 29764 * $scope.title = 'Lorem Ipsum'; 29765 * $scope.link = "https://google.com"; 29766 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 29767 * }]); 29768 * </file> 29769 * <file name="protractor.js" type="protractor"> 29770 * it('should have transcluded the title and the body', function() { 29771 * var titleElement = element(by.model('title')); 29772 * titleElement.clear(); 29773 * titleElement.sendKeys('TITLE'); 29774 * var textElement = element(by.model('text')); 29775 * textElement.clear(); 29776 * textElement.sendKeys('TEXT'); 29777 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 29778 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 29779 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 29780 * }); 29781 * </file> 29782 * </example> 29783 */ 29784var ngTranscludeMinErr = minErr('ngTransclude'); 29785var ngTranscludeDirective = ngDirective({ 29786 restrict: 'EAC', 29787 link: function($scope, $element, $attrs, controller, $transclude) { 29788 29789 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 29790 // If the attribute is of the form: `ng-transclude="ng-transclude"` 29791 // then treat it like the default 29792 $attrs.ngTransclude = ''; 29793 } 29794 29795 function ngTranscludeCloneAttachFn(clone) { 29796 if (clone.length) { 29797 $element.empty(); 29798 $element.append(clone); 29799 } 29800 } 29801 29802 if (!$transclude) { 29803 throw ngTranscludeMinErr('orphan', 29804 'Illegal use of ngTransclude directive in the template! ' + 29805 'No parent directive that requires a transclusion found. ' + 29806 'Element: {0}', 29807 startingTag($element)); 29808 } 29809 29810 // If there is no slot name defined or the slot name is not optional 29811 // then transclude the slot 29812 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 29813 $transclude(ngTranscludeCloneAttachFn, null, slotName); 29814 } 29815}); 29816 29817/** 29818 * @ngdoc directive 29819 * @name script 29820 * @restrict E 29821 * 29822 * @description 29823 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 29824 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 29825 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 29826 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 29827 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 29828 * 29829 * @param {string} type Must be set to `'text/ng-template'`. 29830 * @param {string} id Cache name of the template. 29831 * 29832 * @example 29833 <example> 29834 <file name="index.html"> 29835 <script type="text/ng-template" id="/tpl.html"> 29836 Content of the template. 29837 </script> 29838 29839 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 29840 <div id="tpl-content" ng-include src="currentTpl"></div> 29841 </file> 29842 <file name="protractor.js" type="protractor"> 29843 it('should load template defined inside script tag', function() {
29844 element(by.css('#tpl-link')).click(); 29845 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 29846 }); 29847 </file> 29848 </example> 29849 */ 29850var scriptDirective = ['$templateCache', function($templateCache) { 29851 return { 29852 restrict: 'E', 29853 terminal: true, 29854 compile: function(element, attr) { 29855 if (attr.type == 'text/ng-template') { 29856 var templateUrl = attr.id, 29857 text = element[0].text; 29858 29859 $templateCache.put(templateUrl, text); 29860 } 29861 } 29862 }; 29863}]; 29864 29865var noopNgModelController = { $setViewValue: noop, $render: noop }; 29866 29867function chromeHack(optionElement) { 29868 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 29869 // Adding an <option selected="selected"> element to a <select required="required"> should 29870 // automatically select the new element 29871 if (optionElement[0].hasAttribute('selected')) { 29872 optionElement[0].selected = true; 29873 } 29874} 29875 29876/** 29877 * @ngdoc type 29878 * @name select.SelectController 29879 * @description 29880 * The controller for the `<select>` directive. This provides support for reading 29881 * and writing the selected value(s) of the control and also coordinates dynamically 29882 * added `<option>` elements, perhaps by an `ngRepeat` directive. 29883 */ 29884var SelectController = 29885 ['$element', '$scope', function($element, $scope) { 29886 29887 var self = this, 29888 optionsMap = new HashMap(); 29889 29890 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 29891 self.ngModelCtrl = noopNgModelController; 29892 29893 // The "unknown" option is one that is prepended to the list if the viewValue 29894 // does not match any of the options. When it is rendered the value of the unknown 29895 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 29896 // 29897 // We can't just jqLite('<option>') since jqLite is not smart enough 29898 // to create it in <select> and IE barfs otherwise. 29899 self.unknownOption = jqLite(window.document.createElement('option')); 29900 self.renderUnknownOption = function(val) { 29901 var unknownVal = '? ' + hashKey(val) + ' ?'; 29902 self.unknownOption.val(unknownVal); 29903 $element.prepend(self.unknownOption); 29904 $element.val(unknownVal); 29905 }; 29906 29907 $scope.$on('$destroy', function() { 29908 // disable unknown option so that we don't do work when the whole select is being destroyed 29909 self.renderUnknownOption = noop; 29910 }); 29911 29912 self.removeUnknownOption = function() { 29913 if (self.unknownOption.parent()) self.unknownOption.remove(); 29914 }; 29915 29916 29917 // Read the value of the select control, the implementation of this changes depending 29918 // upon whether the select can have multiple values and whether ngOptions is at work. 29919 self.readValue = function readSingleValue() { 29920 self.removeUnknownOption(); 29921 return $element.val(); 29922 }; 29923 29924 29925 // Write the value to the select control, the implementation of this changes depending 29926 // upon whether the select can have multiple values and whether ngOptions is at work. 29927 self.writeValue = function writeSingleValue(value) { 29928 if (self.hasOption(value)) { 29929 self.removeUnknownOption(); 29930 $element.val(value); 29931 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 29932 } else { 29933 if (value == null && self.emptyOption) { 29934 self.removeUnknownOption(); 29935 $element.val(''); 29936 } else { 29937 self.renderUnknownOption(value); 29938 } 29939 } 29940 }; 29941 29942 29943 // Tell the select control that an option, with the given value, has been added 29944 self.addOption = function(value, element) { 29945 // Skip comment nodes, as they only pollute the `optionsMap` 29946 if (element[0].nodeType === NODE_TYPE_COMMENT) return; 29947 29948 assertNotHasOwnProperty(value, '"option value"'); 29949 if (value === '') { 29950 self.emptyOption = element; 29951 } 29952 var count = optionsMap.get(value) || 0; 29953 optionsMap.put(value, count + 1); 29954 self.ngModelCtrl.$render(); 29955 chromeHack(element); 29956 }; 29957 29958 // Tell the select control that an option, with the given value, has been removed 29959 self.removeOption = function(value) { 29960 var count = optionsMap.get(value); 29961 if (count) { 29962 if (count === 1) { 29963 optionsMap.remove(value); 29964 if (value === '') { 29965 self.emptyOption = undefined; 29966 } 29967 } else { 29968 optionsMap.put(value, count - 1); 29969 } 29970 } 29971 }; 29972 29973 // Check whether the select control has an option matching the given value 29974 self.hasOption = function(value) { 29975 return !!optionsMap.get(value); 29976 }; 29977 29978 29979 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 29980 29981 if (interpolateValueFn) { 29982 // The value attribute is interpolated 29983 var oldVal; 29984 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 29985 if (isDefined(oldVal)) { 29986 self.removeOption(oldVal); 29987 } 29988 oldVal = newVal; 29989 self.addOption(newVal, optionElement); 29990 }); 29991 } else if (interpolateTextFn) { 29992 // The text content is interpolated 29993 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 29994 optionAttrs.$set('value', newVal); 29995 if (oldVal !== newVal) { 29996 self.removeOption(oldVal); 29997 } 29998 self.addOption(newVal, optionElement); 29999 }); 30000 } else { 30001 // The value attribute is static 30002 self.addOption(optionAttrs.value, optionElement); 30003 } 30004 30005 optionElement.on('$destroy', function() { 30006 self.removeOption(optionAttrs.value); 30007 self.ngModelCtrl.$render(); 30008 }); 30009 }; 30010}]; 30011 30012/** 30013 * @ngdoc directive 30014 * @name select 30015 * @restrict E 30016 * 30017 * @description 30018 * HTML `SELECT` element with angular data-binding. 30019 * 30020 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 30021 * between the scope and the `<select>` control (including setting default values). 30022 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 30023 * {@link ngOptions `ngOptions`} directives. 30024 * 30025 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 30026 * to the model identified by the `ngModel` directive. With static or repeated o
30026ptions, this is 30027 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 30028 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 30029 * 30030 * <div class="alert alert-warning"> 30031 * Note that the value of a `select` directive used without `ngOptions` is always a string. 30032 * When the model needs to be bound to a non-string value, you must either explicitly convert it 30033 * using a directive (see example below) or use `ngOptions` to specify the set of options. 30034 * This is because an option element can only be bound to string values at present. 30035 * </div> 30036 * 30037 * If the viewValue of `ngModel` does not match any of the options, then the control 30038 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 30039 * 30040 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 30041 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 30042 * option. See example below for demonstration. 30043 * 30044 * <div class="alert alert-info"> 30045 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 30046 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as 30047 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 30048 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 30049 * a new scope for each repeated instance. 30050 * </div> 30051 * 30052 * 30053 * @param {string} ngModel Assignable angular expression to data-bind to. 30054 * @param {string=} name Property name of the form under which the control is published. 30055 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 30056 * bound to the model as an array. 30057 * @param {string=} required Sets `required` validation error key if the value is not entered. 30058 * @param {string=} ngRequired Adds required attribute and required validation constraint to 30059 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 30060 * when you want to data-bind to the required attribute. 30061 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 30062 * interaction with the select element. 30063 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 30064 * set on the model on selection. See {@link ngOptions `ngOptions`}. 30065 * 30066 * @example 30067 * ### Simple `select` elements with static options 30068 * 30069 * <example name="static-select" module="staticSelect"> 30070 * <file name="index.html">
30071 * <div ng-controller="ExampleController"> 30072 * <form name="myForm"> 30073 * <label for="singleSelect"> Single select: </label><br> 30074 * <select name="singleSelect" ng-model="data.singleSelect"> 30075 * <option value="option-1">Option 1</option> 30076 * <option value="option-2">Option 2</option> 30077 * </select><br> 30078 * 30079 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 30080 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 30081 * <option value="">---Please select---</option> <!-- not selected / blank option --> 30082 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 30083 * <option value="option-2">Option 2</option> 30084 * </select><br> 30085 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 30086 * <tt>singleSelect = {{data.singleSelect}}</tt> 30087 * 30088 * <hr> 30089 * <label for="multipleSelect"> Multiple select: </label><br> 30090 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 30091 * <option value="option-1">Option 1</option> 30092 * <option value="option-2">Option 2</option> 30093 * <option value="option-3">Option 3</option> 30094 * </select><br> 30095 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 30096 * </form> 30097 * </div> 30098 * </file> 30099 * <file name="app.js"> 30100 * angular.module('staticSelect', []) 30101 * .controller('ExampleController', ['$scope', function($scope) { 30102 * $scope.data = { 30103 * singleSelect: null, 30104 * multipleSelect: [], 30105 * option1: 'option-1', 30106 * }; 30107 * 30108 * $scope.forceUnknownOption = function() { 30109 * $scope.data.singleSelect = 'nonsense'; 30110 * }; 30111 * }]); 30112 * </file> 30113 *</example> 30114 * 30115 * ### Using `ngRepeat` to generate `select` options 30116 * <example name="ngrepeat-select" module="ngrepeatSelect"> 30117 * <file name="index.html">
30118 * <div ng-controller="ExampleController"> 30119 * <form name="myForm"> 30120 * <label for="repeatSelect"> Repeat select: </label> 30121 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 30122 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 30123 * </select> 30124 * </form> 30125 * <hr> 30126 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 30127 * </div> 30128 * </file> 30129 * <file name="app.js"> 30130 * angular.module('ngrepeatSelect', []) 30131 * .controller('ExampleController', ['$scope', function($scope) { 30132 * $scope.data = { 30133 * repeatSelect: null, 30134 * availableOptions: [ 30135 * {id: '1', name: 'Option A'}, 30136 * {id: '2', name: 'Option B'}, 30137 * {id: '3', name: 'Option C'} 30138 * ], 30139 * }; 30140 * }]); 30141 * </file> 30142 *</example> 30143 * 30144 * 30145 * ### Using `select` with `ngOptions` and setting a default value 30146 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 30147 * 30148 * <example name="select-with-default-values" module="defaultValueSelect"> 30149 * <file name="index.html">
30150 * <div ng-controller="ExampleController"> 30151 * <form name="myForm"> 30152 * <label for="mySelect">Make a choice:</label> 30153 * <select name="mySelect" id="mySelect" 30154 * ng-options="option.name for option in data.availableOptions track by option.id" 30155 * ng-model="data.selectedOption"></select> 30156 * </form> 30157 * <hr> 30158 * <tt>option = {{data.selectedOption}}</tt><br/> 30159 * </div> 30160 * </file> 30161 * <file name="app.js"> 30162 * angular.module('defaultValueSelect', []) 30163 * .controller('ExampleController', ['$scope', function($scope) { 30164 * $scope.data = { 30165 * availableOptions: [ 30166 * {id: '1', name: 'Option A'}, 30167 * {id: '2', name: 'Option B'}, 30168 * {id: '3', name: 'Option C'} 30169 * ], 30170 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 30171 * }; 30172 * }]); 30173 * </file> 30174 *</example> 30175 * 30176 * 30177 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 30178 * 30179 * <example name="select-with-non-string-options" module="nonStringSelect"> 30180 * <file name="index.html"> 30181 * <select ng-model="model.id" convert-to-number> 30182 * <option value="0">Zero</option> 30183 * <option value="1">One</option> 30184 * <option value="2">Two</option> 30185 * </select> 30186 * {{ model }} 30187 * </file> 30188 * <file name="app.js"> 30189 * angular.module('nonStringSelect', []) 30190 * .run(function($rootScope) { 30191 * $rootScope.model = { id: 2 }; 30192 * }) 30193 * .directive('convertToNumber', function() { 30194 * return { 30195 * require: 'ngModel', 30196 * link: function(scope, element, attrs, ngModel) { 30197 * ngModel.$parsers.push(function(val) { 30198 * return parseInt(val, 10); 30199 * }); 30200 * ngModel.$formatters.push(function(val) { 30201 * return '' + val; 30202 * }); 30203 * } 30204 * }; 30205 * }); 30206 * </file> 30207 * <file name="protractor.js" type="protractor"> 30208 * it('should initialize to model', function() { 30209 * var select = element(by.css('select')); 30210 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 30211 * }); 30212 * </file> 30213 * </example> 30214 * 30215 */ 30216var selectDirective = function() { 30217 30218 return { 30219 restrict: 'E', 30220 require: ['select', '?ngModel'], 30221 controller: SelectController, 30222 priority: 1, 30223 link: { 30224 pre: selectPreLink, 30225 post: selectPostLink 30226 } 30227 }; 30228 30229 function selectPreLink(scope, element, attr, ctrls) { 30230 30231 // if ngModel is not defined, we don't need to do anything 30232 var ngModelCtrl = ctrls[1]; 30233 if (!ngModelCtrl) return; 30234 30235 var selectCtrl = ctrls[0]; 30236 30237 selectCtrl.ngModelCtrl = ngModelCtrl; 30238 30239 // When the selected item(s) changes we delegate getting the value of the select control 30240 // to the `readValue` method, which can be changed if the select can have multiple 30241 // selected values or if the options are being generated by `ngOptions` 30242 element.on('change', function() { 30243 scope.$apply(function() { 30244 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 30245 }); 30246 }); 30247 30248 // If the select allows multiple values then we need to modify how we read and write 30249 // values from and to the control; also what it means for the value to be empty and 30250 // we have to add an extra watch since ngModel doesn't work well with arrays - it 30251 // doesn't trigger rendering if only an item in the array changes. 30252 if (attr.multiple) { 30253 30254 // Read value now needs to check each option to see if it is selected 30255 selectCtrl.readValue = function readMultipleValue() { 30256 var array = []; 30257 forEach(element.find('option'), function(option) { 30258 if (option.selected) { 30259 array.push(option.value); 30260 } 30261 }); 30262 return array; 30263 }; 30264 30265 // Write value now needs to set the selected property of each matching option 30266 selectCtrl.writeValue = function writeMultipleValue(value) { 30267 var items = new HashMap(value); 30268 forEach(element.find('option'), function(option) { 30269 option.selected = isDefined(items.get(option.value)); 30270 }); 30271 }; 30272 30273 // we have to do it on each watch since ngModel watches reference, but 30274 // we need to work of an array, so we need to see if anything was inserted/removed 30275 var lastView, lastViewRef = NaN; 30276 scope.$watch(function selectMultipleWatch() { 30277 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 30278 lastView = shallowCopy(ngModelCtrl.$viewValue); 30279 ngModelCtrl.$render(); 30280 } 30281 lastViewRef = ngModelCtrl.$viewValue; 30282 }); 30283 30284 // If we are a multiple select then value is now a collection 30285 // so the meaning of $isEmpty changes 30286 ngModelCtrl.$isEmpty = function(value) { 30287 return !value || value.length === 0; 30288 }; 30289 30290 } 30291 } 30292 30293 function selectPostLink(scope, element, attrs, ctrls) { 30294 // if ngModel is not defined, we don't need to do anything 30295 var ngModelCtrl = ctrls[1]; 30296 if (!ngModelCtrl) return; 30297 30298 var selectCtrl = ctrls[0]; 30299 30300 // We delegate rendering to the `writeValue` method, which can be changed 30301 // if the select can have multiple selected values or if the options are being 30302 // generated by `ngOptions`. 30303 // This must be done in the postLink fn to prevent $render to be called before 30304 // all nodes have been linked correctly. 30305 ngModelCtrl.$render = function() { 30306 selectCtrl.writeValue(ngModelCtrl.$viewValue); 30307 }; 30308 } 30309}; 30310 30311 30312// The option directive is purely designed to communicate the existence (or lack of) 30313// of dynamically created (and destroyed) option elements to their containing select
vendor: 2,836 bytes, lines 30314-30382
30314// directive via its controller. 30315var optionDirective = ['$interpolate', function($interpolate) { 30316 return { 30317 restrict: 'E', 30318 priority: 100, 30319 compile: function(element, attr) { 30320 if (isDefined(attr.value)) { 30321 // If the value attribute is defined, check if it contains an interpolation 30322 var interpolateValueFn = $interpolate(attr.value, true); 30323 } else { 30324 // If the value attribute is not defined then we fall back to the 30325 // text content of the option element, which may be interpolated 30326 var interpolateTextFn = $interpolate(element.text(), true); 30327 if (!interpolateTextFn) { 30328 attr.$set('value', element.text()); 30329 } 30330 } 30331 30332 return function(scope, element, attr) { 30333 // This is an optimization over using ^^ since we don't want to have to search 30334 // all the way to the root of the DOM for every single option element 30335 var selectCtrlName = '$selectController', 30336 parent = element.parent(), 30337 selectCtrl = parent.data(selectCtrlName) || 30338 parent.parent().data(selectCtrlName); // in case we are in optgroup 30339 30340 if (selectCtrl) { 30341 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 30342 } 30343 }; 30344 } 30345 }; 30346}]; 30347 30348var styleDirective = valueFn({ 30349 restrict: 'E', 30350 terminal: false 30351}); 30352 30353/** 30354 * @ngdoc directive 30355 * @name ngRequired 30356 * 30357 * @description 30358 * 30359 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 30360 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 30361 * applied to custom controls. 30362 * 30363 * The directive sets the `required` attribute on the element if the Angular expression inside 30364 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 30365 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 30366 * for more info. 30367 * 30368 * The validator will set the `required` error key to true if the `required` attribute is set and 30369 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the 30370 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 30371 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 30372 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 30373 * 30374 * @example 30375 * <example name="ngRequiredDirective" module="ngRequiredExample"> 30376 * <file name="index.html"> 30377 * <script> 30378 * angular.module('ngRequiredExample', []) 30379 * .controller('ExampleController', ['$scope', function($scope) { 30380 * $scope.required = true; 30381 * }]); 30382 * </script>
30383 * <div ng-controller="ExampleController"> 30384 * <form name="form"> 30385 * <label for="required">Toggle required: </label> 30386 * <input type="checkbox" ng-model="required" id="required" /> 30387 * <br> 30388 * <label for="input">This input must be filled if `required` is true: </label> 30389 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 30390 * <hr> 30391 * required error set? = <code>{{form.input.$error.required}}</code><br> 30392 * model = <code>{{model}}</code> 30393 * </form> 30394 * </div> 30395 * </file> 30396 * <file name="protractor.js" type="protractor"> 30397 var required = element(by.binding('form.input.$error.required')); 30398 var model = element(by.binding('model')); 30399 var input = element(by.id('input')); 30400 30401 it('should set the required error', function() { 30402 expect(required.getText()).toContain('true'); 30403 30404 input.sendKeys('123'); 30405 expect(required.getText()).not.toContain('true'); 30406 expect(model.getText()).toContain('123'); 30407 }); 30408 * </file> 30409 * </example> 30410 */ 30411var requiredDirective = function() { 30412 return { 30413 restrict: 'A', 30414 require: '?ngModel', 30415 link: function(scope, elm, attr, ctrl) { 30416 if (!ctrl) return; 30417 attr.required = true; // force truthy in case we are on non input element 30418 30419 ctrl.$validators.required = function(modelValue, viewValue) { 30420 return !attr.required || !ctrl.$isEmpty(viewValue); 30421 }; 30422 30423 attr.$observe('required', function() { 30424 ctrl.$validate(); 30425 }); 30426 } 30427 }; 30428}; 30429 30430/** 30431 * @ngdoc directive 30432 * @name ngPattern 30433 * 30434 * @description 30435 * 30436 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 30437 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 30438 * 30439 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 30440 * does not match a RegExp which is obtained by evaluating the Angular expression given in the 30441 * `ngPattern` attribute value: 30442 * * If the expression evaluates to a RegExp object, then this is used directly. 30443 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 30444 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 30445 * 30446 * <div class="alert alert-info"> 30447 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 30448 * start at the index of the last search's match, thus not taking the whole input value into 30449 * account. 30450 * </div> 30451 * 30452 * <div class="alert alert-info"> 30453 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 30454 * differences: 30455 * <ol> 30456 * <li> 30457 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 30458 * not available. 30459 * </li> 30460 * <li> 30461 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 30462 * interpolated. 30463 * </li> 30464 * </ol> 30465 * </div> 30466 * 30467 * @example 30468 * <example name="ngPatternDirective" module="ngPatternExample"> 30469 * <file name="index.html"> 30470 * <script> 30471 * angular.module('ngPatternExample', []) 30472 * .controller('ExampleController', ['$scope', function($scope) { 30473 * $scope.regex = '\\d+'; 30474 * }]); 30475 * </script>
30476 * <div ng-controller="ExampleController"> 30477 * <form name="form"> 30478 * <label for="regex">Set a pattern (regex string): </label> 30479 * <input type="text" ng-model="regex" id="regex" /> 30480 * <br> 30481 * <label for="input">This input is restricted by the current pattern: </label> 30482 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 30483 * <hr> 30484 * input valid? = <code>{{form.input.$valid}}</code><br> 30485 * model = <code>{{model}}</code> 30486 * </form> 30487 * </div> 30488 * </file> 30489 * <file name="protractor.js" type="protractor"> 30490 var model = element(by.binding('model')); 30491 var input = element(by.id('input')); 30492 30493 it('should validate the input with the default pattern', function() { 30494 input.sendKeys('aaa'); 30495 expect(model.getText()).not.toContain('aaa'); 30496 30497 input.clear().then(function() { 30498 input.sendKeys('123'); 30499 expect(model.getText()).toContain('123'); 30500 }); 30501 }); 30502 * </file> 30503 * </example> 30504 */ 30505var patternDirective = function() { 30506 return { 30507 restrict: 'A', 30508 require: '?ngModel', 30509 link: function(scope, elm, attr, ctrl) { 30510 if (!ctrl) return; 30511 30512 var regexp, patternExp = attr.ngPattern || attr.pattern; 30513 attr.$observe('pattern', function(regex) { 30514 if (isString(regex) && regex.length > 0) { 30515 regex = new RegExp('^' + regex + '$'); 30516 } 30517 30518 if (regex && !regex.test) { 30519 throw minErr('ngPattern')('noregexp', 30520 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 30521 regex, startingTag(elm)); 30522 } 30523 30524 regexp = regex || undefined; 30525 ctrl.$validate(); 30526 }); 30527 30528 ctrl.$validators.pattern = function(modelValue, viewValue) { 30529 // HTML5 pattern constraint validates the input value, so we validate the viewValue 30530 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 30531 }; 30532 } 30533 }; 30534}; 30535 30536/** 30537 * @ngdoc directive 30538 * @name ngMaxlength 30539 * 30540 * @description 30541 * 30542 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 30543 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 30544 * 30545 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 30546 * is longer than the integer obtained by evaluating the Angular expression given in the 30547 * `ngMaxlength` attribute value. 30548 * 30549 * <div class="alert alert-info"> 30550 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 30551 * differences: 30552 * <ol> 30553 * <li> 30554 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 30555 * validation is not available. 30556 * </li> 30557 * <li> 30558 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 30559 * interpolated. 30560 * </li> 30561 * </ol> 30562 * </div> 30563 * 30564 * @example 30565 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 30566 * <file name="index.html"> 30567 * <script> 30568 * angular.module('ngMaxlengthExample', []) 30569 * .controller('ExampleController', ['$scope', function($scope) { 30570 * $scope.maxlength = 5; 30571 * }]); 30572 * </script>
30573 * <div ng-controller="ExampleController"> 30574 * <form name="form"> 30575 * <label for="maxlength">Set a maxlength: </label> 30576 * <input type="number" ng-model="maxlength" id="maxlength" /> 30577 * <br> 30578 * <label for="input">This input is restricted by the current maxlength: </label> 30579 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 30580 * <hr> 30581 * input valid? = <code>{{form.input.$valid}}</code><br> 30582 * model = <code>{{model}}</code> 30583 * </form> 30584 * </div> 30585 * </file> 30586 * <file name="protractor.js" type="protractor"> 30587 var model = element(by.binding('model')); 30588 var input = element(by.id('input')); 30589 30590 it('should validate the input with the default maxlength', function() { 30591 input.sendKeys('abcdef'); 30592 expect(model.getText()).not.toContain('abcdef'); 30593 30594 input.clear().then(function() { 30595 input.sendKeys('abcde'); 30596 expect(model.getText()).toContain('abcde'); 30597 }); 30598 }); 30599 * </file> 30600 * </example> 30601 */ 30602var maxlengthDirective = function() { 30603 return { 30604 restrict: 'A', 30605 require: '?ngModel', 30606 link: function(scope, elm, attr, ctrl) { 30607 if (!ctrl) return; 30608 30609 var maxlength = -1; 30610 attr.$observe('maxlength', function(value) { 30611 var intVal = toInt(value); 30612 maxlength = isNaN(intVal) ? -1 : intVal; 30613 ctrl.$validate(); 30614 }); 30615 ctrl.$validators.maxlength = function(modelValue, viewValue) { 30616 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 30617 }; 30618 } 30619 }; 30620}; 30621 30622/** 30623 * @ngdoc directive 30624 * @name ngMinlength 30625 * 30626 * @description 30627 * 30628 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 30629 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 30630 * 30631 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 30632 * is shorter than the integer obtained by evaluating the Angular expression given in the 30633 * `ngMinlength` attribute value. 30634 * 30635 * <div class="alert alert-info"> 30636 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 30637 * differences: 30638 * <ol> 30639 * <li> 30640 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 30641 * validation is not available. 30642 * </li> 30643 * <li> 30644 * The `ngMinlength` value must be an expression, while the `minlength` value must be 30645 * interpolated. 30646 * </li> 30647 * </ol> 30648 * </div> 30649 * 30650 * @example 30651 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 30652 * <file name="index.html"> 30653 * <script> 30654 * angular.module('ngMinlengthExample', []) 30655 * .controller('ExampleController', ['$scope', function($scope) { 30656 * $scope.minlength = 3; 30657 * }]); 30658 * </script>
30659 * <div ng-controller="ExampleController"> 30660 * <form name="form"> 30661 * <label for="minlength">Set a minlength: </label> 30662 * <input type="number" ng-model="minlength" id="minlength" /> 30663 * <br> 30664 * <label for="input">This input is restricted by the current minlength: </label> 30665 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 30666 * <hr> 30667 * input valid? = <code>{{form.input.$valid}}</code><br> 30668 * model = <code>{{model}}</code> 30669 * </form> 30670 * </div> 30671 * </file> 30672 * <file name="protractor.js" type="protractor"> 30673 var model = element(by.binding('model')); 30674 var input = element(by.id('input')); 30675 30676 it('should validate the input with the default minlength', function() { 30677 input.sendKeys('ab'); 30678 expect(model.getText()).not.toContain('ab'); 30679 30680 input.sendKeys('abc'); 30681 expect(model.getText()).toContain('abc'); 30682 }); 30683 * </file> 30684 * </example> 30685 */ 30686var minlengthDirective = function() { 30687 return { 30688 restrict: 'A', 30689 require: '?ngModel', 30690 link: function(scope, elm, attr, ctrl) { 30691 if (!ctrl) return; 30692 30693 var minlength = 0; 30694 attr.$observe('minlength', function(value) { 30695 minlength = toInt(value) || 0; 30696 ctrl.$validate(); 30697 }); 30698 ctrl.$validators.minlength = function(modelValue, viewValue) { 30699 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 30700 }; 30701 } 30702 }; 30703}; 30704 30705if (window.angular.bootstrap) { 30706 //AngularJS is already loaded, so we can return here... 30707 if (window.console) { 30708 console.log('WARNING: Tried to load angular more than once.'); 30709 } 30710 return; 30711} 30712 30713//try to bind to jquery now so that one can write jqLite(document).ready() 30714//but we will rebind on bootstrap again. 30715bindJQuery(); 30716 30717publishExternalAPI(angular); 30718 30719angular.module("ngLocale", [], ["$provide", function($provide) { 30720var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 30721function getDecimals(n) { 30722 n = n + ''; 30723 var i = n.indexOf('.'); 30724 return (i == -1) ? 0 : n.length - i - 1; 30725} 30726 30727function getVF(n, opt_precision) { 30728 var v = opt_precision; 30729 30730 if (undefined === v) { 30731 v = Math.min(getDecimals(n), 3); 30732 } 30733 30734 var base = Math.pow(10, v); 30735 var f = ((n * base) | 0) % base; 30736 return {v: v, f: f}; 30737} 30738 30739$provide.value("$locale", { 30740 "DATETIME_FORMATS": { 30741 "AMPMS": [ 30742 "AM", 30743 "PM" 30744 ], 30745 "DAY": [ 30746 "Sunday", 30747 "Monday", 30748 "Tuesday", 30749 "Wednesday", 30750 "Thursday", 30751 "Friday", 30752 "Saturday" 30753 ], 30754 "ERANAMES": [ 30755 "Before Christ", 30756 "Anno Domini" 30757 ], 30758 "ERAS": [ 30759 "BC", 30760 "AD" 30761 ], 30762 "FIRSTDAYOFWEEK": 6, 30763 "MONTH": [ 30764 "January", 30765 "February", 30766 "March", 30767 "April", 30768 "May", 30769 "June", 30770 "July", 30771 "August", 30772 "September", 30773 "October", 30774 "November", 30775 "December" 30776 ], 30777 "SHORTDAY": [ 30778 "Sun", 30779 "Mon", 30780 "Tue", 30781 "Wed", 30782 "Thu", 30783 "Fri", 30784 "Sat" 30785 ], 30786 "SHORTMONTH": [ 30787 "Jan", 30788 "Feb", 30789 "Mar", 30790 "Apr", 30791 "May", 30792 "Jun", 30793 "Jul", 30794 "Aug", 30795 "Sep", 30796 "Oct", 30797 "Nov", 30798 "Dec" 30799 ], 30800 "STANDALONEMONTH": [ 30801 "January", 30802 "February", 30803 "March", 30804 "April", 30805 "May", 30806 "June", 30807 "July", 30808 "August", 30809 "September", 30810 "October", 30811 "November", 30812 "December" 30813 ], 30814 "WEEKENDRANGE": [ 30815 5, 30816 6 30817 ], 30818 "fullDate": "EEEE, MMMM d, y", 30819 "longDate": "MMMM d, y", 30820 "medium": "MMM d, y h:mm:ss a", 30821 "mediumDate": "MMM d, y", 30822 "mediumTime": "h:mm:ss a", 30823 "short": "M/d/yy h:mm a", 30824 "shortDate": "M/d/yy", 30825 "shortTime": "h:mm a" 30826 }, 30827 "NUMBER_FORMATS": { 30828 "CURRENCY_SYM": "$", 30829 "DECIMAL_SEP": ".", 30830 "GROUP_SEP": ",", 30831 "PATTERNS": [ 30832 { 30833 "gSize": 3, 30834 "lgSize": 3, 30835 "maxFrac": 3, 30836 "minFrac": 0, 30837 "minInt": 1, 30838 "negPre": "-", 30839 "negSuf": "", 30840 "posPre": "", 30841 "posSuf": "" 30842 }, 30843 { 30844 "gSize": 3, 30845 "lgSize": 3, 30846 "maxFrac": 2, 30847 "minFrac": 2, 30848 "minInt": 1, 30849 "negPre": "-\u00a4", 30850 "negSuf": "", 30851 "posPre": "\u00a4", 30852 "posSuf": "" 30853 } 30854 ] 30855 }, 30856 "id": "en-us", 30857 "localeID": "en_US", 30858 "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;} 30859}); 30860}]); 30861 30862 jqLite(window.document).ready(function() { 30863 angularInit(window.document, bootstrap); 30864 }); 30865 30866})(window); 30867 30868!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
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.