1/** 2 * @license AngularJS v1.5.0-rc.1 3 * (c) 2010-2016 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function(window, document, undefined) {'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.0-rc.1/' + 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 * @description 175 * 176 * # ng (core module) 177 * The ng module is loaded by default when an AngularJS application is started. The module itself 178 * contains the essential components for an AngularJS application to function. The table below 179 * lists a high level breakdown of each of the services/factories, filters, directives and testing 180 * components available within this core module. 181 * 182 * <div doc-module-components="ng"></div> 183 */ 184 185var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 186 187// The name of a form control's ValidityState property. 188// This is used so that it's possible for internal tests to create mock ValidityStates. 189var VALIDITY_STATE_PROPERTY = 'validity'; 190 191/** 192 * @ngdoc function 193 * @name angular.lowercase 194 * @module ng 195 * @kind function 196 * 197 * @description Converts the specified string to lowercase. 198 * @param {string} string String to be converted to lowercase. 199 * @returns {string} Lowercased string. 200 */ 201var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 202var hasOwnProperty = Object.prototype.hasOwnProperty; 203 204/** 205 * @ngdoc function 206 * @name angular.uppercase 207 * @module ng 208 * @kind function 209 * 210 * @description Converts the specified string to uppercase. 211 * @param {string} string String to be converted to uppercase. 212 * @returns {string} Uppercased string. 213 */ 214var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 215 216 217var manualLowercase = function(s) { 218 /* jshint bitwise: false */ 219 return isString(s) 220 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCo
220deAt(0) | 32);}) 221 : s; 222}; 223var manualUppercase = function(s) { 224 /* jshint bitwise: false */ 225 return isString(s) 226 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 227 : s; 228}; 229 230 231// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 232// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 233// with correct but slower alternatives. 234if ('i' !== 'I'.toLowerCase()) { 235 lowercase = manualLowercase; 236 uppercase = manualUppercase; 237} 238 239 240var 241 msie, // holds major version number for IE, or NaN if UA is not IE. 242 jqLite, // delay binding since jQuery could be loaded after us. 243 jQuery, // delay binding 244 slice = [].slice, 245 splice = [].splice, 246 push = [].push, 247 toString = Object.prototype.toString, 248 getPrototypeOf = Object.getPrototypeOf, 249 ngMinErr = minErr('ng'), 250 251 /** @name angular */ 252 angular = window.angular || (window.angular = {}), 253 angularModule, 254 uid = 0; 255 256/** 257 * documentMode is an IE-only property 258 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 259 */ 260msie = document.documentMode; 261 262 263/** 264 * @private 265 * @param {*} obj 266 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 267 * String ...) 268 */ 269function isArrayLike(obj) { 270 271 // `null`, `undefined` and `window` are not array-like 272 if (obj == null || isWindow(obj)) return false; 273 274 // arrays, strings and jQuery/jqLite objects are array like 275 // * jqLite is either the jQuery or jqLite constructor function 276 // * we have to check the existence of jqLite first as this method is called 277 // via the forEach method when constructing the jqLite object in the first place 278 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 279
vendor: 4,301 bytes, lines 280-396
280 // Support: iOS 8.2 (not reproducible in simulator) 281 // "length" in obj used to prevent JIT error (gh-11508) 282 var length = "length" in Object(obj) && obj.length; 283 284 // NodeList objects (with `item` method) and 285 // other objects with suitable length characteristics are array-like 286 return isNumber(length) && 287 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function'); 288 289} 290 291/** 292 * @ngdoc function 293 * @name angular.forEach 294 * @module ng 295 * @kind function 296 * 297 * @description 298 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 299 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 300 * is the value of an object property or an array element, `key` is the object property key or 301 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 302 * 303 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 304 * using the `hasOwnProperty` method. 305 * 306 * Unlike ES262's 307 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 308 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 309 * return the value provided. 310 * 311 ```js 312 var values = {name: 'misko', gender: 'male'}; 313 var log = []; 314 angular.forEach(values, function(value, key) { 315 this.push(key + ': ' + value); 316 }, log); 317 expect(log).toEqual(['name: misko', 'gender: male']); 318 ``` 319 * 320 * @param {Object|Array} obj Object to iterate over. 321 * @param {Function} iterator Iterator function. 322 * @param {Object=} context Object to become context (`this`) for the iterator function. 323 * @returns {Object|Array} Reference to `obj`. 324 */ 325 326function forEach(obj, iterator, context) { 327 var key, length; 328 if (obj) { 329 if (isFunction(obj)) { 330 for (key in obj) { 331 // Need to check if hasOwnProperty exists, 332 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 333 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 334 iterator.call(context, obj[key], key, obj); 335 } 336 } 337 } else if (isArray(obj) || isArrayLike(obj)) { 338 var isPrimitive = typeof obj !== 'object'; 339 for (key = 0, length = obj.length; key < length; key++) { 340 if (isPrimitive || key in obj) { 341 iterator.call(context, obj[key], key, obj); 342 } 343 } 344 } else if (obj.forEach && obj.forEach !== forEach) { 345 obj.forEach(iterator, context, obj); 346 } else if (isBlankObject(obj)) { 347 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 348 for (key in obj) { 349 iterator.call(context, obj[key], key, obj); 350 } 351 } else if (typeof obj.hasOwnProperty === 'function') { 352 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 353 for (key in obj) { 354 if (obj.hasOwnProperty(key)) { 355 iterator.call(context, obj[key], key, obj); 356 } 357 } 358 } else { 359 // Slow path for objects which do not have a method `hasOwnProperty` 360 for (key in obj) { 361 if (hasOwnProperty.call(obj, key)) { 362 iterator.call(context, obj[key], key, obj); 363 } 364 } 365 } 366 } 367 return obj; 368} 369 370function forEachSorted(obj, iterator, context) { 371 var keys = Object.keys(obj).sort(); 372 for (var i = 0; i < keys.length; i++) { 373 iterator.call(context, obj[keys[i]], keys[i]); 374 } 375 return keys; 376} 377 378 379/** 380 * when using forEach the params are value, key, but it is often useful to have key, value. 381 * @param {function(string, *)} iteratorFn 382 * @returns {function(*, string)} 383 */ 384function reverseParams(iteratorFn) { 385 return function(value, key) { iteratorFn(key, value); }; 386} 387 388/** 389 * A consistent way of creating unique IDs in angular. 390 * 391 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 392 * we hit number precision issues in JavaScript. 393 * 394 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 395 * 396 * @returns {number}
vendor: 9,078 bytes, lines 396-771
396 an unique alpha-numeric string 397 */ 398function nextUid() { 399 return ++uid; 400} 401 402 403/** 404 * Set or clear the hashkey for an object. 405 * @param obj object 406 * @param h the hashkey (!truthy to delete the hashkey) 407 */ 408function setHashKey(obj, h) { 409 if (h) { 410 obj.$$hashKey = h; 411 } else { 412 delete obj.$$hashKey; 413 } 414} 415 416 417function baseExtend(dst, objs, deep) { 418 var h = dst.$$hashKey; 419 420 for (var i = 0, ii = objs.length; i < ii; ++i) { 421 var obj = objs[i]; 422 if (!isObject(obj) && !isFunction(obj)) continue; 423 var keys = Object.keys(obj); 424 for (var j = 0, jj = keys.length; j < jj; j++) { 425 var key = keys[j]; 426 var src = obj[key]; 427 428 if (deep && isObject(src)) { 429 if (isDate(src)) { 430 dst[key] = new Date(src.valueOf()); 431 } else if (isRegExp(src)) { 432 dst[key] = new RegExp(src); 433 } else if (src.nodeName) { 434 dst[key] = src.cloneNode(true); 435 } else if (isElement(src)) { 436 dst[key] = src.clone(); 437 } else { 438 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 439 baseExtend(dst[key], [src], true); 440 } 441 } else { 442 dst[key] = src; 443 } 444 } 445 } 446 447 setHashKey(dst, h); 448 return dst; 449} 450 451/** 452 * @ngdoc function 453 * @name angular.extend 454 * @module ng 455 * @kind function 456 * 457 * @description 458 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 459 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 460 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 461 * 462 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 463 * {@link angular.merge} for this. 464 * 465 * @param {Object} dst Destination object. 466 * @param {...Object} src Source object(s). 467 * @returns {Object} Reference to `dst`. 468 */ 469function extend(dst) { 470 return baseExtend(dst, slice.call(arguments, 1), false); 471} 472 473 474/** 475* @ngdoc function 476* @name angular.merge 477* @module ng 478* @kind function 479* 480* @description 481* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 482* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 483* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 484* 485* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 486* objects, performing a deep copy. 487* 488* @param {Object} dst Destination object. 489* @param {...Object} src Source object(s). 490* @returns {Object} Reference to `dst`. 491*/ 492function merge(dst) { 493 return baseExtend(dst, slice.call(arguments, 1), true); 494} 495 496 497 498function toInt(str) { 499 return parseInt(str, 10); 500} 501 502 503function inherit(parent, extra) { 504 return extend(Object.create(parent), extra); 505} 506 507/** 508 * @ngdoc function 509 * @name angular.noop 510 * @module ng 511 * @kind function 512 * 513 * @description 514 * A function that performs no operations. This function can be useful when writing code in the 515 * functional style. 516 ```js 517 function foo(callback) { 518 var result = calculateResult(); 519 (callback || angular.noop)(result); 520 } 521 ``` 522 */ 523function noop() {} 524noop.$inject = []; 525 526 527/** 528 * @ngdoc function 529 * @name angular.identity 530 * @module ng 531 * @kind function 532 * 533 * @description 534 * A function that returns its first argument. This function is useful when writing code in the 535 * functional style. 536 * 537 ```js 538 function transformer(transformationFn, value) { 539 return (transformationFn || angular.identity)(value); 540 }; 541 ``` 542 * @param {*} value to be returned. 543 * @returns {*} the value passed in. 544 */ 545function identity($) {return $;} 546identity.$inject = []; 547 548 549function valueFn(value) {return function() {return value;};} 550 551function hasCustomToString(obj) { 552 return isFunction(obj.toString) && obj.toString !== toString; 553} 554 555 556/** 557 * @ngdoc function 558 * @name angular.isUndefined 559 * @module ng 560 * @kind function 561 * 562 * @description 563 * Determines if a reference is undefined. 564 * 565 * @param {*} value Reference to check. 566 * @returns {boolean} True if `value` is undefined. 567 */ 568function isUndefined(value) {return typeof value === 'undefined';} 569 570 571/** 572 * @ngdoc function 573 * @name angular.isDefined 574 * @module ng 575 * @kind function 576 * 577 * @description 578 * Determines if a reference is defined. 579 * 580 * @param {*} value Reference to check. 581 * @returns {boolean} True if `value` is defined. 582 */ 583function isDefined(value) {return typeof value !== 'undefined';} 584 585 586/** 587 * @ngdoc function 588 * @name angular.isObject 589 * @module ng 590 * @kind function 591 * 592 * @description 593 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 594 * considered to be objects. Note that JavaScript arrays are objects. 595 * 596 * @param {*} value Reference to check. 597 * @returns {boolean} True if `value` is an `Object` but not `null`. 598 */ 599function isObject(value) { 600 // http://jsperf.com/isobject4 601 return value !== null && typeof value === 'object'; 602} 603 604 605/** 606 * Determine if a value is an object with a null prototype 607 * 608 * @returns {boolean} True if `value` is an `Object` with a null prototype 609 */ 610function isBlankObject(value) { 611 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 612} 613 614 615/** 616 * @ngdoc function 617 * @name angular.isString 618 * @module ng 619 * @kind function 620 * 621 * @description 622 * Determines if a reference is a `String`. 623 * 624 * @param {*} value Reference to check. 625 * @returns {boolean} True if `value` is a `String`. 626 */ 627function isString(value) {return typeof value === 'string';} 628 629 630/** 631 * @ngdoc function 632 * @name angular.isNumber 633 * @module ng 634 * @kind function 635 * 636 * @description 637 * Determines if a reference is a `Number`. 638 * 639 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 640 * 641 * If you wish to exclude these then you can use the native 642 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 643 * method. 644 * 645 * @param {*} value Reference to check. 646 * @returns {boolean} True if `value` is a `Number`. 647 */ 648function isNumber(value) {return typeof value === 'number';} 649 650 651/** 652 * @ngdoc function 653 * @name angular.isDate 654 * @module ng 655 * @kind function 656 * 657 * @description 658 * Determines if a value is a date. 659 * 660 * @param {*} value Reference to check. 661 * @returns {boolean} True if `value` is a `Date`. 662 */ 663function isDate(value) { 664 return toString.call(value) === '[object Date]'; 665} 666 667 668/** 669 * @ngdoc function 670 * @name angular.isArray 671 * @module ng 672 * @kind function 673 * 674 * @description 675 * Determines if a reference is an `Array`. 676 * 677 * @param {*} value Reference to check. 678 * @returns {boolean} True if `value` is an `Array`. 679 */ 680var isArray = Array.isArray; 681 682/** 683 * @ngdoc function 684 * @name angular.isFunction 685 * @module ng 686 * @kind function 687 * 688 * @description 689 * Determines if a reference is a `Function`. 690 * 691 * @param {*} value Reference to check. 692 * @returns {boolean} True if `value` is a `Function`. 693 */ 694function isFunction(value) {return typeof value === 'function';} 695 696 697/** 698 * Determines if a value is a regular expression object. 699 * 700 * @private 701 * @param {*} value Reference to check. 702 * @returns {boolean} True if `value` is a `RegExp`. 703 */ 704function isRegExp(value) { 705 return toString.call(value) === '[object RegExp]'; 706} 707 708 709/** 710 * Checks if `obj` is a window object. 711 * 712 * @private 713 * @param {*} obj Object to check 714 * @returns {boolean} True if `obj` is a window obj. 715 */ 716function isWindow(obj) { 717 return obj && obj.window === obj; 718} 719 720 721function isScope(obj) { 722 return obj && obj.$evalAsync && obj.$watch; 723} 724 725 726function isFile(obj) { 727 return toString.call(obj) === '[object File]'; 728} 729 730 731function isFormData(obj) { 732 return toString.call(obj) === '[object FormData]'; 733} 734 735 736function isBlob(obj) { 737 return toString.call(obj) === '[object Blob]'; 738} 739 740 741function isBoolean(value) { 742 return typeof value === 'boolean'; 743} 744 745 746function isPromiseLike(obj) { 747 return obj && isFunction(obj.then); 748} 749 750 751var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/; 752function isTypedArray(value) { 753 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 754} 755 756function isArrayBuffer(obj) { 757 return toString.call(obj) === '[object ArrayBuffer]'; 758} 759 760 761var trim = function(value) { 762 return isString(value) ? value.trim() : value; 763}; 764 765// Copied from: 766// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 767// Prereq: s is a string. 768var escapeForRegexp = function(s) { 769 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 770 replace(/\x08/g, '\\x08'); 771};
772 773 774/** 775 * @ngdoc function 776 * @name angular.isElement 777 * @module ng 778 * @kind function 779 * 780 * @description 781 * Determines if a reference is a DOM element (or wrapped jQuery element). 782 * 783 * @param {*} value Reference to check. 784 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 785 */ 786function isElement(node) { 787 return !!(node && 788 (node.nodeName // we are a direct element 789 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 790} 791 792/** 793 * @param str 'key1,key2,...' 794 * @returns {object} in the form of {key1:true, key2:true, ...} 795 */ 796function makeMap(str) { 797 var obj = {}, items = str.split(","), i; 798 for (i = 0; i < items.length; i++) { 799 obj[items[i]] = true; 800 } 801 return obj; 802} 803 804 805function nodeName_(element) { 806 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 807} 808 809function includes(array, obj) { 810 return Array.prototype.indexOf.call(array, obj) != -1; 811} 812 813function arrayRemove(array, value) { 814 var index = array.indexOf(value); 815 if (index >= 0) { 816 array.splice(index, 1); 817 } 818 return index; 819} 820 821/** 822 * @ngdoc function 823 * @name angular.copy 824 * @module ng 825 * @kind function 826 * 827 * @description 828 * Creates a deep copy of `source`, which should be an object or an array. 829 * 830 * * If no destination is supplied, a copy of the object or array is created. 831 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 832 * are deleted and then all elements/properties from the source are copied to it. 833 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 834 * * If `source` is identical to 'destination' an exception will be thrown. 835 * 836 * @param {*} source The source that will be used to make a copy. 837 * Can be any type, including primitives, `null`, and `undefined`. 838 * @param {(Object|Array)=} destination Destination into which the source is copied. If 839 * provided, must be of the same type as `source`. 840 * @returns {*} The copy or updated `destination`, if `destination` was specified. 841 * 842 * @example 843 <example module="copyExample"> 844 <file name="index.html">
845 <div ng-controller="ExampleController"> 846 <form novalidate class="simple-form"> 847 Name: <input type="text" ng-model="user.name" /><br /> 848 E-mail: <input type="email" ng-model="user.email" /><br /> 849 Gender: <input type="radio" ng-model="user.gender" value="male" />male 850 <input type="radio" ng-model="user.gender" value="female" />female<br /> 851 <button ng-click="reset()">RESET</button> 852 <button ng-click="update(user)">SAVE</button> 853 </form> 854 <pre>form = {{user | json}}</pre> 855 <pre>master = {{master | json}}</pre> 856 </div> 857 858 <script> 859 angular.module('copyExample', []) 860 .controller('ExampleController', ['$scope', function($scope) { 861 $scope.master= {}; 862 863 $scope.update = function(user) { 864 // Example with 1 argument 865 $scope.master= angular.copy(user); 866 }; 867 868 $scope.reset = function() { 869 // Example with 2 arguments 870 angular.copy($scope.master, $scope.user); 871 }; 872 873 $scope.reset(); 874 }]); 875 </script> 876 </file> 877 </example> 878 */ 879function copy(source, destination) { 880 var stackSource = []; 881 var stackDest = []; 882 883 if (destination) { 884 if (isTypedArray(destination) || isArrayBuffer(destination)) { 885 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated."); 886 } 887 if (source === destination) { 888 throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); 889 } 890 891 // Empty the destination object 892 if (isArray(destination)) { 893 destination.length = 0; 894 } else { 895 forEach(destination, function(value, key) { 896 if (key !== '$$hashKey') { 897 delete destination[key]; 898 } 899 }); 900 } 901 902 stackSource.push(source); 903 stackDest.push(destination); 904 return copyRecurse(source, destination); 905 } 906 907 return copyElement(source); 908 909 function copyRecurse(source, destination) { 910 var h = destination.$$hashKey; 911 var result, key; 912 if (isArray(source)) { 913 for (var i = 0, ii = source.length; i < ii; i++) { 914 destination.push(copyElement(source[i])); 915 } 916 } else if (isBlankObject(source)) { 917 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 918 for (key in source) { 919 destination[key] = copyElement(source[key]); 920 } 921 } else if (source && typeof source.hasOwnProperty === 'function') { 922 // Slow path, which must rely on hasOwnProperty 923 for (key in source) { 924 if (source.hasOwnProperty(key)) { 925 destination[key] = copyElement(source[key]); 926 } 927 } 928 } else { 929 // Slowest path --- hasOwnProperty can't be called as a method 930 for (key in source) { 931 if (hasOwnProperty.call(source, key)) { 932 destination[key] = copyElement(source[key]); 933 } 934 } 935 } 936 setHashKey(destination, h); 937 return destination; 938 } 939 940 function copyElement(source) { 941 // Simple values 942 if (!isObject(source)) { 943 return source; 944 } 945 946 // Already copied values 947 var index = stackSource.indexOf(source); 948 if (index !== -1) { 949 return stackDest[index]; 950 } 951 952 if (isWindow(source) || isScope(source)) { 953 throw ngMinErr('cpws', 954 "Can't copy! Making copies of Window or Scope instances is not supported."); 955 } 956 957 var needsRecurse = false; 958 var destination = copyType(source); 959 960 if (destination === undefined) { 961 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 962 needsRecurse = true; 963 } 964 965 stackSource.push(source); 966 stackDest.push(destination); 967 968 return needsRecurse 969 ? copyRecurse(source, destination) 970 : destination; 971 } 972 973 function copyType(source) { 974 switch (toString.call(source)) { 975 case '[object Int8Array]': 976 case '[object Int16Array]': 977 case '[object Int32Array]': 978 case '[object Float32Array]': 979 case '[object Float64Array]': 980 case '[object Uint8Array]': 981 case '[object Uint8ClampedArray]': 982 case '[object Uint16Array]': 983 case '[object Uint32Array]': 984 return new source.constructor(copyElement(source.buffer)); 985 986 case '[object ArrayBuffer]': 987 //Support: IE10 988 if (!source.slice) { 989 var copied = new ArrayBuffer(source.byteLength); 990 new Uint8Array(copied).set(new Uint8Array(source)); 991 return copied; 992 } 993 return source.slice(0); 994 995 case '[object Boolean]': 996 case '[object Number]': 997 case '[object String]': 998 case '[object Date]': 999 return new source.constructor(source.valueOf()); 1000 1001 case '[object RegExp]': 1002 var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 1003 re.lastIndex = source.lastIndex; 1004 return re; 1005 } 1006 1007 if (isFunction(source.cloneNode)) { 1008 return source.cloneNode(true); 1009 } 1010 } 1011} 1012 1013/** 1014 * Creates a shallow copy of an object, an array or a primitive. 1015 * 1016 * Assumes that there are no proto properties for objects. 1017 */ 1018function shallowCopy(src, dst) { 1019 if (isArray(src)) { 1020 dst = dst || []; 1021 1022 for (var i = 0, ii = src.length; i < ii; i++) { 1023 dst[i] = src[i];
vendor: 14,750 bytes, lines 1024-1491
1024 } 1025 } else if (isObject(src)) { 1026 dst = dst || {}; 1027 1028 for (var key in src) { 1029 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 1030 dst[key] = src[key]; 1031 } 1032 } 1033 } 1034 1035 return dst || src; 1036} 1037 1038 1039/** 1040 * @ngdoc function 1041 * @name angular.equals 1042 * @module ng 1043 * @kind function 1044 * 1045 * @description 1046 * Determines if two objects or two values are equivalent. Supports value types, regular 1047 * expressions, arrays and objects. 1048 * 1049 * Two objects or values are considered equivalent if at least one of the following is true: 1050 * 1051 * * Both objects or values pass `===` comparison. 1052 * * Both objects or values are of the same type and all of their properties are equal by 1053 * comparing them with `angular.equals`. 1054 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1055 * * Both values represent the same regular expression (In JavaScript, 1056 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1057 * representation matches). 1058 * 1059 * During a property comparison, properties of `function` type and properties with names 1060 * that begin with `$` are ignored. 1061 * 1062 * Scope and DOMWindow objects are being compared only by identify (`===`). 1063 * 1064 * @param {*} o1 Object or value to compare. 1065 * @param {*} o2 Object or value to compare. 1066 * @returns {boolean} True if arguments are equal. 1067 */ 1068function equals(o1, o2) { 1069 if (o1 === o2) return true; 1070 if (o1 === null || o2 === null) return false; 1071 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1072 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1073 if (t1 == t2) { 1074 if (t1 == 'object') { 1075 if (isArray(o1)) { 1076 if (!isArray(o2)) return false; 1077 if ((length = o1.length) == o2.length) { 1078 for (key = 0; key < length; key++) { 1079 if (!equals(o1[key], o2[key])) return false; 1080 } 1081 return true; 1082 } 1083 } else if (isDate(o1)) { 1084 if (!isDate(o2)) return false; 1085 return equals(o1.getTime(), o2.getTime()); 1086 } else if (isRegExp(o1)) { 1087 return isRegExp(o2) ? o1.toString() == o2.toString() : false; 1088 } else { 1089 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1090 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1091 keySet = createMap(); 1092 for (key in o1) { 1093 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1094 if (!equals(o1[key], o2[key])) return false; 1095 keySet[key] = true; 1096 } 1097 for (key in o2) { 1098 if (!(key in keySet) && 1099 key.charAt(0) !== '$' && 1100 isDefined(o2[key]) && 1101 !isFunction(o2[key])) return false; 1102 } 1103 return true; 1104 } 1105 } 1106 } 1107 return false; 1108} 1109 1110var csp = function() { 1111 if (!isDefined(csp.rules)) { 1112 1113 1114 var ngCspElement = (document.querySelector('[ng-csp]') || 1115 document.querySelector('[data-ng-csp]')); 1116 1117 if (ngCspElement) { 1118 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1119 ngCspElement.getAttribute('data-ng-csp'); 1120 csp.rules = { 1121 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1122 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1123 }; 1124 } else { 1125 csp.rules = { 1126 noUnsafeEval: noUnsafeEval(), 1127 noInlineStyle: false 1128 }; 1129 } 1130 } 1131 1132 return csp.rules; 1133 1134 function noUnsafeEval() { 1135 try { 1136 /* jshint -W031, -W054 */ 1137 new Function(''); 1138 /* jshint +W031, +W054 */ 1139 return false; 1140 } catch (e) { 1141 return true; 1142 } 1143 } 1144}; 1145 1146/** 1147 * @ngdoc directive 1148 * @module ng 1149 * @name ngJq 1150 * 1151 * @element ANY 1152 * @param {string=} ngJq the name of the library available under `window` 1153 * to be used for angular.element 1154 * @description 1155 * Use this directive to force the angular.element library. This should be 1156 * used to force either jqLite by leaving ng-jq blank or setting the name of 1157 * the jquery variable under window (eg. jQuery). 1158 * 1159 * Since angular looks for this directive when it is loaded (doesn't wait for the 1160 * DOMContentLoaded event), it must be placed on an element that comes before the script 1161 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1162 * others ignored. 1163 * 1164 * @example 1165 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1166 ```html 1167 <!doctype html> 1168 <html ng-app ng-jq> 1169 ... 1170 ... 1171 </html> 1172 ``` 1173 * @example 1174 * This example shows how to use a jQuery based library of a different name. 1175 * The library name must be available at the top most 'window'. 1176 ```html 1177 <!doctype html> 1178 <html ng-app ng-jq="jQueryLib"> 1179 ... 1180 ... 1181 </html> 1182 ``` 1183 */ 1184var jq = function() { 1185 if (isDefined(jq.name_)) return jq.name_; 1186 var el; 1187 var i, ii = ngAttrPrefixes.length, prefix, name; 1188 for (i = 0; i < ii; ++i) { 1189 prefix = ngAttrPrefixes[i]; 1190 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1191 name = el.getAttribute(prefix + 'jq'); 1192 break; 1193 } 1194 } 1195 1196 return (jq.name_ = name); 1197}; 1198 1199function concat(array1, array2, index) { 1200 return array1.concat(slice.call(array2, index)); 1201} 1202 1203function sliceArgs(args, startIndex) { 1204 return slice.call(args, startIndex || 0); 1205} 1206 1207 1208/* jshint -W101 */ 1209/** 1210 * @ngdoc function 1211 * @name angular.bind 1212 * @module ng 1213 * @kind function 1214 * 1215 * @description 1216 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1217 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1218 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1219 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1220 * 1221 * @param {Object} self Context which `fn` should be evaluated in. 1222 * @param {function()} fn Function to be bound. 1223 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1224 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1225 */ 1226/* jshint +W101 */ 1227function bind(self, fn) { 1228 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1229 if (isFunction(fn) && !(fn instanceof RegExp)) { 1230 return curryArgs.length 1231 ? function() { 1232 return arguments.length 1233 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1234 : fn.apply(self, curryArgs); 1235 } 1236 : function() { 1237 return arguments.length 1238 ? fn.apply(self, arguments) 1239 : fn.call(self); 1240 }; 1241 } else { 1242 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1243 return fn; 1244 } 1245} 1246 1247 1248function toJsonReplacer(key, value) { 1249 var val = value; 1250 1251 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1252 val = undefined; 1253 } else if (isWindow(value)) { 1254 val = '$WINDOW'; 1255 } else if (value && document === value) { 1256 val = '$DOCUMENT'; 1257 } else if (isScope(value)) { 1258 val = '$SCOPE'; 1259 } 1260 1261 return val; 1262} 1263 1264 1265/** 1266 * @ngdoc function 1267 * @name angular.toJson 1268 * @module ng 1269 * @kind function 1270 * 1271 * @description 1272 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1273 * stripped since angular uses this notation internally. 1274 * 1275 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1276 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1277 * If set to an integer, the JSON output will contain that many spaces per indentation. 1278 * @returns {string|undefined} JSON-ified string representing `obj`. 1279 */ 1280function toJson(obj, pretty) { 1281 if (typeof obj === 'undefined') return undefined; 1282 if (!isNumber(pretty)) { 1283 pretty = pretty ? 2 : null; 1284 } 1285 return JSON.stringify(obj, toJsonReplacer, pretty); 1286} 1287 1288 1289/** 1290 * @ngdoc function 1291 * @name angular.fromJson 1292 * @module ng 1293 * @kind function 1294 * 1295 * @description 1296 * Deserializes a JSON string. 1297 * 1298 * @param {string} json JSON string to deserialize. 1299 * @returns {Object|Array|string|number} Deserialized JSON string. 1300 */ 1301function fromJson(json) { 1302 return isString(json) 1303 ? JSON.parse(json) 1304 : json; 1305} 1306 1307 1308function timezoneToOffset(timezone, fallback) { 1309 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1310 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1311} 1312 1313 1314function addDateMinutes(date, minutes) { 1315 date = new Date(date.getTime()); 1316 date.setMinutes(date.getMinutes() + minutes); 1317 return date; 1318} 1319 1320 1321function convertTimezoneToLocal(date, timezone, reverse) { 1322 reverse = reverse ? -1 : 1; 1323 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 1324 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); 1325} 1326 1327 1328/** 1329 * @returns {string} Returns the string representation of the element. 1330 */ 1331function startingTag(element) { 1332 element = jqLite(element).clone(); 1333 try { 1334 // turns out IE does not let you set .html() on elements which 1335 // are not allowed to have children. So we just ignore it. 1336 element.empty(); 1337 } catch (e) {} 1338 var elemHtml = jqLite('<div>').append(element).html(); 1339 try { 1340 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1341 elemHtml. 1342 match(/^(<[^>]+>)/)[1]. 1343 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1344 } catch (e) { 1345 return lowercase(elemHtml); 1346 } 1347 1348} 1349 1350 1351///////////////////////////////////////////////// 1352 1353/** 1354 * Tries to decode the URI component without throwing an exception. 1355 * 1356 * @private 1357 * @param str value potential URI component to check. 1358 * @returns {boolean} True if `value` can be decoded 1359 * with the decodeURIComponent function. 1360 */ 1361function tryDecodeURIComponent(value) { 1362 try { 1363 return decodeURIComponent(value); 1364 } catch (e) { 1365 // Ignore any invalid uri component 1366 } 1367} 1368 1369 1370/** 1371 * Parses an escaped url query string into key-value pairs. 1372 * @returns {Object.<string,boolean|Array>} 1373 */ 1374function parseKeyValue(/**string*/keyValue) { 1375 var obj = {}; 1376 forEach((keyValue || "").split('&'), function(keyValue) { 1377 var splitPoint, key, val; 1378 if (keyValue) { 1379 key = keyValue = keyValue.replace(/\+/g,'%20'); 1380 splitPoint = keyValue.indexOf('='); 1381 if (splitPoint !== -1) { 1382 key = keyValue.substring(0, splitPoint); 1383 val = keyValue.substring(splitPoint + 1); 1384 } 1385 key = tryDecodeURIComponent(key); 1386 if (isDefined(key)) { 1387 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1388 if (!hasOwnProperty.call(obj, key)) { 1389 obj[key] = val; 1390 } else if (isArray(obj[key])) { 1391 obj[key].push(val); 1392 } else { 1393 obj[key] = [obj[key],val]; 1394 } 1395 } 1396 } 1397 }); 1398 return obj; 1399} 1400 1401function toKeyValue(obj) { 1402 var parts = []; 1403 forEach(obj, function(value, key) { 1404 if (isArray(value)) { 1405 forEach(value, function(arrayValue) { 1406 parts.push(encodeUriQuery(key, true) + 1407 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1408 }); 1409 } else { 1410 parts.push(encodeUriQuery(key, true) + 1411 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1412 } 1413 }); 1414 return parts.length ? parts.join('&') : ''; 1415} 1416 1417 1418/** 1419 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1420 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1421 * segments: 1422 * segment = *pchar 1423 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1424 * pct-encoded = "%" HEXDIG HEXDIG 1425 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1426 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1427 * / "*" / "+" / "," / ";" / "=" 1428 */ 1429function encodeUriSegment(val) { 1430 return encodeUriQuery(val, true). 1431 replace(/%26/gi, '&'). 1432 replace(/%3D/gi, '='). 1433 replace(/%2B/gi, '+'); 1434} 1435 1436 1437/** 1438 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1439 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1440 * encoded per http://tools.ietf.org/html/rfc3986: 1441 * query = *( pchar / "/" / "?" ) 1442 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1443 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1444 * pct-encoded = "%" HEXDIG HEXDIG 1445 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1446 * / "*" / "+" / "," / ";" / "=" 1447 */ 1448function encodeUriQuery(val, pctEncodeSpaces) { 1449 return encodeURIComponent(val). 1450 replace(/%40/gi, '@'). 1451 replace(/%3A/gi, ':'). 1452 replace(/%24/g, '$'). 1453 replace(/%2C/gi, ','). 1454 replace(/%3B/gi, ';'). 1455 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1456} 1457 1458var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1459 1460function getNgAttribute(element, ngAttr) { 1461 var attr, i, ii = ngAttrPrefixes.length; 1462 for (i = 0; i < ii; ++i) { 1463 attr = ngAttrPrefixes[i] + ngAttr; 1464 if (isString(attr = element.getAttribute(attr))) { 1465 return attr; 1466 } 1467 } 1468 return null; 1469} 1470 1471/** 1472 * @ngdoc directive 1473 * @name ngApp 1474 * @module ng 1475 * 1476 * @element ANY 1477 * @param {angular.Module} ngApp an optional application 1478 * {@link angular.module module} name to load. 1479 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1480 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1481 * do not use explicit function annotation (and are thus unsuitable for minification), as described 1482 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1483 * tracking down the root of these bugs. 1484 * 1485 * @description 1486 * 1487 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1488 * designates the **root element** of the application and is typically placed near the root element 1489 * of the page - e.g. on the `<body>` or `<html>` tags. 1490 * 1491 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1491p` 1492 * found in the document will be used to define the root element to auto-bootstrap as an 1493 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1494 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1495 * 1496 * You can specify an **AngularJS module** to be used as the root module for the application. This 1497 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1498 * should contain the application code needed or have dependencies on other modules that will 1499 * contain the code. See {@link angular.module} for more information. 1500 * 1501 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1502 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1503 * would not be resolved to `3`. 1504 * 1505 * `ngApp` is the easiest, and most common way to bootstrap an application. 1506 * 1507 <example module="ngAppDemo"> 1508 <file name="index.html"> 1509 <div ng-controller="ngAppDemoController"> 1510 I can add: {{a}} + {{b}} = {{ a+b }} 1511 </div> 1512 </file> 1513 <file name="script.js"> 1514 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1515 $scope.a = 1; 1516 $scope.b = 2; 1517 }); 1518 </file> 1519 </example> 1520 * 1521 * Using `ngStrictDi`, you would see something like this: 1522 * 1523 <example ng-app-included="true"> 1524 <file name="index.html"> 1525 <div ng-app="ngAppStrictDemo" ng-strict-di> 1526 <div ng-controller="GoodController1"> 1527 I can add: {{a}} + {{b}} = {{ a+b }} 1528 1529 <p>This renders because the controller does not fail to 1530 instantiate, by using explicit annotation style (see 1531 script.js for details) 1532 </p> 1533 </div> 1534 1535 <div ng-controller="GoodController2"> 1536 Name: <input ng-model="name"><br /> 1537 Hello, {{name}}! 1538 1539 <p>This renders because the controller does not fail to 1540 instantiate, by using explicit annotation style 1541 (see script.js for details) 1542 </p> 1543 </div> 1544 1545 <div ng-controller="BadController"> 1546 I can add: {{a}} + {{b}} = {{ a+b }} 1547 1548 <p>The controller could not be instantiated, due to relying 1549 on automatic function annotations (which are disabled in 1550 strict mode). As such, the content of this section is not 1551 interpolated, and there should be an error in your web console. 1552 </p> 1553 </div> 1554 </div> 1555 </file> 1556 <file name="script.js"> 1557 angular.module('ngAppStrictDemo', []) 1558 // BadController will fail to instantiate, due to relying on automatic function annotation, 1559 // rather than an explicit annotation 1560 .controller('BadController', function($scope) { 1561 $scope.a = 1; 1562 $scope.b = 2; 1563 }) 1564 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1565 // due to using explicit annotations using the array style and $inject property, respectively. 1566 .controller('GoodController1', ['$scope', function($scope) { 1567 $scope.a = 1; 1568 $scope.b = 2; 1569 }]) 1570 .controller('GoodController2', GoodController2); 1571 function GoodController2($scope) { 1572 $scope.name = "World"; 1573 } 1574 GoodController2.$inject = ['$scope']; 1575 </file> 1576 <file name="style.css"> 1577 div[ng-controller] { 1578 margin-bottom: 1em; 1579 -webkit-border-radius: 4px; 1580 border-radius: 4px; 1581 border: 1px solid; 1582 padding: .5em; 1583 } 1584 div[ng-controller^=Good] { 1585 border-color: #d6e9c6; 1586 background-color: #dff0d8; 1587 color: #3c763d; 1588 } 1589 div[ng-controller^=Bad] { 1590 border-color: #ebccd1; 1591 background-color: #f2dede; 1592 color: #a94442; 1593 margin-bottom: 0; 1594 } 1595 </file> 1596 </example> 1597 */ 1598function angularInit(element, bootstrap) { 1599 var appElement, 1600 module, 1601 config = {}; 1602 1603 // The element `element` has priority over any other element 1604 forEach(ngAttrPrefixes, function(prefix) { 1605 var name = prefix + 'app'; 1606 1607 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1608 appElement = element; 1609 module = element.getAttribute(name); 1610 } 1611 }); 1612 forEach(ngAttrPrefixes, function(prefix) { 1613 var name = prefix + 'app'; 1614 var candidate; 1615 1616 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1617 appElement = candidate; 1618 module = candidate.getAttribute(name); 1619 } 1620 }); 1621 if (appElement) { 1622 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1623 bootstrap(appElement, module ? [module] : [], config); 1624 } 1625} 1626 1627/** 1628 * @ngdoc function 1629 * @name angular.bootstrap 1630 * @module ng 1631 * @description 1632 * Use this function to manually start up angular application. 1633 * 1634 * See: {@link guide/bootstrap Bootstrap} 1635 *
1636 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1637 * They must use {@link ng.directive:ngApp ngApp}. 1638 * 1639 * Angular will detect if it has been loaded into the browser more than once and only allow the 1640 * first loaded script to be bootstrapped and will report a warning to the browser console for 1641 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1642 * multiple instances of Angular try to work on the DOM. 1643 * 1644 * ```html 1645 * <!doctype html> 1646 * <html> 1647 * <body> 1648 * <div ng-controller="WelcomeController"> 1649 * {{greeting}} 1650 * </div> 1651 * 1652 * <script src="angular.js"></script> 1653 * <script> 1654 * var app = angular.module('demo', []) 1655 * .controller('WelcomeController', function($scope) { 1656 * $scope.greeting = 'Welcome!'; 1657 * }); 1658 * angular.bootstrap(document, ['demo']); 1659 * </script> 1660 * </body> 1661 * </html> 1662 * ``` 1663 * 1664 * @param {DOMElement} element DOM element which is the root of angular application. 1665 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1666 * Each item in the array should be the name of a predefined module or a (DI annotated) 1667 * function that will be invoked by the injector as a `config` block. 1668 * See: {@link angular.module modules} 1669 * @param {Object=} config an object for defining configuration options for the application. The 1670 * following keys are supported: 1671 * 1672 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1673 * assist in finding bugs which break minified code. Defaults to `false`. 1674 * 1675 * @returns {auto.$injector} Returns the newly created injector for this app. 1676 */ 1677function bootstrap(element, modules, config) { 1678 if (!isObject(config)) config = {}; 1679 var defaultConfig = { 1680 strictDi: false 1681 }; 1682 config = extend(defaultConfig, config); 1683 var doBootstrap = function() { 1684 element = jqLite(element); 1685 1686 if (element.injector()) { 1687 var tag = (element[0] === document) ? 'document' : startingTag(element); 1688 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1689 throw ngMinErr( 1690 'btstrpd', 1691 "App Already Bootstrapped with this Element '{0}'", 1692 tag.replace(/</,'<').replace(/>/,'>')); 1693 } 1694 1695 modules = modules || []; 1696 modules.unshift(['$provide', function($provide) { 1697 $provide.value('$rootElement', element); 1698 }]); 1699 1700 if (config.debugInfoEnabled) { 1701 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1702 modules.push(['$compileProvider', function($compileProvider) { 1703 $compileProvider.debugInfoEnabled(true); 1704 }]); 1705 } 1706 1707 modules.unshift('ng'); 1708 var injector = createInjector(modules, config.strictDi); 1709 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1710 function bootstrapApply(scope, element, compile, injector) { 1711 scope.$apply(function() { 1712 element.data('$injector', injector); 1713 compile(element)(scope); 1714 }); 1715 }] 1716 ); 1717 return injector; 1718 }; 1719 1720 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1721 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1722 1723 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1724 config.debugInfoEnabled = true; 1725 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1726 } 1727 1728 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1729 return doBootstrap(); 1730 } 1731 1732 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1733 angular.resumeBootstrap = function(extraModules) { 1734 forEach(extraModules, function(module) { 1735 modules.push(module); 1736 }); 1737 return doBootstrap(); 1738 }; 1739 1740 if (isFunction(angular.resumeDeferredBootstrap)) { 1741 angular.resumeDeferredBootstrap(); 1742 } 1743} 1744 1745/** 1746 * @ngdoc function 1747 * @name angular.reloadWithDebugInfo 1748 * @module ng 1749 * @description 1750 * Use this function to reload the current application with debug information turned on. 1751 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1752 * 1753 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1754 */ 1755function reloadWithDebugInfo() { 1756 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1757 window.location.reload(); 1758} 1759 1760/** 1761 * @name angular.getTestability 1762 * @module ng 1763 * @description 1764 * Get the testability service for the instance of Angular on the given 1765 * element. 1766 * @param {DOMElement} element DOM element which is the root of angular application. 1767 */ 1768function getTestability(rootElement) { 1769 var injector = angular.element(rootElement).injector(); 1770 if (!injector) { 1771 throw ngMinErr('test', 1772 'no injector found for element argument to getTestability'); 1773 } 1774 return injector.get('$$testability'); 1775} 1776 1777var SNAKE_CASE_REGEXP = /[A-Z]/g; 1778function snake_case(name, separator) { 1779 separator = separator || '_'; 1780 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1781 return (pos ? separator : '') + letter.toLowerCase(); 1782 }); 1783} 1784 1785var bindJQueryFired = false; 1786function bindJQuery() { 1787 var originalCleanData; 1788 1789 if (bindJQueryFired) { 1790 return; 1791 } 1792 1793 // bind to jQuery if present; 1794 var jqName = jq(); 1795 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 1796 !jqName ? undefined : // use jqLite 1797 window[jqName]; // use jQuery specified by `ngJq` 1798 1799 // Use jQuery if it exists with proper functionality, otherwise default to us. 1800 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1801 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1802 // versions. It will not work for sure with jQuery <1.7, though. 1803 if (jQuery && jQuery.fn.on) { 1804 jqLite = jQuery; 1805 extend(jQuery.fn, { 1806 scope: JQLitePrototype.scope, 1807 isolateScope: JQLitePrototype.isolateScope, 1808 controller: JQLitePrototype.controller, 1809 injector: JQLitePrototype.injector, 1810 inheritedData: JQLitePrototype.inheritedData 1811 });
vendor: 4,465 bytes, lines 1812-1971
1812 1813 // All nodes removed from the DOM via various jQuery APIs like .remove() 1814 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1815 // the $destroy event on all removed nodes. 1816 originalCleanData = jQuery.cleanData; 1817 jQuery.cleanData = function(elems) { 1818 var events; 1819 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1820 events = jQuery._data(elem, "events"); 1821 if (events && events.$destroy) { 1822 jQuery(elem).triggerHandler('$destroy'); 1823 } 1824 } 1825 originalCleanData(elems); 1826 }; 1827 } else { 1828 jqLite = JQLite; 1829 } 1830 1831 angular.element = jqLite; 1832 1833 // Prevent double-proxying. 1834 bindJQueryFired = true; 1835} 1836 1837/** 1838 * throw error if the argument is falsy. 1839 */ 1840function assertArg(arg, name, reason) { 1841 if (!arg) { 1842 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1843 } 1844 return arg; 1845} 1846 1847function assertArgFn(arg, name, acceptArrayAnnotation) { 1848 if (acceptArrayAnnotation && isArray(arg)) { 1849 arg = arg[arg.length - 1]; 1850 } 1851 1852 assertArg(isFunction(arg), name, 'not a function, got ' + 1853 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1854 return arg; 1855} 1856 1857/** 1858 * throw error if the name given is hasOwnProperty 1859 * @param {String} name the name to test 1860 * @param {String} context the context in which the name is used, such as module or directive 1861 */ 1862function assertNotHasOwnProperty(name, context) { 1863 if (name === 'hasOwnProperty') { 1864 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1865 } 1866} 1867 1868/** 1869 * Return the value accessible from the object by path. Any undefined traversals are ignored 1870 * @param {Object} obj starting object 1871 * @param {String} path path to traverse 1872 * @param {boolean} [bindFnToScope=true] 1873 * @returns {Object} value as accessible by path 1874 */ 1875//TODO(misko): this function needs to be removed 1876function getter(obj, path, bindFnToScope) { 1877 if (!path) return obj; 1878 var keys = path.split('.'); 1879 var key; 1880 var lastInstance = obj; 1881 var len = keys.length; 1882 1883 for (var i = 0; i < len; i++) { 1884 key = keys[i]; 1885 if (obj) { 1886 obj = (lastInstance = obj)[key]; 1887 } 1888 } 1889 if (!bindFnToScope && isFunction(obj)) { 1890 return bind(lastInstance, obj); 1891 } 1892 return obj; 1893} 1894 1895/** 1896 * Return the DOM siblings between the first and last node in the given array. 1897 * @param {Array} array like object 1898 * @returns {Array} the inputted object or a jqLite collection containing the nodes 1899 */ 1900function getBlockNodes(nodes) { 1901 // TODO(perf): update `nodes` instead of creating a new object? 1902 var node = nodes[0]; 1903 var endNode = nodes[nodes.length - 1]; 1904 var blockNodes; 1905 1906 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 1907 if (blockNodes || nodes[i] !== node) { 1908 if (!blockNodes) { 1909 blockNodes = jqLite(slice.call(nodes, 0, i)); 1910 } 1911 blockNodes.push(node); 1912 } 1913 } 1914 1915 return blockNodes || nodes; 1916} 1917 1918 1919/** 1920 * Creates a new object without a prototype. This object is useful for lookup without having to 1921 * guard against prototypically inherited properties via hasOwnProperty. 1922 * 1923 * Related micro-benchmarks: 1924 * - http://jsperf.com/object-create2 1925 * - http://jsperf.com/proto-map-lookup/2 1926 * - http://jsperf.com/for-in-vs-object-keys2 1927 * 1928 * @returns {Object} 1929 */ 1930function createMap() { 1931 return Object.create(null); 1932} 1933 1934var NODE_TYPE_ELEMENT = 1; 1935var NODE_TYPE_ATTRIBUTE = 2; 1936var NODE_TYPE_TEXT = 3; 1937var NODE_TYPE_COMMENT = 8; 1938var NODE_TYPE_DOCUMENT = 9; 1939var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1940 1941/** 1942 * @ngdoc type 1943 * @name angular.Module 1944 * @module ng 1945 * @description 1946 * 1947 * Interface for configuring angular {@link angular.module modules}. 1948 */ 1949 1950function setupModuleLoader(window) { 1951 1952 var $injectorMinErr = minErr('$injector'); 1953 var ngMinErr = minErr('ng'); 1954 1955 function ensure(obj, name, factory) { 1956 return obj[name] || (obj[name] = factory()); 1957 } 1958 1959 var angular = ensure(window, 'angular', Object); 1960 1961 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1962 angular.$$minErr = angular.$$minErr || minErr; 1963 1964 return ensure(angular, 'module', function() { 1965 /** @type {Object.<string, angular.Module>} */ 1966 var modules = {}; 1967 1968 /** 1969 * @ngdoc function 1970 * @name angular.module 1971 * @module ng
1972 * @description 1973 * 1974 * The `angular.module` is a global place for creating, registering and retrieving Angular 1975 * modules. 1976 * All modules (angular core or 3rd party) that should be available to an application must be 1977 * registered using this mechanism. 1978 * 1979 * Passing one argument retrieves an existing {@link angular.Module}, 1980 * whereas passing more than one argument creates a new {@link angular.Module} 1981 * 1982 * 1983 * # Module 1984 * 1985 * A module is a collection of services, directives, controllers, filters, and configuration information. 1986 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1987 * 1988 * ```js 1989 * // Create a new module 1990 * var myModule = angular.module('myModule', []); 1991 * 1992 * // register a new service 1993 * myModule.value('appName', 'MyCoolApp'); 1994 * 1995 * // configure existing services inside initialization blocks. 1996 * myModule.config(['$locationProvider', function($locationProvider) { 1997 * // Configure existing providers 1998 * $locationProvider.hashPrefix('!'); 1999 * }]); 2000 * ``` 2001 * 2002 * Then you can create an injector and load your modules like this: 2003 * 2004 * ```js 2005 * var injector = angular.injector(['ng', 'myModule']) 2006 * ``` 2007 * 2008 * However it's more likely that you'll just use 2009 * {@link ng.directive:ngApp ngApp} or 2010 * {@link angular.bootstrap} to simplify this process for you. 2011 * 2012 * @param {!string} name The name of the module to create or retrieve. 2013 * @param {!Array.<string>=} requires If specified then new module is being created. If 2014 * unspecified then the module is being retrieved for further configuration. 2015 * @param {Function=} configFn Optional configuration function for the module. Same as 2016 * {@link angular.Module#config Module#config()}. 2017 * @returns {angular.Module} new module with the {@link angular.Module} api. 2018 */ 2019 return function module(name, requires, configFn) { 2020 var assertNotHasOwnProperty = function(name, context) { 2021 if (name === 'hasOwnProperty') { 2022 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2023 } 2024 }; 2025 2026 assertNotHasOwnProperty(name, 'module'); 2027 if (requires && modules.hasOwnProperty(name)) { 2028 modules[name] = null; 2029 } 2030 return ensure(modules, name, function() { 2031 if (!requires) { 2032 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 2033 "the module name or forgot to load it. If registering a module ensure that you " + 2034 "specify the dependencies as the second argument.", name); 2035 } 2036 2037 /** @type {!Array.<Array.<*>>} */ 2038 var invokeQueue = []; 2039 2040 /** @type {!Array.<Function>} */ 2041 var configBlocks = []; 2042 2043 /** @type {!Array.<Function>} */ 2044 var runBlocks = []; 2045 2046 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2047 2048 /** @type {angular.Module} */ 2049 var moduleInstance = { 2050 // Private state 2051 _invokeQueue: invokeQueue, 2052 _configBlocks: configBlocks, 2053 _runBlocks: runBlocks, 2054 2055 /** 2056 * @ngdoc property 2057 * @name angular.Module#requires 2058 * @module ng 2059 * 2060 * @description 2061 * Holds the list of modules which the injector will load before the current module is 2062 * loaded. 2063 */ 2064 requires: requires, 2065 2066 /** 2067 * @ngdoc property 2068 * @name angular.Module#name 2069 * @module ng 2070 * 2071 * @description 2072 * Name of the module. 2073 */ 2074 name: name, 2075 2076 2077 /** 2078 * @ngdoc method 2079 * @name angular.Module#provider 2080 * @module ng 2081 * @param {string} name service name 2082 * @param {Function} providerType Construction function for creating new instance of the 2083 * service.
2084 * @description 2085 * See {@link auto.$provide#provider $provide.provider()}. 2086 */ 2087 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2088 2089 /** 2090 * @ngdoc method 2091 * @name angular.Module#factory 2092 * @module ng 2093 * @param {string} name service name 2094 * @param {Function} providerFunction Function for creating new instance of the service. 2095 * @description 2096 * See {@link auto.$provide#factory $provide.factory()}. 2097 */ 2098 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2099 2100 /** 2101 * @ngdoc method 2102 * @name angular.Module#service 2103 * @module ng 2104 * @param {string} name service name 2105 * @param {Function} constructor A constructor function that will be instantiated. 2106 * @description 2107 * See {@link auto.$provide#service $provide.service()}. 2108 */ 2109 service: invokeLaterAndSetModuleName('$provide', 'service'), 2110 2111 /** 2112 * @ngdoc method 2113 * @name angular.Module#value 2114 * @module ng 2115 * @param {string} name service name 2116 * @param {*} object Service instance object. 2117 * @description 2118 * See {@link auto.$provide#value $provide.value()}. 2119 */ 2120 value: invokeLater('$provide', 'value'), 2121 2122 /** 2123 * @ngdoc method 2124 * @name angular.Module#constant 2125 * @module ng 2126 * @param {string} name constant name 2127 * @param {*} object Constant value. 2128 * @description 2129 * Because the constants are fixed, they get applied before other provide methods. 2130 * See {@link auto.$provide#constant $provide.constant()}. 2131 */ 2132 constant: invokeLater('$provide', 'constant', 'unshift'), 2133 2134 /** 2135 * @ngdoc method 2136 * @name angular.Module#decorator 2137 * @module ng 2138 * @param {string} The name of the service to decorate. 2139 * @param {Function} This function will be invoked when the service needs to be 2140 * instantiated and should return the decorated service instance. 2141 * @description 2142 * See {@link auto.$provide#decorator $provide.decorator()}. 2143 */ 2144 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 2145 2146 /** 2147 * @ngdoc method 2148 * @name angular.Module#animation 2149 * @module ng 2150 * @param {string} name animation name 2151 * @param {Function} animationFactory Factory function for creating new instance of an 2152 * animation. 2153 * @description 2154 * 2155 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2156 * 2157 * 2158 * Defines an animation hook that can be later used with 2159 * {@link $animate $animate} service and directives that use this service. 2160 * 2161 * ```js 2162 * module.animation('.animation-name', function($inject1, $inject2) { 2163 * return { 2164 * eventName : function(element, done) { 2165 * //code to run the animation 2166 * //once complete, then run done() 2167 * return function cancellationFunction(element) { 2168 * //code to cancel the animation 2169 * } 2170 * } 2171 * } 2172 * }) 2173 * ``` 2174 * 2175 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2176 * {@link ngAnimate ngAnimate module} for more information. 2177 */ 2178 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2179 2180 /** 2181 * @ngdoc method 2182 * @name angular.Module#filter 2183 * @module ng 2184 * @param {string} name Filter name - this must be a valid angular expression identifier 2185 * @param {Function} filterFactory Factory function for creating new instance of filter.
2186 * @description 2187 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2188 * 2189 * <div class="alert alert-warning"> 2190 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2191 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2192 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2193 * (`myapp_subsection_filterx`). 2194 * </div> 2195 */ 2196 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2197 2198 /** 2199 * @ngdoc method 2200 * @name angular.Module#controller 2201 * @module ng 2202 * @param {string|Object} name Controller name, or an object map of controllers where the 2203 * keys are the names and the values are the constructors. 2204 * @param {Function} constructor Controller constructor function. 2205 * @description 2206 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2207 */ 2208 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2209 2210 /** 2211 * @ngdoc method 2212 * @name angular.Module#directive 2213 * @module ng 2214 * @param {string|Object} name Directive name, or an object map of directives where the 2215 * keys are the names and the values are the factories. 2216 * @param {Function} directiveFactory Factory function for creating new instance of 2217 * directives. 2218 * @description 2219 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2220 */ 2221 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2222 2223 /** 2224 * @ngdoc method 2225 * @name angular.Module#component 2226 * @module ng 2227 * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) 2228 * @param {Object} options Component definition object (a simplified 2229 * {@link ng.$compile#directive-definition-object directive definition object}) 2230 * 2231 * @description 2232 * See {@link ng.$compileProvider#component $compileProvider.component()}. 2233 */ 2234 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 2235 2236 /** 2237 * @ngdoc method 2238 * @name angular.Module#config 2239 * @module ng 2240 * @param {Function} configFn Execute this function on module load. Useful for service 2241 * configuration. 2242 * @description 2243 * Use this method to register work which needs to be performed on module loading. 2244 * For more about how to configure services, see 2245 * {@link providers#provider-recipe Provider Recipe}. 2246 */ 2247 config: config, 2248 2249 /** 2250 * @ngdoc method 2251 * @name angular.Module#run 2252 * @module ng 2253 * @param {Function} initializationFn Execute this function after injector creation. 2254 * Useful for application initialization. 2255 * @description 2256 * Use this method to register work which should be performed when the injector is done 2257 * loading all modules. 2258 */ 2259 run: function(block) { 2260 runBlocks.push(block); 2261 return this; 2262 } 2263 }; 2264 2265 if (configFn) { 2266 config(configFn); 2267 } 2268 2269 return moduleInstance; 2270 2271 /** 2272 * @param {string} provider 2273 * @param {string} method 2274 * @param {String=} insertMethod 2275 * @returns {angular.Module} 2276 */ 2277 function invokeLater(provider, method, insertMethod, queue) { 2278 if (!queue) queue = invokeQueue; 2279 return function() { 2280 queue[insertMethod || 'push']([provider, method, arguments]); 2281 return moduleInstance; 2282 }; 2283 } 2284 2285 /** 2286 * @param {string} provider 2287 * @param {string} method 2288 * @returns {angular.Module} 2289 */
vendor: 4,918 bytes, lines 2290-2489
2290 function invokeLaterAndSetModuleName(provider, method) { 2291 return function(recipeName, factoryFunction) { 2292 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2293 invokeQueue.push([provider, method, arguments]); 2294 return moduleInstance; 2295 }; 2296 } 2297 }); 2298 }; 2299 }); 2300 2301} 2302 2303/* global: toDebugString: true */ 2304 2305function serializeObject(obj) { 2306 var seen = []; 2307 2308 return JSON.stringify(obj, function(key, val) { 2309 val = toJsonReplacer(key, val); 2310 if (isObject(val)) { 2311 2312 if (seen.indexOf(val) >= 0) return '...'; 2313 2314 seen.push(val); 2315 } 2316 return val; 2317 }); 2318} 2319 2320function toDebugString(obj) { 2321 if (typeof obj === 'function') { 2322 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2323 } else if (isUndefined(obj)) { 2324 return 'undefined'; 2325 } else if (typeof obj !== 'string') { 2326 return serializeObject(obj); 2327 } 2328 return obj; 2329} 2330 2331/* global angularModule: true, 2332 version: true, 2333 2334 $CompileProvider, 2335 2336 htmlAnchorDirective, 2337 inputDirective, 2338 inputDirective, 2339 formDirective, 2340 scriptDirective, 2341 selectDirective, 2342 styleDirective, 2343 optionDirective, 2344 ngBindDirective, 2345 ngBindHtmlDirective, 2346 ngBindTemplateDirective, 2347 ngClassDirective, 2348 ngClassEvenDirective, 2349 ngClassOddDirective, 2350 ngCloakDirective, 2351 ngControllerDirective, 2352 ngFormDirective, 2353 ngHideDirective, 2354 ngIfDirective, 2355 ngIncludeDirective, 2356 ngIncludeFillContentDirective, 2357 ngInitDirective, 2358 ngNonBindableDirective, 2359 ngPluralizeDirective, 2360 ngRepeatDirective, 2361 ngShowDirective, 2362 ngStyleDirective, 2363 ngSwitchDirective, 2364 ngSwitchWhenDirective, 2365 ngSwitchDefaultDirective, 2366 ngOptionsDirective, 2367 ngTranscludeDirective, 2368 ngModelDirective, 2369 ngListDirective, 2370 ngChangeDirective, 2371 patternDirective, 2372 patternDirective, 2373 requiredDirective, 2374 requiredDirective, 2375 minlengthDirective, 2376 minlengthDirective, 2377 maxlengthDirective, 2378 maxlengthDirective, 2379 ngValueDirective, 2380 ngModelOptionsDirective, 2381 ngAttributeAliasDirectives, 2382 ngEventDirectives, 2383 2384 $AnchorScrollProvider, 2385 $AnimateProvider, 2386 $CoreAnimateCssProvider, 2387 $$CoreAnimateJsProvider, 2388 $$CoreAnimateQueueProvider, 2389 $$AnimateRunnerFactoryProvider, 2390 $$AnimateAsyncRunFactoryProvider, 2391 $BrowserProvider, 2392 $CacheFactoryProvider, 2393 $ControllerProvider, 2394 $DateProvider, 2395 $DocumentProvider, 2396 $ExceptionHandlerProvider, 2397 $FilterProvider, 2398 $$ForceReflowProvider, 2399 $InterpolateProvider, 2400 $IntervalProvider, 2401 $$HashMapProvider, 2402 $HttpProvider, 2403 $HttpParamSerializerProvider, 2404 $HttpParamSerializerJQLikeProvider, 2405 $HttpBackendProvider, 2406 $xhrFactoryProvider, 2407 $LocationProvider, 2408 $LogProvider, 2409 $ParseProvider, 2410 $RootScopeProvider, 2411 $QProvider, 2412 $$QProvider, 2413 $$SanitizeUriProvider, 2414 $SceProvider, 2415 $SceDelegateProvider, 2416 $SnifferProvider, 2417 $TemplateCacheProvider, 2418 $TemplateRequestProvider, 2419 $$TestabilityProvider, 2420 $TimeoutProvider, 2421 $$RAFProvider, 2422 $WindowProvider, 2423 $$jqLiteProvider, 2424 $$CookieReaderProvider 2425*/ 2426 2427 2428/** 2429 * @ngdoc object 2430 * @name angular.version 2431 * @module ng 2432 * @description 2433 * An object that contains information about the current AngularJS version. 2434 * 2435 * This object has the following properties: 2436 * 2437 * - `full` â `{string}` â Full version string, such as "0.9.18". 2438 * - `major` â `{number}` â Major version number, such as "0". 2439 * - `minor` â `{number}` â Minor version number, such as "9". 2440 * - `dot` â `{number}` â Dot version number, such as "18". 2441 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2442 */ 2443var version = { 2444 full: '1.5.0-rc.1', // all of these placeholder strings will be replaced by grunt's 2445 major: 1, // package task 2446 minor: 5, 2447 dot: 0, 2448 codeName: 'quantum-fermentation' 2449}; 2450 2451 2452function publishExternalAPI(angular) { 2453 extend(angular, { 2454 'bootstrap': bootstrap, 2455 'copy': copy, 2456 'extend': extend, 2457 'merge': merge, 2458 'equals': equals, 2459 'element': jqLite, 2460 'forEach': forEach, 2461 'injector': createInjector, 2462 'noop': noop, 2463 'bind': bind, 2464 'toJson': toJson, 2465 'fromJson': fromJson, 2466 'identity': identity, 2467 'isUndefined': isUndefined, 2468 'isDefined': isDefined, 2469 'isString': isString, 2470 'isFunction': isFunction, 2471 'isObject': isObject, 2472 'isNumber': isNumber, 2473 'isElement': isElement, 2474 'isArray': isArray, 2475 'version': version, 2476 'isDate': isDate, 2477 'lowercase': lowercase, 2478 'uppercase': uppercase, 2479 'callbacks': {counter: 0}, 2480 'getTestability': getTestability, 2481 '$$minErr': minErr, 2482 '$$csp': csp, 2483 'reloadWithDebugInfo': reloadWithDebugInfo 2484 }); 2485 2486 angularModule = setupModuleLoader(window); 2487 2488 angularModule('ng', ['ngLocale'], ['$provide', 2489 function ngModule($provide) {
2490 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2491 $provide.provider({ 2492 $$sanitizeUri: $$SanitizeUriProvider 2493 }); 2494 $provide.provider('$compile', $CompileProvider). 2495 directive({ 2496 a: htmlAnchorDirective, 2497 input: inputDirective, 2498 textarea: inputDirective, 2499 form: formDirective, 2500 script: scriptDirective, 2501 select: selectDirective, 2502 style: styleDirective, 2503 option: optionDirective, 2504 ngBind: ngBindDirective, 2505 ngBindHtml: ngBindHtmlDirective, 2506 ngBindTemplate: ngBindTemplateDirective, 2507 ngClass: ngClassDirective, 2508 ngClassEven: ngClassEvenDirective, 2509 ngClassOdd: ngClassOddDirective, 2510 ngCloak: ngCloakDirective, 2511 ngController: ngControllerDirective, 2512 ngForm: ngFormDirective, 2513 ngHide: ngHideDirective, 2514 ngIf: ngIfDirective, 2515 ngInclude: ngIncludeDirective, 2516 ngInit: ngInitDirective, 2517 ngNonBindable: ngNonBindableDirective, 2518 ngPluralize: ngPluralizeDirective, 2519 ngRepeat: ngRepeatDirective, 2520 ngShow: ngShowDirective, 2521 ngStyle: ngStyleDirective, 2522 ngSwitch: ngSwitchDirective, 2523 ngSwitchWhen: ngSwitchWhenDirective, 2524 ngSwitchDefault: ngSwitchDefaultDirective, 2525 ngOptions: ngOptionsDirective, 2526 ngTransclude: ngTranscludeDirective, 2527 ngModel: ngModelDirective, 2528 ngList: ngListDirective, 2529 ngChange: ngChangeDirective, 2530 pattern: patternDirective, 2531 ngPattern: patternDirective, 2532 required: requiredDirective, 2533 ngRequired: requiredDirective, 2534 minlength: minlengthDirective, 2535 ngMinlength: minlengthDirective, 2536 maxlength: maxlengthDirective, 2537 ngMaxlength: maxlengthDirective, 2538 ngValue: ngValueDirective, 2539 ngModelOptions: ngModelOptionsDirective 2540 }). 2541 directive({ 2542 ngInclude: ngIncludeFillContentDirective 2543 }). 2544 directive(ngAttributeAliasDirectives). 2545 directive(ngEventDirectives); 2546 $provide.provider({ 2547 $anchorScroll: $AnchorScrollProvider, 2548 $animate: $AnimateProvider, 2549 $animateCss: $CoreAnimateCssProvider, 2550 $$animateJs: $$CoreAnimateJsProvider, 2551 $$animateQueue: $$CoreAnimateQueueProvider, 2552 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 2553 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 2554 $browser: $BrowserProvider, 2555 $cacheFactory: $CacheFactoryProvider, 2556 $controller: $ControllerProvider, 2557 $document: $DocumentProvider, 2558 $exceptionHandler: $ExceptionHandlerProvider, 2559 $filter: $FilterProvider, 2560 $$forceReflow: $$ForceReflowProvider, 2561 $interpolate: $InterpolateProvider, 2562 $interval: $IntervalProvider, 2563 $http: $HttpProvider, 2564 $httpParamSerializer: $HttpParamSerializerProvider, 2565 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2566 $httpBackend: $HttpBackendProvider, 2567 $xhrFactory: $xhrFactoryProvider, 2568 $location: $LocationProvider, 2569 $log: $LogProvider, 2570 $parse: $ParseProvider, 2571 $rootScope: $RootScopeProvider, 2572 $q: $QProvider, 2573 $$q: $$QProvider, 2574 $sce: $SceProvider, 2575 $sceDelegate: $SceDelegateProvider, 2576 $sniffer: $SnifferProvider, 2577 $templateCache: $TemplateCacheProvider, 2578 $templateRequest: $TemplateRequestProvider, 2579 $$testability: $$TestabilityProvider, 2580 $timeout: $TimeoutProvider, 2581 $window: $WindowProvider, 2582 $$rAF: $$RAFProvider, 2583 $$jqLite: $$jqLiteProvider, 2584 $$HashMap: $$HashMapProvider, 2585 $$cookieReader: $$CookieReaderProvider 2586 }); 2587 } 2588 ]); 2589} 2590 2591/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2592 * Any commits to this file should be reviewed with security in mind. * 2593 * Changes to this file can potentially create security vulnerabilities. * 2594 * An approval from 2 Core members with history of modifying * 2595 * this file is required. * 2596 * * 2597 * Does the change somehow allow for arbitrary javascript to be executed? * 2598 * Or allows for someone to change the prototype of built-in objects? * 2599 * Or gives undesired access to variables likes document or window? * 2600 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2601 2602/* global JQLitePrototype: true, 2603 addEventListenerFn: true, 2604 removeEventListenerFn: true, 2605 BOOLEAN_ATTR: true, 2606 ALIASED_ATTR: true, 2607*/ 2608 2609////////////////////////////////// 2610//JQLite 2611////////////////////////////////// 2612 2613/** 2614 * @ngdoc function 2615 * @name angular.element 2616 * @module ng 2617 * @kind function 2618 * 2619 * @description 2620 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2621 * 2622 * If jQuery is available, `angular.element` is an alias for the 2623 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2624 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 2625 * 2626 * jqLite is a tiny, API-compatible subset of jQuery that allows 2627 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 2628 * commonly needed functionality with the goal of having a very small footprint. 2629 * 2630 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 2631 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 2632 * specific version of jQuery if multiple versions exist on the page. 2633 * 2634 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with
2634jQuery or 2635 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 2636 * 2637 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 2638 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 2639 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 2640 * 2641 * ## Angular's jqLite 2642 * jqLite provides only the following jQuery methods: 2643 * 2644 * - [`addClass()`](http://api.jquery.com/addClass/) 2645 * - [`after()`](http://api.jquery.com/after/) 2646 * - [`append()`](http://api.jquery.com/append/) 2647 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2648 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2649 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2650 * - [`clone()`](http://api.jquery.com/clone/) 2651 * - [`contents()`](http://api.jquery.com/contents/) 2652 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 2653 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 2654 * - [`data()`](http://api.jquery.com/data/) 2655 * - [`detach()`](http://api.jquery.com/detach/) 2656 * - [`empty()`](http://api.jquery.com/empty/) 2657 * - [`eq()`](http://api.jquery.com/eq/) 2658 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2659 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2660 * - [`html()`](http://api.jquery.com/html/) 2661 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2662 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2663 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 2664 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2665 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2666 * - [`prepend()`](http://api.jquery.com/prepend/) 2667 * - [`prop()`](http://api.jquery.com/prop/) 2668 * - [`ready()`](http://api.jquery.com/ready/) 2669 * - [`remove()`](http://api.jquery.com/remove/) 2670 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2671 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2672 * - [`removeData()`](http://api.jquery.com/removeData/) 2673 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2674 * - [`text()`](http://api.jquery.com/text/) 2675 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2676 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2677 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 2678 * - [`val()`](http://api.jquery.com/val/) 2679 * - [`wrap()`](http://api.jquery.com/wrap/) 2680 * 2681 * ## jQuery/jqLite Extras 2682 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2683 * 2684 * ### Events 2685 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 2686 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2687 * element before it is removed. 2688 * 2689 * ### Methods 2690 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2691 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2692 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2693 * `'ngModel'`). 2694 * - `injector()` - retrieves the injector of the current element or its parent. 2695 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2696 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2697 * be enabled. 2698 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2699 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2700 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2701 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2702 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2703 * parent element is reached. 2704 * 2705 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2706 * @returns {Object} jQuery object. 2707 */ 2708 2709JQLite.expando = 'ng339'; 2710 2711var jqCache = JQLite.cache = {}, 2712 jqId = 1, 2713 addEventListenerFn = function(element, type, fn) { 2714 element.addEventListener(type, fn, false); 2715 }, 2716 removeEventListenerFn = function(element, type, fn) { 2717 element.removeEventListener(type, fn, false); 2718 }; 2719 2720/* 2721 * !!! This is an undocumented "private" function !!! 2722 */ 2723JQLite._data = function(node) { 2724 //jQuery always returns an object on cache miss 2725 return this.cache[node[this.expando]] || {}; 2726}; 2727 2728function jqNextId() { return ++jqId; } 2729 2730 2731var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2732var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2733var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2734var jqLiteMinErr = minErr('jqLite'); 2735 2736/** 2737 * Converts snake_case to camelCase. 2738 * Also there is special case for Moz prefix starting with upper case letter. 2739 * @param name Name to normalize 2740 */ 2741function camelCase(name) { 2742 return name. 2743 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2744 return offset ? letter.toUpperCase() : letter; 2745 }). 2746 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2747} 2748 2749var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 2750var HTML_REGEXP = /<|&#?\w+;/; 2751var TAG_NAME_REGEXP = /<([\w:-]+)/; 2752var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 2753 2754var wrapMap = { 2755 'option': [1, '<select multiple="multiple">', '</select>'], 2756 2757 'thead': [1, '<table>', '</table>'], 2758 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2759 'tr': [2, '<table><tbody>', '</tbody></table>'], 2760 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2761 '_default': [0, "", ""] 2762}; 2763 2764wrapMap.optgroup = wrapMap.option; 2765wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2766wrapMap.th = wrapMap.td; 2767 2768 2769function jqLiteIsTextNode(html) { 2770 return !HTML_REGEXP.test(html); 2771} 2772 2773function jqLiteAcceptsData(node) { 2774 // The window object can accept data but has no nodeType 2775 // Otherwise we are only interested in elements (1) and documents (9) 2776 var nodeType = node.nodeType; 2777 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2778} 2779 2780function jqLiteHasData(node) { 2781 for (var key in jqCache[node.ng339]) { 2782 return true; 2783 } 2784 return false; 2785} 2786 2787function jqLiteCleanData(nodes) { 2788 for (var i = 0, ii = nodes.length; i < ii; i++) { 2789 jqLiteRemoveData(nodes[i]); 2790 } 2791} 2792 2793function jqLiteBuildFragment(html, context) { 2794 var tmp, tag, wrap, 2795 fragment = context.createDocumentFragment(), 2796 nodes = [], i; 2797 2798 if (jqLiteIsTextNode(html)) { 2799 // Convert non-html into a text node 2800 nodes.push(context.createTextNode(html)); 2801 } else { 2802 // Convert html into DOM nodes 2803 tmp = tmp || fragment.appendChild(context.createElement("div")); 2804 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2805 wrap = wrapMap[tag] || wrapMap._default; 2806 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2807 2808 // Descend through wrappers to the right content 2809 i = wrap[0]; 2810 while (i--) { 2811 tmp = tmp.lastChild; 2812 } 2813 2814 nodes = concat(nodes, tmp.childNodes); 2815 2816 tmp = fragment.firstChild; 2817 tmp.textContent = ""; 2818 } 2819 2820 // Remove wrapper from fragment 2821 fragment.textContent = ""; 2822 fragment.innerHTML = ""; // Clear inner HTML 2823 forEach(nodes, function(node) { 2824 fragment.appendChild(node); 2825 }); 2826 2827 return fragment; 2828} 2829 2830function jqLiteParseHTML(html, context) { 2831 context = context || document; 2832 var parsed; 2833 2834 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2835 return [context.createElement(parsed[1])]; 2836 } 2837 2838 if ((parsed = jqLiteBuildFragment(html, context))) { 2839 return parsed.childNodes; 2840 } 2841 2842 return []; 2843} 2844 2845 2846// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 2847var jqLiteContains = Node.prototype.contains || function(arg) { 2848 // jshint bitwise: false 2849 return !!(this.compareDocumentPosition(arg) & 16); 2850 // jshint bitwise: true 2851}; 2852
vendor: 23,307 bytes, lines 2853-3624
2853///////////////////////////////////////////// 2854function JQLite(element) { 2855 if (element instanceof JQLite) { 2856 return element; 2857 } 2858 2859 var argIsString; 2860 2861 if (isString(element)) { 2862 element = trim(element); 2863 argIsString = true; 2864 } 2865 if (!(this instanceof JQLite)) { 2866 if (argIsString && element.charAt(0) != '<') { 2867 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2868 } 2869 return new JQLite(element); 2870 } 2871 2872 if (argIsString) { 2873 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2874 } else { 2875 jqLiteAddNodes(this, element); 2876 } 2877} 2878 2879function jqLiteClone(element) { 2880 return element.cloneNode(true); 2881} 2882 2883function jqLiteDealoc(element, onlyDescendants) { 2884 if (!onlyDescendants) jqLiteRemoveData(element); 2885 2886 if (element.querySelectorAll) { 2887 var descendants = element.querySelectorAll('*'); 2888 for (var i = 0, l = descendants.length; i < l; i++) { 2889 jqLiteRemoveData(descendants[i]); 2890 } 2891 } 2892} 2893 2894function jqLiteOff(element, type, fn, unsupported) { 2895 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2896 2897 var expandoStore = jqLiteExpandoStore(element); 2898 var events = expandoStore && expandoStore.events; 2899 var handle = expandoStore && expandoStore.handle; 2900 2901 if (!handle) return; //no listeners registered 2902 2903 if (!type) { 2904 for (type in events) { 2905 if (type !== '$destroy') { 2906 removeEventListenerFn(element, type, handle); 2907 } 2908 delete events[type]; 2909 } 2910 } else { 2911 2912 var removeHandler = function(type) { 2913 var listenerFns = events[type]; 2914 if (isDefined(fn)) { 2915 arrayRemove(listenerFns || [], fn); 2916 } 2917 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 2918 removeEventListenerFn(element, type, handle); 2919 delete events[type]; 2920 } 2921 }; 2922 2923 forEach(type.split(' '), function(type) { 2924 removeHandler(type); 2925 if (MOUSE_EVENT_MAP[type]) { 2926 removeHandler(MOUSE_EVENT_MAP[type]); 2927 } 2928 }); 2929 } 2930} 2931 2932function jqLiteRemoveData(element, name) { 2933 var expandoId = element.ng339; 2934 var expandoStore = expandoId && jqCache[expandoId]; 2935 2936 if (expandoStore) { 2937 if (name) { 2938 delete expandoStore.data[name]; 2939 return; 2940 } 2941 2942 if (expandoStore.handle) { 2943 if (expandoStore.events.$destroy) { 2944 expandoStore.handle({}, '$destroy'); 2945 } 2946 jqLiteOff(element); 2947 } 2948 delete jqCache[expandoId]; 2949 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2950 } 2951} 2952 2953 2954function jqLiteExpandoStore(element, createIfNecessary) { 2955 var expandoId = element.ng339, 2956 expandoStore = expandoId && jqCache[expandoId]; 2957 2958 if (createIfNecessary && !expandoStore) { 2959 element.ng339 = expandoId = jqNextId(); 2960 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 2961 } 2962 2963 return expandoStore; 2964} 2965 2966 2967function jqLiteData(element, key, value) { 2968 if (jqLiteAcceptsData(element)) { 2969 2970 var isSimpleSetter = isDefined(value); 2971 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 2972 var massGetter = !key; 2973 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 2974 var data = expandoStore && expandoStore.data; 2975 2976 if (isSimpleSetter) { // data('key', value) 2977 data[key] = value; 2978 } else { 2979 if (massGetter) { // data() 2980 return data; 2981 } else { 2982 if (isSimpleGetter) { // data('key') 2983 // don't force creation of expandoStore if it doesn't exist yet 2984 return data && data[key]; 2985 } else { // mass-setter: data({key1: val1, key2: val2}) 2986 extend(data, key); 2987 } 2988 } 2989 } 2990 } 2991} 2992 2993function jqLiteHasClass(element, selector) { 2994 if (!element.getAttribute) return false; 2995 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2996 indexOf(" " + selector + " ") > -1); 2997} 2998 2999function jqLiteRemoveClass(element, cssClasses) { 3000 if (cssClasses && element.setAttribute) { 3001 forEach(cssClasses.split(' '), function(cssClass) { 3002 element.setAttribute('class', trim( 3003 (" " + (element.getAttribute('class') || '') + " ") 3004 .replace(/[\n\t]/g, " ") 3005 .replace(" " + trim(cssClass) + " ", " ")) 3006 ); 3007 }); 3008 } 3009} 3010 3011function jqLiteAddClass(element, cssClasses) { 3012 if (cssClasses && element.setAttribute) { 3013 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3014 .replace(/[\n\t]/g, " "); 3015 3016 forEach(cssClasses.split(' '), function(cssClass) { 3017 cssClass = trim(cssClass); 3018 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 3019 existingClasses += cssClass + ' '; 3020 } 3021 }); 3022 3023 element.setAttribute('class', trim(existingClasses)); 3024 } 3025} 3026 3027 3028function jqLiteAddNodes(root, elements) { 3029 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 3030 3031 if (elements) { 3032 3033 // if a Node (the most common case) 3034 if (elements.nodeType) { 3035 root[root.length++] = elements; 3036 } else { 3037 var length = elements.length; 3038 3039 // if an Array or NodeList and not a Window 3040 if (typeof length === 'number' && elements.window !== elements) { 3041 if (length) { 3042 for (var i = 0; i < length; i++) { 3043 root[root.length++] = elements[i]; 3044 } 3045 } 3046 } else { 3047 root[root.length++] = elements; 3048 } 3049 } 3050 } 3051} 3052 3053 3054function jqLiteController(element, name) { 3055 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 3056} 3057 3058function jqLiteInheritedData(element, name, value) { 3059 // if element is the document object work with the html element instead 3060 // this makes $(document).scope() possible 3061 if (element.nodeType == NODE_TYPE_DOCUMENT) { 3062 element = element.documentElement; 3063 } 3064 var names = isArray(name) ? name : [name]; 3065 3066 while (element) { 3067 for (var i = 0, ii = names.length; i < ii; i++) { 3068 if (isDefined(value = jqLite.data(element, names[i]))) return value; 3069 } 3070 3071 // If dealing with a document fragment node with a host element, and no parent, use the host 3072 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 3073 // to lookup parent controllers. 3074 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 3075 } 3076} 3077 3078function jqLiteEmpty(element) { 3079 jqLiteDealoc(element, true); 3080 while (element.firstChild) { 3081 element.removeChild(element.firstChild); 3082 } 3083} 3084 3085function jqLiteRemove(element, keepData) { 3086 if (!keepData) jqLiteDealoc(element); 3087 var parent = element.parentNode; 3088 if (parent) parent.removeChild(element); 3089} 3090 3091 3092function jqLiteDocumentLoaded(action, win) { 3093 win = win || window; 3094 if (win.document.readyState === 'complete') { 3095 // Force the action to be run async for consistent behavior 3096 // from the action's point of view 3097 // i.e. it will definitely not be in a $apply 3098 win.setTimeout(action); 3099 } else { 3100 // No need to unbind this handler as load is only ever called once 3101 jqLite(win).on('load', action); 3102 } 3103} 3104 3105////////////////////////////////////////// 3106// Functions which are declared directly. 3107////////////////////////////////////////// 3108var JQLitePrototype = JQLite.prototype = { 3109 ready: function(fn) { 3110 var fired = false; 3111 3112 function trigger() { 3113 if (fired) return; 3114 fired = true; 3115 fn(); 3116 } 3117 3118 // check if document is already loaded 3119 if (document.readyState === 'complete') { 3120 setTimeout(trigger); 3121 } else { 3122 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 3123 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 3124 // jshint -W064 3125 JQLite(window).on('load', trigger); // fallback to window.onload for others 3126 // jshint +W064 3127 } 3128 }, 3129 toString: function() { 3130 var value = []; 3131 forEach(this, function(e) { value.push('' + e);}); 3132 return '[' + value.join(', ') + ']'; 3133 }, 3134 3135 eq: function(index) { 3136 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3137 }, 3138 3139 length: 0, 3140 push: push, 3141 sort: [].sort, 3142 splice: [].splice 3143}; 3144 3145////////////////////////////////////////// 3146// Functions iterating getter/setters. 3147// these functions return self on setter and 3148// value on get. 3149////////////////////////////////////////// 3150var BOOLEAN_ATTR = {}; 3151forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3152 BOOLEAN_ATTR[lowercase(value)] = value; 3153}); 3154var BOOLEAN_ELEMENTS = {}; 3155forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3156 BOOLEAN_ELEMENTS[value] = true; 3157}); 3158var ALIASED_ATTR = { 3159 'ngMinlength': 'minlength', 3160 'ngMaxlength': 'maxlength', 3161 'ngMin': 'min', 3162 'ngMax': 'max', 3163 'ngPattern': 'pattern' 3164}; 3165 3166function getBooleanAttrName(element, name) { 3167 // check dom last since we will most likely fail on name 3168 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3169 3170 // booleanAttr is here twice to minimize DOM access 3171 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3172} 3173 3174function getAliasedAttrName(name) { 3175 return ALIASED_ATTR[name]; 3176} 3177 3178forEach({ 3179 data: jqLiteData, 3180 removeData: jqLiteRemoveData, 3181 hasData: jqLiteHasData, 3182 cleanData: jqLiteCleanData 3183}, function(fn, name) { 3184 JQLite[name] = fn; 3185}); 3186 3187forEach({ 3188 data: jqLiteData, 3189 inheritedData: jqLiteInheritedData, 3190 3191 scope: function(element) { 3192 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3193 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3194 }, 3195 3196 isolateScope: function(element) { 3197 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3198 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3199 }, 3200 3201 controller: jqLiteController, 3202 3203 injector: function(element) { 3204 return jqLiteInheritedData(element, '$injector'); 3205 }, 3206 3207 removeAttr: function(element, name) { 3208 element.removeAttribute(name); 3209 }, 3210 3211 hasClass: jqLiteHasClass, 3212 3213 css: function(element, name, value) { 3214 name = camelCase(name); 3215 3216 if (isDefined(value)) { 3217 element.style[name] = value; 3218 } else { 3219 return element.style[name]; 3220 } 3221 }, 3222 3223 attr: function(element, name, value) { 3224 var nodeType = element.nodeType; 3225 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3226 return; 3227 } 3228 var lowercasedName = lowercase(name); 3229 if (BOOLEAN_ATTR[lowercasedName]) { 3230 if (isDefined(value)) { 3231 if (!!value) { 3232 element[name] = true; 3233 element.setAttribute(name, lowercasedName); 3234 } else { 3235 element[name] = false; 3236 element.removeAttribute(lowercasedName); 3237 } 3238 } else { 3239 return (element[name] || 3240 (element.attributes.getNamedItem(name) || noop).specified) 3241 ? lowercasedName 3242 : undefined; 3243 } 3244 } else if (isDefined(value)) { 3245 element.setAttribute(name, value); 3246 } else if (element.getAttribute) { 3247 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3248 // some elements (e.g. Document) don't have get attribute, so return undefined 3249 var ret = element.getAttribute(name, 2); 3250 // normalize non-existing attributes to undefined (as jQuery) 3251 return ret === null ? undefined : ret; 3252 } 3253 }, 3254 3255 prop: function(element, name, value) { 3256 if (isDefined(value)) { 3257 element[name] = value; 3258 } else { 3259 return element[name]; 3260 } 3261 }, 3262 3263 text: (function() { 3264 getText.$dv = ''; 3265 return getText; 3266 3267 function getText(element, value) { 3268 if (isUndefined(value)) { 3269 var nodeType = element.nodeType; 3270 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3271 } 3272 element.textContent = value; 3273 } 3274 })(), 3275 3276 val: function(element, value) { 3277 if (isUndefined(value)) { 3278 if (element.multiple && nodeName_(element) === 'select') { 3279 var result = []; 3280 forEach(element.options, function(option) { 3281 if (option.selected) { 3282 result.push(option.value || option.text); 3283 } 3284 }); 3285 return result.length === 0 ? null : result; 3286 } 3287 return element.value; 3288 } 3289 element.value = value; 3290 }, 3291 3292 html: function(element, value) { 3293 if (isUndefined(value)) { 3294 return element.innerHTML; 3295 } 3296 jqLiteDealoc(element, true); 3297 element.innerHTML = value; 3298 }, 3299 3300 empty: jqLiteEmpty 3301}, function(fn, name) { 3302 /** 3303 * Properties: writes return selection, reads return first value 3304 */ 3305 JQLite.prototype[name] = function(arg1, arg2) { 3306 var i, key; 3307 var nodeCount = this.length; 3308 3309 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3310 // in a way that survives minification. 3311 // jqLiteEmpty takes no arguments but is a setter. 3312 if (fn !== jqLiteEmpty && 3313 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3314 if (isObject(arg1)) { 3315 3316 // we are a write, but the object properties are the key/values 3317 for (i = 0; i < nodeCount; i++) { 3318 if (fn === jqLiteData) { 3319 // data() takes the whole object in jQuery 3320 fn(this[i], arg1); 3321 } else { 3322 for (key in arg1) { 3323 fn(this[i], key, arg1[key]); 3324 } 3325 } 3326 } 3327 // return self for chaining 3328 return this; 3329 } else { 3330 // we are a read, so read the first child. 3331 // TODO: do we still need this? 3332 var value = fn.$dv; 3333 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3334 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3335 for (var j = 0; j < jj; j++) { 3336 var nodeValue = fn(this[j], arg1, arg2); 3337 value = value ? value + nodeValue : nodeValue; 3338 } 3339 return value; 3340 } 3341 } else { 3342 // we are a write, so apply to all children 3343 for (i = 0; i < nodeCount; i++) { 3344 fn(this[i], arg1, arg2); 3345 } 3346 // return self for chaining 3347 return this; 3348 } 3349 }; 3350}); 3351 3352function createEventHandler(element, events) { 3353 var eventHandler = function(event, type) { 3354 // jQuery specific api 3355 event.isDefaultPrevented = function() { 3356 return event.defaultPrevented; 3357 }; 3358 3359 var eventFns = events[type || event.type]; 3360 var eventFnsLength = eventFns ? eventFns.length : 0; 3361 3362 if (!eventFnsLength) return; 3363 3364 if (isUndefined(event.immediatePropagationStopped)) { 3365 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3366 event.stopImmediatePropagation = function() { 3367 event.immediatePropagationStopped = true; 3368 3369 if (event.stopPropagation) { 3370 event.stopPropagation(); 3371 } 3372 3373 if (originalStopImmediatePropagation) { 3374 originalStopImmediatePropagation.call(event); 3375 } 3376 }; 3377 } 3378 3379 event.isImmediatePropagationStopped = function() { 3380 return event.immediatePropagationStopped === true; 3381 }; 3382 3383 // Some events have special handlers that wrap the real handler 3384 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 3385 3386 // Copy event handlers in case event handlers array is modified during execution. 3387 if ((eventFnsLength > 1)) { 3388 eventFns = shallowCopy(eventFns); 3389 } 3390 3391 for (var i = 0; i < eventFnsLength; i++) { 3392 if (!event.isImmediatePropagationStopped()) { 3393 handlerWrapper(element, event, eventFns[i]); 3394 } 3395 } 3396 }; 3397 3398 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3399 // events on `element` 3400 eventHandler.elem = element; 3401 return eventHandler; 3402} 3403 3404function defaultHandlerWrapper(element, event, handler) { 3405 handler.call(element, event); 3406} 3407 3408function specialMouseHandlerWrapper(target, event, handler) { 3409 // Refer to jQuery's implementation of mouseenter & mouseleave 3410 // Read about mouseenter and mouseleave: 3411 // http://www.quirksmode.org/js/events_mouse.html#link8 3412 var related = event.relatedTarget; 3413 // For mousenter/leave call the handler if related is outside the target. 3414 // NB: No relatedTarget if the mouse left/entered the browser window 3415 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 3416 handler.call(target, event); 3417 } 3418} 3419 3420////////////////////////////////////////// 3421// Functions iterating traversal. 3422// These functions chain results into a single 3423// selector. 3424////////////////////////////////////////// 3425forEach({ 3426 removeData: jqLiteRemoveData, 3427 3428 on: function jqLiteOn(element, type, fn, unsupported) { 3429 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3430 3431 // Do not add event handlers to non-elements because they will not be cleaned up. 3432 if (!jqLiteAcceptsData(element)) { 3433 return; 3434 } 3435 3436 var expandoStore = jqLiteExpandoStore(element, true); 3437 var events = expandoStore.events; 3438 var handle = expandoStore.handle; 3439 3440 if (!handle) { 3441 handle = expandoStore.handle = createEventHandler(element, events); 3442 } 3443 3444 // http://jsperf.com/string-indexof-vs-split 3445 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3446 var i = types.length; 3447 3448 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 3449 var eventFns = events[type]; 3450 3451 if (!eventFns) { 3452 eventFns = events[type] = []; 3453 eventFns.specialHandlerWrapper = specialHandlerWrapper; 3454 if (type !== '$destroy' && !noEventListener) { 3455 addEventListenerFn(element, type, handle); 3456 } 3457 } 3458 3459 eventFns.push(fn); 3460 }; 3461 3462 while (i--) { 3463 type = types[i]; 3464 if (MOUSE_EVENT_MAP[type]) { 3465 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 3466 addHandler(type, undefined, true); 3467 } else { 3468 addHandler(type); 3469 } 3470 } 3471 }, 3472 3473 off: jqLiteOff, 3474 3475 one: function(element, type, fn) { 3476 element = jqLite(element); 3477 3478 //add the listener twice so that when it is called 3479 //you can remove the original function and still be 3480 //able to call element.off(ev, fn) normally 3481 element.on(type, function onFn() { 3482 element.off(type, fn); 3483 element.off(type, onFn); 3484 }); 3485 element.on(type, fn); 3486 }, 3487 3488 replaceWith: function(element, replaceNode) { 3489 var index, parent = element.parentNode; 3490 jqLiteDealoc(element); 3491 forEach(new JQLite(replaceNode), function(node) { 3492 if (index) { 3493 parent.insertBefore(node, index.nextSibling); 3494 } else { 3495 parent.replaceChild(node, element); 3496 } 3497 index = node; 3498 }); 3499 }, 3500 3501 children: function(element) { 3502 var children = []; 3503 forEach(element.childNodes, function(element) { 3504 if (element.nodeType === NODE_TYPE_ELEMENT) { 3505 children.push(element); 3506 } 3507 }); 3508 return children; 3509 }, 3510 3511 contents: function(element) { 3512 return element.contentDocument || element.childNodes || []; 3513 }, 3514 3515 append: function(element, node) { 3516 var nodeType = element.nodeType; 3517 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3518 3519 node = new JQLite(node); 3520 3521 for (var i = 0, ii = node.length; i < ii; i++) { 3522 var child = node[i]; 3523 element.appendChild(child); 3524 } 3525 }, 3526 3527 prepend: function(element, node) { 3528 if (element.nodeType === NODE_TYPE_ELEMENT) { 3529 var index = element.firstChild; 3530 forEach(new JQLite(node), function(child) { 3531 element.insertBefore(child, index); 3532 }); 3533 } 3534 }, 3535 3536 wrap: function(element, wrapNode) { 3537 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3538 var parent = element.parentNode; 3539 if (parent) { 3540 parent.replaceChild(wrapNode, element); 3541 } 3542 wrapNode.appendChild(element); 3543 }, 3544 3545 remove: jqLiteRemove, 3546 3547 detach: function(element) { 3548 jqLiteRemove(element, true); 3549 }, 3550 3551 after: function(element, newElement) { 3552 var index = element, parent = element.parentNode; 3553 newElement = new JQLite(newElement); 3554 3555 for (var i = 0, ii = newElement.length; i < ii; i++) { 3556 var node = newElement[i]; 3557 parent.insertBefore(node, index.nextSibling); 3558 index = node; 3559 } 3560 }, 3561 3562 addClass: jqLiteAddClass, 3563 removeClass: jqLiteRemoveClass, 3564 3565 toggleClass: function(element, selector, condition) { 3566 if (selector) { 3567 forEach(selector.split(' '), function(className) { 3568 var classCondition = condition; 3569 if (isUndefined(classCondition)) { 3570 classCondition = !jqLiteHasClass(element, className); 3571 } 3572 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3573 }); 3574 } 3575 }, 3576 3577 parent: function(element) { 3578 var parent = element.parentNode; 3579 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3580 }, 3581 3582 next: function(element) { 3583 return element.nextElementSibling; 3584 }, 3585 3586 find: function(element, selector) { 3587 if (element.getElementsByTagName) { 3588 return element.getElementsByTagName(selector); 3589 } else { 3590 return []; 3591 } 3592 }, 3593 3594 clone: jqLiteClone, 3595 3596 triggerHandler: function(element, event, extraParameters) { 3597 3598 var dummyEvent, eventFnsCopy, handlerArgs; 3599 var eventName = event.type || event; 3600 var expandoStore = jqLiteExpandoStore(element); 3601 var events = expandoStore && expandoStore.events; 3602 var eventFns = events && events[eventName]; 3603 3604 if (eventFns) { 3605 // Create a dummy event to pass to the handlers 3606 dummyEvent = { 3607 preventDefault: function() { this.defaultPrevented = true; }, 3608 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3609 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3610 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3611 stopPropagation: noop, 3612 type: eventName, 3613 target: element 3614 }; 3615 3616 // If a custom event was provided then extend our dummy event with it 3617 if (event.type) { 3618 dummyEvent = extend(dummyEvent, event); 3619 } 3620 3621 // Copy event handlers in case event handlers array is modified during execution. 3622 eventFnsCopy = shallowCopy(eventFns); 3623 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3624
3625 forEach(eventFnsCopy, function(fn) { 3626 if (!dummyEvent.isImmediatePropagationStopped()) { 3627 fn.apply(element, handlerArgs); 3628 } 3629 }); 3630 } 3631 } 3632}, function(fn, name) { 3633 /** 3634 * chaining functions 3635 */ 3636 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3637 var value; 3638 3639 for (var i = 0, ii = this.length; i < ii; i++) { 3640 if (isUndefined(value)) { 3641 value = fn(this[i], arg1, arg2, arg3); 3642 if (isDefined(value)) { 3643 // any function which returns a value needs to be wrapped 3644 value = jqLite(value); 3645 } 3646 } else { 3647 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3648 } 3649 } 3650 return isDefined(value) ? value : this; 3651 }; 3652 3653 // bind legacy bind/unbind to on/off 3654 JQLite.prototype.bind = JQLite.prototype.on; 3655 JQLite.prototype.unbind = JQLite.prototype.off; 3656}); 3657 3658 3659// Provider for private $$jqLite service 3660function $$jqLiteProvider() { 3661 this.$get = function $$jqLite() { 3662 return extend(JQLite, { 3663 hasClass: function(node, classes) { 3664 if (node.attr) node = node[0]; 3665 return jqLiteHasClass(node, classes); 3666 }, 3667 addClass: function(node, classes) { 3668 if (node.attr) node = node[0]; 3669 return jqLiteAddClass(node, classes); 3670 }, 3671 removeClass: function(node, classes) { 3672 if (node.attr) node = node[0]; 3673 return jqLiteRemoveClass(node, classes); 3674 } 3675 }); 3676 }; 3677} 3678 3679/** 3680 * Computes a hash of an 'obj'. 3681 * Hash of a: 3682 * string is string 3683 * number is number as string 3684 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3685 * that is also assigned to the $$hashKey property of the object. 3686 * 3687 * @param obj 3688 * @returns {string} hash string such that the same input will have the same hash string. 3689 * The resulting string key is in 'type:hashKey' format. 3690 */ 3691function hashKey(obj, nextUidFn) { 3692 var key = obj && obj.$$hashKey; 3693 3694 if (key) { 3695 if (typeof key === 'function') { 3696 key = obj.$$hashKey(); 3697 } 3698 return key; 3699 } 3700 3701 var objType = typeof obj; 3702 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3703 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3704 } else { 3705 key = objType + ':' + obj; 3706 } 3707 3708 return key; 3709} 3710 3711/** 3712 * HashMap which can use objects as keys 3713 */ 3714function HashMap(array, isolatedUid) { 3715 if (isolatedUid) { 3716 var uid = 0; 3717 this.nextUid = function() { 3718 return ++uid; 3719 }; 3720 } 3721 forEach(array, this.put, this); 3722} 3723HashMap.prototype = { 3724 /** 3725 * Store key value pair 3726 * @param key key to store can be any type 3727 * @param value value to store can be any type 3728 */ 3729 put: function(key, value) { 3730 this[hashKey(key, this.nextUid)] = value; 3731 }, 3732 3733 /** 3734 * @param key 3735 * @returns {Object} the value for the key 3736 */ 3737 get: function(key) { 3738 return this[hashKey(key, this.nextUid)]; 3739 }, 3740 3741 /** 3742 * Remove the key/value pair 3743 * @param key 3744 */ 3745 remove: function(key) { 3746 var value = this[key = hashKey(key, this.nextUid)]; 3747 delete this[key]; 3748 return value; 3749 } 3750}; 3751 3752var $$HashMapProvider = [function() { 3753 this.$get = [function() { 3754 return HashMap; 3755 }]; 3756}]; 3757 3758/** 3759 * @ngdoc function 3760 * @module ng 3761 * @name angular.injector 3762 * @kind function 3763 * 3764 * @description 3765 * Creates an injector object that can be used for retrieving services as well as for 3766 * dependency injection (see {@link guide/di dependency injection}). 3767 * 3768 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 3769 * {@link angular.module}. The `ng` module must be explicitly added. 3770 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3771 * disallows argument name annotation inference. 3772 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3773 * 3774 * @example 3775 * Typical usage 3776 * ```js 3777 * // create an injector 3778 * var $injector = angular.injector(['ng']); 3779 * 3780 * // use the injector to kick off your application 3781 * // use the type inference to auto inject arguments, or use impl
3781icit injection 3782 * $injector.invoke(function($rootScope, $compile, $document) { 3783 * $compile($document)($rootScope); 3784 * $rootScope.$digest(); 3785 * }); 3786 * ``` 3787 * 3788 * Sometimes you want to get access to the injector of a currently running Angular app 3789 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3790 * application has been bootstrapped. You can do this using the extra `injector()` added 3791 * to JQuery/jqLite elements. See {@link angular.element}. 3792 * 3793 * *This is fairly rare but could be the case if a third party library is injecting the 3794 * markup.* 3795 * 3796 * In the following example a new block of HTML containing a `ng-controller` 3797 * directive is added to the end of the document body by JQuery. We then compile and link 3798 * it into the current AngularJS scope. 3799 * 3800 * ```js 3801 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3802 * $(document.body).append($div); 3803 * 3804 * angular.element(document).injector().invoke(function($compile) { 3805 * var scope = angular.element($div).scope(); 3806 * $compile($div)(scope); 3807 * }); 3808 * ``` 3809 */ 3810 3811 3812/** 3813 * @ngdoc module 3814 * @name auto 3815 * @description 3816 * 3817 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3818 */ 3819 3820var ARROW_ARG = /^([^\(]+?)=>/; 3821var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 3822var FN_ARG_SPLIT = /,/; 3823var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3824var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3825var $injectorMinErr = minErr('$injector'); 3826 3827function extractArgs(fn) { 3828 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3829 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 3830 return args; 3831} 3832 3833function anonFn(fn) { 3834 // For anonymous functions, showing at the very least the function signature can help in 3835 // debugging. 3836 var args = extractArgs(fn); 3837 if (args) { 3838 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3839 } 3840 return 'fn'; 3841} 3842 3843function annotate(fn, strictDi, name) { 3844 var $inject, 3845 argDecl, 3846 last; 3847 3848 if (typeof fn === 'function') { 3849 if (!($inject = fn.$inject)) { 3850 $inject = []; 3851 if (fn.length) { 3852 if (strictDi) { 3853 if (!isString(name) || !name) { 3854 name = fn.name || anonFn(fn); 3855 } 3856 throw $injectorMinErr('strictdi', 3857 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3858 } 3859 argDecl = extractArgs(fn); 3860 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3861 arg.replace(FN_ARG, function(all, underscore, name) { 3862 $inject.push(name); 3863 }); 3864 }); 3865 } 3866 fn.$inject = $inject; 3867 } 3868 } else if (isArray(fn)) { 3869 last = fn.length - 1; 3870 assertArgFn(fn[last], 'fn'); 3871 $inject = fn.slice(0, last); 3872 } else { 3873 assertArgFn(fn, 'fn', true); 3874 } 3875 return $inject; 3876} 3877 3878/////////////////////////////////////// 3879 3880/** 3881 * @ngdoc service 3882 * @name $injector 3883 * 3884 * @description 3885 * 3886 * `$injector` is used to retrieve object instances as defined by 3887 * {@link auto.$provide provider}, instantiate types, invoke methods, 3888 * and load modules. 3889 * 3890 * The following always holds true: 3891 * 3892 * ```js 3893 * var $injector = angular.injector(); 3894 * expect($injector.get('$injector')).toBe($injector); 3895 * expect($injector.invoke(function($injector) { 3896 * return $injector; 3897 * })).toBe($injector); 3898 * ``` 3899 * 3900 * # Injection Function Annotation 3901 * 3902 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3903 * following are all valid ways of annotating function with injection arguments and are equivalent. 3904 * 3905 * ```js 3906 * // inferred (only works if code not minified/obfuscated) 3907 * $injector.invoke(function(serviceA){}); 3908 * 3909 * // annotated 3910 * function explicit(serviceA) {}; 3911 * explicit.$inject = ['serviceA']; 3912 * $injector.invoke(explicit); 3913 * 3914 * // inline 3915 * $injector.invoke(['serviceA', function(serviceA){}]); 3916 * ``` 3917 * 3918 * ## Inference 3919 * 3920 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3921 * can then be parsed and the function arguments can be extracted. This method of discovering 3922 * annotations is disallowed when the injector is in strict mode. 3923 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3924 * argument names. 3925 * 3926 * ## `$inject` Annotation 3927 * By adding an `$inject` property onto a function the injection parameters can be specified. 3928 * 3929 * ## Inline 3930 * As an array of injection names, where the last item in the array is the function to call. 3931 */ 3932 3933/** 3934 * @ngdoc method 3935 * @name $injector#get 3936 * 3937 * @description 3938 * Return an instance of the service. 3939 * 3940 * @param {string} name The name of the instance to retrieve. 3941 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 3942 * @return {*} The instance. 3943 */ 3944 3945/** 3946 * @ngdoc method 3947 * @name $injector#invoke 3948 * 3949 * @description 3950 * Invoke the method and supply the method arguments from the `$injector`. 3951 * 3952 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 3953 * injected according to the {@link guide/di $inject Annotation} rules. 3954 * @param {Object=} self The `this` for the invoked method. 3955 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3956 * object first, before the `$injector` is consulted. 3957 * @returns {*} the value returned by the invoked `fn` function. 3958 */ 3959 3960/** 3961 * @ngdoc method 3962 * @name $injector#has 3963 * 3964 * @description 3965 * Allows the user to query if the particular service exists. 3966 * 3967 * @param {string} name Name of the service to query. 3968 * @returns {boolean} `true` if injector has given service. 3969 */ 3970 3971/** 3972 * @ngdoc method 3973 * @name $injector#instantiate 3974 * @description 3975 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3976 * operator, and supplies all of the arguments to the constructor function as specified by the 3977 * constructor annotation. 3978 * 3979 * @param {Function} Type Annotated constructor function. 3980 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3981 * object first, before the `$injector` is consulted. 3982 * @returns {Object} new instance of `Type`. 3983 */ 3984 3985/** 3986 * @ngdoc method 3987 * @name $injector#annotate 3988 * 3989 * @description 3990 * Returns an array of service names which the function is requesting for injection. This API is 3991 * used by the injector to determine which services need to be injected into the function when the 3992 * function is invoked. There are three ways in which the function can be annotated with the needed 3993 * dependencies. 3994 * 3995 * # Argument names 3996 * 3997 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3998 * by converting the function into a string using `toString()` method and extracting the argument 3999 * names. 4000 * ```js 4001 * // Given 4002 * function MyController($scope, $route) { 4003 * // ... 4004 * } 4005 * 4006 * // Then 4007 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4008 * ``` 4009 * 4010 * You can disallow this method by using strict injection mode. 4011 * 4012 * This method does not work with code minification / obfuscation. For this reason the following 4013 * annotation strategies are supported. 4014 * 4015 * # The `$inject` property 4016 * 4017 * If a function has an `$inject` property and its value is an array of strings, then the strings 4018 * represent names of services to be injected into the function. 4019 * ```js 4020 * // Given 4021 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 4022 * // ... 4023 * } 4024 * // Define function dependencies 4025 * MyController['$inject'] = ['$scope', '$route']; 4026 * 4027 * // Then 4028 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4029 * ``` 4030 * 4031 * # The array notation 4032 * 4033 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 4034 * is very inconvenient. In these situations using the array notation to specify the dependencies in 4035 * a way that survives minification is a better choice: 4036 * 4037 * ```js 4038 * // We wish to write this (not minification / obfuscation safe) 4039 * injector.invoke(function($compile, $rootScope) { 4040 * // ... 4041 * }); 4042 * 4043 * // We are forced to write break inlining 4044 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 4045 * // ... 4046 * }; 4047 * tmpFn.$inject = ['$compile', '$rootScope']; 4048 * injector.invoke(tmpFn); 4049 * 4050 * // To better support inline function the inline annotation is supported 4051 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 4052 * // ... 4053 * }]); 4054 * 4055 * // Therefore 4056 * expect(injector.annotate( 4057 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 4058 * ).toEqual(['$compile', '$rootScope']); 4059 * ``` 4060 * 4061 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 4062 * be retrieved as described above. 4063 * 4064 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 4065 * 4066 * @returns {Array.<string>} The names of the services which the function requires. 4067 */ 4068 4069 4070 4071 4072/** 4073 * @ngdoc service 4074 * @name $provide 4075 * 4076 * @description 4077 * 4078 * The {@link auto.$provide $provide} service has a number of methods for registering components 4079 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 4080 * {@link angular.Module}. 4081 * 4082 * An Angular **service** is a singleton object created by a **service factory**. These **service 4083 * factories** are functions which, in turn, are created by a **service provider**. 4084 * The **service providers** are constructor functions. When instantiated they must contain a 4085 * property called `$get`, which holds the **service factory** function. 4086 * 4087 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 4088 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 4089 * function to get the instance of the **service**. 4090 * 4091 * Often services have no configuration options and there is no need to add methods to the service 4092 * provider. The provider will be no more than a constructor function with a `$get` property. For 4093 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 4094 * services without specifying a provider. 4095 * 4096 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 4097 * {@link auto.$injector $injector} 4098 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 4099 * providers and services. 4100 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 4101 * services, not providers. 4102 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 4103 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 4104 * given factory function. 4105 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 4106 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 4107 * a new object using the given constructor function. 4108 * 4109 * See the individual methods for more information and examples. 4110 */ 4111 4112/** 4113 * @ngdoc method 4114 * @name $provide#provider 4115 * @description 4116 * 4117 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4118 * are constructor functions, whose instances are responsible for "providing" a factory for a 4119 * service. 4120 * 4121 * Service provider names start with the name of the service they provide followed by `Provider`. 4122 * For example, the {@link ng.$log $log} service has a provider called 4123 * {@link ng.$logProvider $logProvider}. 4124 * 4125 * Service provider objects can have additional methods which allow configuration of the provider 4126 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4127 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4128 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4129 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4130 * console or not. 4131 * 4132 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4133 'Provider'` key. 4134 * @param {(Object|function())} provider If the provider is: 4135 * 4136 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4137 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4138 * - `Constructor`: a new instance of the provider will be created using 4139 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4140 * 4141 * @returns {Object} registered provider instance 4142 4143 * @example 4144 * 4145 * The following example shows how to create a simple event tracking service and register it using 4146 * {@link auto.$provide#provider $provide.provider()}. 4147 * 4148 * ```js 4149 * // Define the eventTracker provider 4150 * function EventTrackerProvider() { 4151 * var trackingUrl = '/track'; 4152 * 4153 * // A provider method for configuring where the tracked events should been saved 4154 * this.setTrackingUrl = function(url) { 4155 * trackingUrl = url; 4156 * }; 4157 * 4158 * // The service factory function 4159 * this.$get = ['$http', function($http) { 4160 * var trackedEvents = {}; 4161 * return { 4162 * // Call this to track an event 4163 * event: function(event) { 4164 * var count = trackedEvents[event] || 0; 4165 * count += 1; 4166 * trackedEvents[event] = count; 4167 * return count; 4168 * }, 4169 * // Call this to save the tracked events to the trackingUrl 4170 * save: function() { 4171 * $http.post(trackingUrl, trackedEvents); 4172 * } 4173 * }; 4174 * }]; 4175 * } 4176 * 4177 * describe('eventTracker', function() { 4178 * var postSpy; 4179 * 4180 * beforeEach(module(function($provide) { 4181 * // Register the eventTracker provider 4182 * $provide.provider('eventTracker', EventTrackerProvider); 4183 * })); 4184 * 4185 * beforeEach(module(function(eventTrackerProvider) { 4186 * // Configure eventTracker provider 4187 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4188 * })); 4189 * 4190 * it('tracks events', inject(function(eventTracker) { 4191 * expect(eventTracker.event('login')).toEqual(1); 4192 * expect(eventTracker.event('login')).toEqual(2); 4193 * })); 4194 * 4195 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4196 * postSpy = spyOn($http, 'post'); 4197 * eventTracker.event('login'); 4198 * eventTracker.save(); 4199 * expect(postSpy).toHaveBeenCalled(); 4200 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4201 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4202 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4203 * })); 4204 * }); 4205 * ``` 4206 */ 4207 4208/** 4209 * @ngdoc method 4210 * @name $provide#factory 4211 * @description 4212 * 4213 * Register a **service factory**, which will be called to return the service instance. 4214 * This is short for registering a service where its provider consists of only a `$get` property, 4215 * which is the given service factory function. 4216 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4217 * configure your service in a provider. 4218 * 4219 * @param {string} name The name of the instance. 4220 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4221 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4222 * @returns {Object} registered provider instance 4223 * 4224 * @example 4225 * Here is an example of registering a service 4226 * ```js 4227 * $provide.factory('ping', ['$http', function($http) { 4228 * return function ping() { 4229 * return $http.send('/ping'); 4230 * }; 4231 * }]); 4232 * ``` 4233 * You would then inject and use this service like this: 4234 * ```js 4235 * someModule.controller('Ctrl', ['ping', function(ping) { 4236 * ping(); 4237 * }]); 4238 * ``` 4239 */ 4240 4241 4242/** 4243 * @ngdoc method 4244 * @name $provide#service 4245 * @description 4246 * 4247 * Register a **service constructor**, which will be invoked with `new` to create the service 4248 * instance. 4249 * This is short for registering a service where its provider's `$get` property is the service 4250 * constructor function that will be used to instantiate the service instance. 4251 * 4252 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4253 * as a type/class. 4254 * 4255 * @param {string} name The name of the instance. 4256 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4257 * that will be instantiated. 4258 * @returns {Object} registered provider instance 4259 * 4260 * @example 4261 * Here is an example of registering a service using 4262 * {@link auto.$provide#service $provide.service(class)}. 4263 * ```js 4264 * var Ping = function($http) { 4265 * this.$http = $http; 4266 * }; 4267 * 4268 * Ping.$inject = ['$http']; 4269 * 4270 * Ping.prototype.send = function() { 4271 * return this.$http.get('/ping'); 4272 * }; 4273 * $provide.service('ping', Ping); 4274 * ``` 4275 * You would then inject and use this service like this: 4276 * ```js 4277 * someModule.controller('Ctrl', ['ping', function(ping) { 4278 * ping.send(); 4279 * }]); 4280 * ``` 4281 */ 4282 4283 4284/** 4285 * @ngdoc method 4286 * @name $provide#value 4287 * @description 4288 * 4289 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4290 * number, an array, an object or a function. This is short for registering a service where its 4291 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4292 * service**. 4293 * 4294 * Value services are similar to constant services, except that they cannot be injected into a 4295 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4296 * an Angular 4297 * {@link auto.$provide#decorator decorator}. 4298 * 4299 * @param {string} name The name of the instance. 4300 * @param {*} value The value. 4301 * @returns {Object} registered provider instance 4302 * 4303 * @example 4304 * Here are some examples of creating value services. 4305 * ```js 4306 * $provide.value('ADMIN_USER', 'admin'); 4307 *
4308 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4309 * 4310 * $provide.value('halfOf', function(value) { 4311 * return value / 2; 4312 * }); 4313 * ``` 4314 */ 4315 4316 4317/** 4318 * @ngdoc method 4319 * @name $provide#constant 4320 * @description 4321 * 4322 * Register a **constant service**, such as a string, a number, an array, an object or a function, 4323 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 4324 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4325 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4326 * 4327 * @param {string} name The name of the constant. 4328 * @param {*} value The constant value. 4329 * @returns {Object} registered instance 4330 * 4331 * @example 4332 * Here a some examples of creating constants: 4333 * ```js 4334 * $provide.constant('SHARD_HEIGHT', 306); 4335 * 4336 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4337 * 4338 * $provide.constant('double', function(value) { 4339 * return value * 2; 4340 * }); 4341 * ``` 4342 */ 4343 4344 4345/** 4346 * @ngdoc method 4347 * @name $provide#decorator 4348 * @description 4349 * 4350 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4351 * intercepts the creation of a service, allowing it to override or modify the behavior of the 4352 * service. The object returned by the decorator may be the original service, or a new service 4353 * object which replaces or wraps and delegates to the original service. 4354 * 4355 * @param {string} name The name of the service to decorate. 4356 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4357 * instantiated and should return the decorated service instance. The function is called using 4358 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4359 * Local injection arguments: 4360 * 4361 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4362 * decorated or delegated to. 4363 * 4364 * @example 4365 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4366 * calls to {@link ng.$log#error $log.warn()}. 4367 * ```js 4368 * $provide.decorator('$log', ['$delegate', function($delegate) { 4369 * $delegate.warn = $delegate.error; 4370 * return $delegate; 4371 * }]); 4372 * ``` 4373 */ 4374 4375 4376function createInjector(modulesToLoad, strictDi) { 4377 strictDi = (strictDi === true); 4378 var INSTANTIATING = {}, 4379 providerSuffix = 'Provider', 4380 path = [], 4381 loadedModules = new HashMap([], true), 4382 providerCache = { 4383 $provide: { 4384 provider: supportObject(provider), 4385 factory: supportObject(factory), 4386 service: supportObject(service), 4387 value: supportObject(value), 4388 constant: supportObject(constant), 4389 decorator: decorator 4390 } 4391 }, 4392 providerInjector = (providerCache.$injector = 4393 createInternalInjector(providerCache, function(serviceName, caller) { 4394 if (angular.isString(caller)) { 4395 path.push(caller); 4396 } 4397 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4398 })), 4399 instanceCache = {}, 4400 protoInstanceInjector = 4401 createInternalInjector(instanceCache, function(serviceName, caller) { 4402 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4403 return instanceInjector.invoke( 4404 provider.$get, provider, undefined, serviceName); 4405 }), 4406 instanceInjector = protoInstanceInjector; 4407 4408 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 4409 var runBlocks = loadModules(modulesToLoad); 4410 instanceInjector = protoInstanceInjector.get('$injector'); 4411 instanceInjector.strictDi = strictDi; 4412 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 4413 4414 return instanceInjector; 4415 4416 //////////////////////////////////// 4417 // $provider 4418 //////////////////////////////////// 4419 4420 function supportObject(delegate) { 4421 return function(key, value) { 4422 if (isObject(key)) {
vendor: 10,531 bytes, lines 4423-4705
4423 forEach(key, reverseParams(delegate)); 4424 } else { 4425 return delegate(key, value); 4426 } 4427 }; 4428 } 4429 4430 function provider(name, provider_) { 4431 assertNotHasOwnProperty(name, 'service'); 4432 if (isFunction(provider_) || isArray(provider_)) { 4433 provider_ = providerInjector.instantiate(provider_); 4434 } 4435 if (!provider_.$get) { 4436 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4437 } 4438 return providerCache[name + providerSuffix] = provider_; 4439 } 4440 4441 function enforceReturnValue(name, factory) { 4442 return function enforcedReturnValue() { 4443 var result = instanceInjector.invoke(factory, this); 4444 if (isUndefined(result)) { 4445 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4446 } 4447 return result; 4448 }; 4449 } 4450 4451 function factory(name, factoryFn, enforce) { 4452 return provider(name, { 4453 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4454 }); 4455 } 4456 4457 function service(name, constructor) { 4458 return factory(name, ['$injector', function($injector) { 4459 return $injector.instantiate(constructor); 4460 }]); 4461 } 4462 4463 function value(name, val) { return factory(name, valueFn(val), false); } 4464 4465 function constant(name, value) { 4466 assertNotHasOwnProperty(name, 'constant'); 4467 providerCache[name] = value; 4468 instanceCache[name] = value; 4469 } 4470 4471 function decorator(serviceName, decorFn) { 4472 var origProvider = providerInjector.get(serviceName + providerSuffix), 4473 orig$get = origProvider.$get; 4474 4475 origProvider.$get = function() { 4476 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4477 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4478 }; 4479 } 4480 4481 //////////////////////////////////// 4482 // Module Loading 4483 //////////////////////////////////// 4484 function loadModules(modulesToLoad) { 4485 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 4486 var runBlocks = [], moduleFn; 4487 forEach(modulesToLoad, function(module) { 4488 if (loadedModules.get(module)) return; 4489 loadedModules.put(module, true); 4490 4491 function runInvokeQueue(queue) { 4492 var i, ii; 4493 for (i = 0, ii = queue.length; i < ii; i++) { 4494 var invokeArgs = queue[i], 4495 provider = providerInjector.get(invokeArgs[0]); 4496 4497 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4498 } 4499 } 4500 4501 try { 4502 if (isString(module)) { 4503 moduleFn = angularModule(module); 4504 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4505 runInvokeQueue(moduleFn._invokeQueue); 4506 runInvokeQueue(moduleFn._configBlocks); 4507 } else if (isFunction(module)) { 4508 runBlocks.push(providerInjector.invoke(module)); 4509 } else if (isArray(module)) { 4510 runBlocks.push(providerInjector.invoke(module)); 4511 } else { 4512 assertArgFn(module, 'module'); 4513 } 4514 } catch (e) { 4515 if (isArray(module)) { 4516 module = module[module.length - 1]; 4517 } 4518 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4519 // Safari & FF's stack traces don't contain error.message content 4520 // unlike those of Chrome and IE 4521 // So if stack doesn't contain message, we create a new string that contains both. 4522 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4523 /* jshint -W022 */ 4524 e = e.message + '\n' + e.stack; 4525 } 4526 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4527 module, e.stack || e.message || e); 4528 } 4529 }); 4530 return runBlocks; 4531 } 4532 4533 //////////////////////////////////// 4534 // internal Injector 4535 //////////////////////////////////// 4536 4537 function createInternalInjector(cache, factory) { 4538 4539 function getService(serviceName, caller) { 4540 if (cache.hasOwnProperty(serviceName)) { 4541 if (cache[serviceName] === INSTANTIATING) { 4542 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4543 serviceName + ' <- ' + path.join(' <- ')); 4544 } 4545 return cache[serviceName]; 4546 } else { 4547 try { 4548 path.unshift(serviceName); 4549 cache[serviceName] = INSTANTIATING; 4550 return cache[serviceName] = factory(serviceName, caller); 4551 } catch (err) { 4552 if (cache[serviceName] === INSTANTIATING) { 4553 delete cache[serviceName]; 4554 } 4555 throw err; 4556 } finally { 4557 path.shift(); 4558 } 4559 } 4560 } 4561 4562 4563 function injectionArgs(fn, locals, serviceName) { 4564 var args = [], 4565 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 4566 4567 for (var i = 0, length = $inject.length; i < length; i++) { 4568 var key = $inject[i]; 4569 if (typeof key !== 'string') { 4570 throw $injectorMinErr('itkn', 4571 'Incorrect injection token! Expected service name as string, got {0}', key); 4572 } 4573 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 4574 getService(key, serviceName)); 4575 } 4576 return args; 4577 } 4578 4579 function isClass(func) { 4580 // IE 9-11 do not support classes and IE9 leaks with the code below. 4581 if (msie <= 11) { 4582 return false; 4583 } 4584 // Workaround for MS Edge. 4585 // Check https://connect.microsoft.com/IE/Feedback/Details/2211653 4586 return typeof func === 'function' 4587 && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func)); 4588 } 4589 4590 function invoke(fn, self, locals, serviceName) { 4591 if (typeof locals === 'string') { 4592 serviceName = locals; 4593 locals = null; 4594 } 4595 4596 var args = injectionArgs(fn, locals, serviceName); 4597 if (isArray(fn)) { 4598 fn = fn[fn.length - 1]; 4599 } 4600 4601 if (!isClass(fn)) { 4602 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4603 // #5388 4604 return fn.apply(self, args); 4605 } else { 4606 args.unshift(null); 4607 /*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here. 4608 return new (Function.prototype.bind.apply(fn, args)); 4609 /*jshint +W058 */ 4610 } 4611 } 4612 4613 4614 function instantiate(Type, locals, serviceName) { 4615 // Check if Type is annotated and use just the given function at n-1 as parameter 4616 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4617 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 4618 var args = injectionArgs(Type, locals, serviceName); 4619 // Empty object at position 0 is ignored for invocation with `new`, but required. 4620 args.unshift(null); 4621 /*jshint -W058 */ // Applying a constructor without immediate parentheses is the point here. 4622 return new (Function.prototype.bind.apply(ctor, args)); 4623 /*jshint +W058 */ 4624 } 4625 4626 4627 return { 4628 invoke: invoke, 4629 instantiate: instantiate, 4630 get: getService, 4631 annotate: createInjector.$$annotate, 4632 has: function(name) { 4633 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4634 } 4635 }; 4636 } 4637} 4638 4639createInjector.$$annotate = annotate; 4640 4641/** 4642 * @ngdoc provider 4643 * @name $anchorScrollProvider 4644 * 4645 * @description 4646 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4647 * {@link ng.$location#hash $location.hash()} changes. 4648 */ 4649function $AnchorScrollProvider() { 4650 4651 var autoScrollingEnabled = true; 4652 4653 /** 4654 * @ngdoc method 4655 * @name $anchorScrollProvider#disableAutoScrolling 4656 * 4657 * @description 4658 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4659 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4660 * Use this method to disable automatic scrolling. 4661 * 4662 * If automatic scrolling is disabled, one must explicitly call 4663 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4664 * current hash. 4665 */ 4666 this.disableAutoScrolling = function() { 4667 autoScrollingEnabled = false; 4668 }; 4669 4670 /** 4671 * @ngdoc service 4672 * @name $anchorScroll 4673 * @kind function 4674 * @requires $window 4675 * @requires $location 4676 * @requires $rootScope 4677 * 4678 * @description 4679 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4680 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4681 * in the 4682 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document). 4683 * 4684 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4685 * match any anchor whenever it changes. This can be disabled by calling 4686 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4687 * 4688 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4689 * vertical scroll-offset (either fixed or dynamic). 4690 * 4691 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4692 * {@link ng.$location#hash $location.hash()} will be used. 4693 * 4694 * @property {(number|function|jqLite)} yOffset 4695 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4696 * positioned elements at the top of the page, such as navbars, headers etc. 4697 * 4698 * `yOffset` can be specified in various ways: 4699 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4700 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4701 * a number representing the offset (in pixels).<br /><br /> 4702 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4703 * the top of the page to the element's bottom will be used as offset.<br /> 4704 * **Note**: The element will be taken into account only as long as its `position` is set to 4705 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adju
4705st 4706 * their height and/or positioning according to the viewport's size. 4707 * 4708 * <br /> 4709 * <div class="alert alert-warning"> 4710 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4711 * not some child element. 4712 * </div> 4713 * 4714 * @example 4715 <example module="anchorScrollExample"> 4716 <file name="index.html"> 4717 <div id="scrollArea" ng-controller="ScrollController"> 4718 <a ng-click="gotoBottom()">Go to bottom</a> 4719 <a id="bottom"></a> You're at the bottom! 4720 </div> 4721 </file> 4722 <file name="script.js"> 4723 angular.module('anchorScrollExample', []) 4724 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4725 function ($scope, $location, $anchorScroll) { 4726 $scope.gotoBottom = function() { 4727 // set the location.hash to the id of 4728 // the element you wish to scroll to. 4729 $location.hash('bottom'); 4730 4731 // call $anchorScroll() 4732 $anchorScroll(); 4733 }; 4734 }]); 4735 </file> 4736 <file name="style.css"> 4737 #scrollArea { 4738 height: 280px; 4739 overflow: auto; 4740 } 4741 4742 #bottom { 4743 display: block; 4744 margin-top: 2000px; 4745 } 4746 </file> 4747 </example> 4748 * 4749 * <hr /> 4750 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4751 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4752 * 4753 * @example 4754 <example module="anchorScrollOffsetExample"> 4755 <file name="index.html"> 4756 <div class="fixed-header" ng-controller="headerCtrl"> 4757 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4758 Go to anchor {{x}} 4759 </a> 4760 </div> 4761 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4762 Anchor {{x}} of 5 4763 </div> 4764 </file> 4765 <file name="script.js"> 4766 angular.module('anchorScrollOffsetExample', []) 4767 .run(['$anchorScroll', function($anchorScroll) { 4768 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4769 }]) 4770 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4771 function ($anchorScroll, $location, $scope) {
vendor: 10,461 bytes, lines 4772-5097
4772 $scope.gotoAnchor = function(x) { 4773 var newHash = 'anchor' + x; 4774 if ($location.hash() !== newHash) { 4775 // set the $location.hash to `newHash` and 4776 // $anchorScroll will automatically scroll to it 4777 $location.hash('anchor' + x); 4778 } else { 4779 // call $anchorScroll() explicitly, 4780 // since $location.hash hasn't changed 4781 $anchorScroll(); 4782 } 4783 }; 4784 } 4785 ]); 4786 </file> 4787 <file name="style.css"> 4788 body { 4789 padding-top: 50px; 4790 } 4791 4792 .anchor { 4793 border: 2px dashed DarkOrchid; 4794 padding: 10px 10px 200px 10px; 4795 } 4796 4797 .fixed-header { 4798 background-color: rgba(0, 0, 0, 0.2); 4799 height: 50px; 4800 position: fixed; 4801 top: 0; left: 0; right: 0; 4802 } 4803 4804 .fixed-header > a { 4805 display: inline-block; 4806 margin: 5px 15px; 4807 } 4808 </file> 4809 </example> 4810 */ 4811 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4812 var document = $window.document; 4813 4814 // Helper function to get first anchor from a NodeList 4815 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4816 // and working in all supported browsers.) 4817 function getFirstAnchor(list) { 4818 var result = null; 4819 Array.prototype.some.call(list, function(element) { 4820 if (nodeName_(element) === 'a') { 4821 result = element; 4822 return true; 4823 } 4824 }); 4825 return result; 4826 } 4827 4828 function getYOffset() { 4829 4830 var offset = scroll.yOffset; 4831 4832 if (isFunction(offset)) { 4833 offset = offset(); 4834 } else if (isElement(offset)) { 4835 var elem = offset[0]; 4836 var style = $window.getComputedStyle(elem); 4837 if (style.position !== 'fixed') { 4838 offset = 0; 4839 } else { 4840 offset = elem.getBoundingClientRect().bottom; 4841 } 4842 } else if (!isNumber(offset)) { 4843 offset = 0; 4844 } 4845 4846 return offset; 4847 } 4848 4849 function scrollTo(elem) { 4850 if (elem) { 4851 elem.scrollIntoView(); 4852 4853 var offset = getYOffset(); 4854 4855 if (offset) { 4856 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4857 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4858 // top of the viewport. 4859 // 4860 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4861 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4862 // way down the page. 4863 // 4864 // This is often the case for elements near the bottom of the page. 4865 // 4866 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4867 // the top of the element and the offset, which is enough to align the top of `elem` at the 4868 // desired position. 4869 var elemTop = elem.getBoundingClientRect().top; 4870 $window.scrollBy(0, elemTop - offset); 4871 } 4872 } else { 4873 $window.scrollTo(0, 0); 4874 } 4875 } 4876 4877 function scroll(hash) { 4878 hash = isString(hash) ? hash : $location.hash(); 4879 var elm; 4880 4881 // empty hash, scroll to the top of the page 4882 if (!hash) scrollTo(null); 4883 4884 // element with given id 4885 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4886 4887 // first anchor with given name :-D 4888 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4889 4890 // no element and hash == 'top', scroll to the top of the page 4891 else if (hash === 'top') scrollTo(null); 4892 } 4893 4894 // does not scroll when user clicks on anchor link that is currently on 4895 // (no url change, no $location.hash() change), browser native does scroll 4896 if (autoScrollingEnabled) { 4897 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4898 function autoScrollWatchAction(newVal, oldVal) { 4899 // skip the initial scroll if $location.hash is empty 4900 if (newVal === oldVal && newVal === '') return; 4901 4902 jqLiteDocumentLoaded(function() { 4903 $rootScope.$evalAsync(scroll); 4904 }); 4905 }); 4906 } 4907 4908 return scroll; 4909 }]; 4910} 4911 4912var $animateMinErr = minErr('$animate'); 4913var ELEMENT_NODE = 1; 4914var NG_ANIMATE_CLASSNAME = 'ng-animate'; 4915 4916function mergeClasses(a,b) { 4917 if (!a && !b) return ''; 4918 if (!a) return b; 4919 if (!b) return a; 4920 if (isArray(a)) a = a.join(' '); 4921 if (isArray(b)) b = b.join(' '); 4922 return a + ' ' + b; 4923} 4924 4925function extractElementNode(element) { 4926 for (var i = 0; i < element.length; i++) { 4927 var elm = element[i]; 4928 if (elm.nodeType === ELEMENT_NODE) { 4929 return elm; 4930 } 4931 } 4932} 4933 4934function splitClasses(classes) { 4935 if (isString(classes)) { 4936 classes = classes.split(' '); 4937 } 4938 4939 // Use createMap() to prevent class assumptions involving property names in 4940 // Object.prototype 4941 var obj = createMap(); 4942 forEach(classes, function(klass) { 4943 // sometimes the split leaves empty string values 4944 // incase extra spaces were applied to the options 4945 if (klass.length) { 4946 obj[klass] = true; 4947 } 4948 }); 4949 return obj; 4950} 4951 4952// if any other type of options value besides an Object value is 4953// passed into the $animate.method() animation then this helper code 4954// will be run which will ignore it. While this patch is not the 4955// greatest solution to this, a lot of existing plugins depend on 4956// $animate to either call the callback (< 1.2) or return a promise 4957// that can be changed. This helper function ensures that the options 4958// are wiped clean incase a callback function is provided. 4959function prepareAnimateOptions(options) { 4960 return isObject(options) 4961 ? options 4962 : {}; 4963} 4964 4965var $$CoreAnimateJsProvider = function() { 4966 this.$get = function() {}; 4967}; 4968 4969// this is prefixed with Core since it conflicts with 4970// the animateQueueProvider defined in ngAnimate/animateQueue.js 4971var $$CoreAnimateQueueProvider = function() { 4972 var postDigestQueue = new HashMap(); 4973 var postDigestElements = []; 4974 4975 this.$get = ['$$AnimateRunner', '$rootScope', 4976 function($$AnimateRunner, $rootScope) { 4977 return { 4978 enabled: noop, 4979 on: noop, 4980 off: noop, 4981 pin: noop, 4982 4983 push: function(element, event, options, domOperation) { 4984 domOperation && domOperation(); 4985 4986 options = options || {}; 4987 options.from && element.css(options.from); 4988 options.to && element.css(options.to); 4989 4990 if (options.addClass || options.removeClass) { 4991 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 4992 } 4993 4994 var runner = new $$AnimateRunner(); // jshint ignore:line 4995 4996 // since there are no animations to run the runner needs to be 4997 // notified that the animation call is complete. 4998 runner.complete(); 4999 return runner; 5000 } 5001 }; 5002 5003 5004 function updateData(data, classes, value) { 5005 var changed = false; 5006 if (classes) { 5007 classes = isString(classes) ? classes.split(' ') : 5008 isArray(classes) ? classes : []; 5009 forEach(classes, function(className) { 5010 if (className) { 5011 changed = true; 5012 data[className] = value; 5013 } 5014 }); 5015 } 5016 return changed; 5017 } 5018 5019 function handleCSSClassChanges() { 5020 forEach(postDigestElements, function(element) { 5021 var data = postDigestQueue.get(element); 5022 if (data) { 5023 var existing = splitClasses(element.attr('class')); 5024 var toAdd = ''; 5025 var toRemove = ''; 5026 forEach(data, function(status, className) { 5027 var hasClass = !!existing[className]; 5028 if (status !== hasClass) { 5029 if (status) { 5030 toAdd += (toAdd.length ? ' ' : '') + className; 5031 } else { 5032 toRemove += (toRemove.length ? ' ' : '') + className; 5033 } 5034 } 5035 }); 5036 5037 forEach(element, function(elm) { 5038 toAdd && jqLiteAddClass(elm, toAdd); 5039 toRemove && jqLiteRemoveClass(elm, toRemove); 5040 }); 5041 postDigestQueue.remove(element); 5042 } 5043 }); 5044 postDigestElements.length = 0; 5045 } 5046 5047 5048 function addRemoveClassesPostDigest(element, add, remove) { 5049 var data = postDigestQueue.get(element) || {}; 5050 5051 var classesAdded = updateData(data, add, true); 5052 var classesRemoved = updateData(data, remove, false); 5053 5054 if (classesAdded || classesRemoved) { 5055 5056 postDigestQueue.put(element, data); 5057 postDigestElements.push(element); 5058 5059 if (postDigestElements.length === 1) { 5060 $rootScope.$$postDigest(handleCSSClassChanges); 5061 } 5062 } 5063 } 5064 }]; 5065}; 5066 5067/** 5068 * @ngdoc provider 5069 * @name $animateProvider 5070 * 5071 * @description 5072 * Default implementation of $animate that doesn't perform any animations, instead just 5073 * synchronously performs DOM updates and resolves the returned runner promise. 5074 * 5075 * In order to enable animations the `ngAnimate` module has to be loaded. 5076 * 5077 * To see the functional implementation check out `src/ngAnimate/animate.js`. 5078 */ 5079var $AnimateProvider = ['$provide', function($provide) { 5080 var provider = this; 5081 5082 this.$$registeredAnimations = Object.create(null); 5083 5084 /** 5085 * @ngdoc method 5086 * @name $animateProvider#register 5087 * 5088 * @description 5089 * Registers a new injectable animation factory function. The factory function produces the 5090 * animation object which contains callback functions for each event that is expected to be 5091 * animated. 5092 * 5093 * * `eventFn`: `function(element, ... , doneFunction, options)` 5094 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 5095 * on the type of animation additional arguments will be injected into the animation function. The 5096 * list below explains the function signatures for the different animation methods: 5097 *
5098 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 5099 * - addClass: function(element, addedClasses, doneFunction, options) 5100 * - removeClass: function(element, removedClasses, doneFunction, options) 5101 * - enter, leave, move: function(element, doneFunction, options) 5102 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 5103 * 5104 * Make sure to trigger the `doneFunction` once the animation is fully complete. 5105 * 5106 * ```js 5107 * return { 5108 * //enter, leave, move signature 5109 * eventFn : function(element, done, options) { 5110 * //code to run the animation 5111 * //once complete, then run done() 5112 * return function endFunction(wasCancelled) { 5113 * //code to cancel the animation 5114 * } 5115 * } 5116 * } 5117 * ``` 5118 * 5119 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 5120 * @param {Function} factory The factory function that will be executed to return the animation 5121 * object. 5122 */ 5123 this.register = function(name, factory) { 5124 if (name && name.charAt(0) !== '.') { 5125 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 5126 } 5127 5128 var key = name + '-animation'; 5129 provider.$$registeredAnimations[name.substr(1)] = key; 5130 $provide.factory(key, factory); 5131 }; 5132 5133 /** 5134 * @ngdoc method 5135 * @name $animateProvider#classNameFilter 5136 * 5137 * @description 5138 * Sets and/or returns the CSS class regular expression that is checked when performing 5139 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5140 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5141 * When setting the `classNameFilter` value, animations will only be performed on elements 5142 * that successfully match the filter expression. This in turn can boost performance 5143 * for low-powered devices as well as applications containing a lot of structural operations. 5144 * @param {RegExp=} expression The className expression which will be checked against all animations 5145 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5146 */ 5147 this.classNameFilter = function(expression) { 5148 if (arguments.length === 1) { 5149 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 5150 if (this.$$classNameFilter) { 5151 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 5152 if (reservedRegex.test(this.$$classNameFilter.toString())) { 5153 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5154 5155 } 5156 } 5157 } 5158 return this.$$classNameFilter; 5159 }; 5160 5161 this.$get = ['$$animateQueue', function($$animateQueue) { 5162 function domInsert(element, parentElement, afterElement) { 5163 // if for some reason the previous element was removed 5164 // from the dom sometime before this code runs then let's 5165 // just stick to using the parent element as the anchor 5166 if (afterElement) { 5167 var afterNode = extractElementNode(afterElement); 5168 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5169 afterElement = null; 5170 } 5171 } 5172 afterElement ? afterElement.after(element) : parentElement.prepend(element); 5173 } 5174 5175 /** 5176 * @ngdoc service 5177 * @name $animate 5178 * @description The $animate service exposes a series of DOM utility methods that provide support 5179 * for animation hooks. The default behavior is the application of DOM operations, however, 5180 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5181 * to ensure that animation runs with the triggered DOM operation. 5182 * 5183 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 5184 * included and only when it is active then the animation hooks that `$animate` triggers will be 5185 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5186 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5187 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5188 * 5189 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5190 * 5191 * To learn more about enabling animation support, click here to visit the 5192 * {@link ngAnimate ngAnimate module page}. 5193 */ 5194 return { 5195 // we don't call it directly since non-existant arguments may 5196 // be interpreted as null within the sub enabled function 5197 5198 /** 5199 * 5200 * @ngdoc method 5201 * @name $animate#on 5202 * @kind function
5203 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5204 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5205 * is fired with the following params: 5206 * 5207 * ```js 5208 * $animate.on('enter', container, 5209 * function callback(element, phase) { 5210 * // cool we detected an enter animation within the container 5211 * } 5212 * ); 5213 * ``` 5214 * 5215 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5216 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5217 * as well as among its children 5218 * @param {Function} callback the callback function that will be fired when the listener is triggered 5219 * 5220 * The arguments present in the callback function are: 5221 * * `element` - The captured DOM element that the animation was fired on. 5222 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5223 */ 5224 on: $$animateQueue.on, 5225 5226 /** 5227 * 5228 * @ngdoc method 5229 * @name $animate#off 5230 * @kind function 5231 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5232 * can be used in three different ways depending on the arguments: 5233 * 5234 * ```js 5235 * // remove all the animation event listeners listening for `enter` 5236 * $animate.off('enter'); 5237 * 5238 * // remove all the animation event listeners listening for `enter` on the given element and its children 5239 * $animate.off('enter', container); 5240 * 5241 * // remove the event listener function provided by `listenerFn` that is set 5242 * // to listen for `enter` on the given `element` as well as its children 5243 * $animate.off('enter', container, callback); 5244 * ``` 5245 * 5246 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 5247 * @param {DOMElement=} container the container element the event listener was placed on 5248 * @param {Function=} callback the callback function that was registered as the listener 5249 */ 5250 off: $$animateQueue.off, 5251 5252 /** 5253 * @ngdoc method 5254 * @name $animate#pin 5255 * @kind function 5256 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5257 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5258 * element despite being outside the realm of the application or within another application. Say for example if the application 5259 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5260 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5261 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5262 * 5263 * Note that this feature is only active when the `ngAnimate` module is used. 5264 * 5265 * @param {DOMElement} element the external element that will be pinned 5266 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5267 */ 5268 pin: $$animateQueue.pin, 5269 5270 /** 5271 * 5272 * @ngdoc method 5273 * @name $animate#enabled 5274 * @kind function 5275 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5276 * function can be called in four ways: 5277 * 5278 * ```js 5279 * // returns true or false 5280 * $animate.enabled(); 5281 * 5282 * // changes the enabled state for all animations 5283 * $animate.enabled(false); 5284 * $animate.enabled(true); 5285 * 5286 * // returns true or false if animations are enabled for an element 5287 * $animate.enabled(element); 5288 * 5289 * // changes the enabled state for an element and its children 5290 * $animate.enabled(element, true); 5291 * $animate.enabled(element, false); 5292 * ``` 5293 * 5294 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5295 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5296 * 5297 * @return {boolean} whether or not animations are enabled 5298 */ 5299 enabled: $$animateQueue.enabled, 5300 5301 /** 5302 * @ngdoc method 5303 * @name $animate#cancel 5304 * @kind function
5305 * @description Cancels the provided animation. 5306 * 5307 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5308 */ 5309 cancel: function(runner) { 5310 runner.end && runner.end(); 5311 }, 5312 5313 /** 5314 * 5315 * @ngdoc method 5316 * @name $animate#enter 5317 * @kind function 5318 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5319 * as the first child within the `parent` element and then triggers an animation. 5320 * A promise is returned that will be resolved during the next digest once the animation 5321 * has completed. 5322 * 5323 * @param {DOMElement} element the element which will be inserted into the DOM 5324 * @param {DOMElement} parent the parent element which will append the element as 5325 * a child (so long as the after element is not present) 5326 * @param {DOMElement=} after the sibling element after which the element will be appended 5327 * @param {object=} options an optional collection of options/styles that will be applied to the element 5328 * 5329 * @return {Promise} the animation callback promise 5330 */ 5331 enter: function(element, parent, after, options) { 5332 parent = parent && jqLite(parent); 5333 after = after && jqLite(after); 5334 parent = parent || after.parent(); 5335 domInsert(element, parent, after); 5336 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5337 }, 5338 5339 /** 5340 * 5341 * @ngdoc method 5342 * @name $animate#move 5343 * @kind function 5344 * @description Inserts (moves) the element into its new position in the DOM either after 5345 * the `after` element (if provided) or as the first child within the `parent` element 5346 * and then triggers an animation. A promise is returned that will be resolved 5347 * during the next digest once the animation has completed. 5348 * 5349 * @param {DOMElement} element the element which will be moved into the new DOM position 5350 * @param {DOMElement} parent the parent element which will append the element as 5351 * a child (so long as the after element is not present) 5352 * @param {DOMElement=} after the sibling element after which the element will be appended 5353 * @param {object=} options an optional collection of options/styles that will be applied to the element 5354 * 5355 * @return {Promise} the animation callback promise 5356 */ 5357 move: function(element, parent, after, options) { 5358 parent = parent && jqLite(parent); 5359 after = after && jqLite(after); 5360 parent = parent || after.parent(); 5361 domInsert(element, parent, after); 5362 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5363 }, 5364 5365 /** 5366 * @ngdoc method 5367 * @name $animate#leave 5368 * @kind function 5369 * @description Triggers an animation and then removes the element from the DOM. 5370 * When the function is called a promise is returned that will be resolved during the next 5371 * digest once the animation has completed. 5372 * 5373 * @param {DOMElement} element the element which will be removed from the DOM 5374 * @param {object=} options an optional collection of options/styles that will be applied to the element 5375 * 5376 * @return {Promise} the animation callback promise 5377 */ 5378 leave: function(element, options) { 5379 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5380 element.remove(); 5381 }); 5382 }, 5383 5384 /** 5385 * @ngdoc method 5386 * @name $animate#addClass 5387 * @kind function 5388 * 5389 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5390 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5391 * animation if element already contains the CSS class or if the class is removed at a later step. 5392 * Note that class-based animations are treated differently compared to structural animations 5393 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5394 * depending if CSS or JavaScript animations are used. 5395 * 5396 * @param {DOMElement} element the element which the CSS classes will be applied to 5397 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5398 * @param {object=} options an optional collection of options/styles that will be applied to the element 5399 * 5400 * @return {Promise} the animation callback promise 5401 */ 5402 addClass: function(element, className, options) { 5403 options = prepareAnimateOptions(options); 5404 options.addClass = mergeClasses(options.addclass, className); 5405 return $$animateQueue.push(element, 'addClass', options); 5406 }, 5407 5408 /** 5409 * @ngdoc method 5410 * @name $animate#removeClass 5411 * @kind function 5412 *
5413 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5414 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5415 * animation if element does not contain the CSS class or if the class is added at a later step. 5416 * Note that class-based animations are treated differently compared to structural animations 5417 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5418 * depending if CSS or JavaScript animations are used. 5419 * 5420 * @param {DOMElement} element the element which the CSS classes will be applied to 5421 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5422 * @param {object=} options an optional collection of options/styles that will be applied to the element 5423 * 5424 * @return {Promise} the animation callback promise 5425 */ 5426 removeClass: function(element, className, options) { 5427 options = prepareAnimateOptions(options); 5428 options.removeClass = mergeClasses(options.removeClass, className); 5429 return $$animateQueue.push(element, 'removeClass', options); 5430 }, 5431 5432 /** 5433 * @ngdoc method 5434 * @name $animate#setClass 5435 * @kind function 5436 * 5437 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5438 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5439 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5440 * passed. Note that class-based animations are treated differently compared to structural animations 5441 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5442 * depending if CSS or JavaScript animations are used. 5443 * 5444 * @param {DOMElement} element the element which the CSS classes will be applied to 5445 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5446 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5447 * @param {object=} options an optional collection of options/styles that will be applied to the element 5448 * 5449 * @return {Promise} the animation callback promise 5450 */ 5451 setClass: function(element, add, remove, options) { 5452 options = prepareAnimateOptions(options); 5453 options.addClass = mergeClasses(options.addClass, add); 5454 options.removeClass = mergeClasses(options.removeClass, remove); 5455 return $$animateQueue.push(element, 'setClass', options); 5456 }, 5457 5458 /** 5459 * @ngdoc method 5460 * @name $animate#animate 5461 * @kind function 5462 * 5463 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5464 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 5465 * on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and 5466 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 5467 * style in `to`, the style in `from` is applied immediately, and no animation is run. 5468 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 5469 * method (or as part of the `options` parameter): 5470 * 5471 * ```js 5472 * ngModule.animation('.my-inline-animation', function() { 5473 * return { 5474 * animate : function(element, from, to, done, options) { 5475 * //animation 5476 * done(); 5477 * } 5478 * } 5479 * }); 5480 * ``` 5481 * 5482 * @param {DOMElement} element the element which the CSS styles will be applied to 5483 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5484 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5485 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5486 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5487 * (Note that if no animation is detected then this value will not be applied to the element.) 5488 * @param {object=} options an optional collection of options/styles that will be applied to the element 5489 * 5490 * @return {Promise} the animation callback promise 5491 */ 5492 animate: function(element, from, to, className, options) { 5493 options = prepareAnimateOptions(options); 5494 options.from = options.from ? extend(options.from, from) : from; 5495 options.to = options.to ? extend(options.to, to) : to; 5496 5497 className = className || 'ng-inline-animate';
vendor: 6,625 bytes, lines 5498-5759
5498 options.tempClasses = mergeClasses(options.tempClasses, className); 5499 return $$animateQueue.push(element, 'animate', options); 5500 } 5501 }; 5502 }]; 5503}]; 5504 5505var $$AnimateAsyncRunFactoryProvider = function() { 5506 this.$get = ['$$rAF', function($$rAF) { 5507 var waitQueue = []; 5508 5509 function waitForTick(fn) { 5510 waitQueue.push(fn); 5511 if (waitQueue.length > 1) return; 5512 $$rAF(function() { 5513 for (var i = 0; i < waitQueue.length; i++) { 5514 waitQueue[i](); 5515 } 5516 waitQueue = []; 5517 }); 5518 } 5519 5520 return function() { 5521 var passed = false; 5522 waitForTick(function() { 5523 passed = true; 5524 }); 5525 return function(callback) { 5526 passed ? callback() : waitForTick(callback); 5527 }; 5528 }; 5529 }]; 5530}; 5531 5532var $$AnimateRunnerFactoryProvider = function() { 5533 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', 5534 function($q, $sniffer, $$animateAsyncRun) { 5535 5536 var INITIAL_STATE = 0; 5537 var DONE_PENDING_STATE = 1; 5538 var DONE_COMPLETE_STATE = 2; 5539 5540 AnimateRunner.chain = function(chain, callback) { 5541 var index = 0; 5542 5543 next(); 5544 function next() { 5545 if (index === chain.length) { 5546 callback(true); 5547 return; 5548 } 5549 5550 chain[index](function(response) { 5551 if (response === false) { 5552 callback(false); 5553 return; 5554 } 5555 index++; 5556 next(); 5557 }); 5558 } 5559 }; 5560 5561 AnimateRunner.all = function(runners, callback) { 5562 var count = 0; 5563 var status = true; 5564 forEach(runners, function(runner) { 5565 runner.done(onProgress); 5566 }); 5567 5568 function onProgress(response) { 5569 status = status && response; 5570 if (++count === runners.length) { 5571 callback(status); 5572 } 5573 } 5574 }; 5575 5576 function AnimateRunner(host) { 5577 this.setHost(host); 5578 5579 this._doneCallbacks = []; 5580 this._runInAnimationFrame = $$animateAsyncRun(); 5581 this._state = 0; 5582 } 5583 5584 AnimateRunner.prototype = { 5585 setHost: function(host) { 5586 this.host = host || {}; 5587 }, 5588 5589 done: function(fn) { 5590 if (this._state === DONE_COMPLETE_STATE) { 5591 fn(); 5592 } else { 5593 this._doneCallbacks.push(fn); 5594 } 5595 }, 5596 5597 progress: noop, 5598 5599 getPromise: function() { 5600 if (!this.promise) { 5601 var self = this; 5602 this.promise = $q(function(resolve, reject) { 5603 self.done(function(status) { 5604 status === false ? reject() : resolve(); 5605 }); 5606 }); 5607 } 5608 return this.promise; 5609 }, 5610 5611 then: function(resolveHandler, rejectHandler) { 5612 return this.getPromise().then(resolveHandler, rejectHandler); 5613 }, 5614 5615 'catch': function(handler) { 5616 return this.getPromise()['catch'](handler); 5617 }, 5618 5619 'finally': function(handler) { 5620 return this.getPromise()['finally'](handler); 5621 }, 5622 5623 pause: function() { 5624 if (this.host.pause) { 5625 this.host.pause(); 5626 } 5627 }, 5628 5629 resume: function() { 5630 if (this.host.resume) { 5631 this.host.resume(); 5632 } 5633 }, 5634 5635 end: function() { 5636 if (this.host.end) { 5637 this.host.end(); 5638 } 5639 this._resolve(true); 5640 }, 5641 5642 cancel: function() { 5643 if (this.host.cancel) { 5644 this.host.cancel(); 5645 } 5646 this._resolve(false); 5647 }, 5648 5649 complete: function(response) { 5650 var self = this; 5651 if (self._state === INITIAL_STATE) { 5652 self._state = DONE_PENDING_STATE; 5653 self._runInAnimationFrame(function() { 5654 self._resolve(response); 5655 }); 5656 } 5657 }, 5658 5659 _resolve: function(response) { 5660 if (this._state !== DONE_COMPLETE_STATE) { 5661 forEach(this._doneCallbacks, function(fn) { 5662 fn(response); 5663 }); 5664 this._doneCallbacks.length = 0; 5665 this._state = DONE_COMPLETE_STATE; 5666 } 5667 } 5668 }; 5669 5670 return AnimateRunner; 5671 }]; 5672}; 5673 5674/** 5675 * @ngdoc service 5676 * @name $animateCss 5677 * @kind object 5678 * 5679 * @description 5680 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 5681 * then the `$animateCss` service will actually perform animations. 5682 * 5683 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 5684 */ 5685var $CoreAnimateCssProvider = function() { 5686 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 5687 5688 return function(element, initialOptions) { 5689 // all of the animation functions should create 5690 // a copy of the options data, however, if a 5691 // parent service has already created a copy then 5692 // we should stick to using that 5693 var options = initialOptions || {}; 5694 if (!options.$$prepared) { 5695 options = copy(options); 5696 } 5697 5698 // there is no point in applying the styles since 5699 // there is no animation that goes on at all in 5700 // this version of $animateCss. 5701 if (options.cleanupStyles) { 5702 options.from = options.to = null; 5703 } 5704 5705 if (options.from) { 5706 element.css(options.from); 5707 options.from = null; 5708 } 5709 5710 /* jshint newcap: false */ 5711 var closed, runner = new $$AnimateRunner(); 5712 return { 5713 start: run, 5714 end: run 5715 }; 5716 5717 function run() { 5718 $$rAF(function() { 5719 applyAnimationContents(); 5720 if (!closed) { 5721 runner.complete(); 5722 } 5723 closed = true; 5724 }); 5725 return runner; 5726 } 5727 5728 function applyAnimationContents() { 5729 if (options.addClass) { 5730 element.addClass(options.addClass); 5731 options.addClass = null; 5732 } 5733 if (options.removeClass) { 5734 element.removeClass(options.removeClass); 5735 options.removeClass = null; 5736 } 5737 if (options.to) { 5738 element.css(options.to); 5739 options.to = null; 5740 } 5741 } 5742 }; 5743 }]; 5744}; 5745 5746/* global stripHash: true */ 5747 5748/** 5749 * ! This is a private undocumented service ! 5750 * 5751 * @name $browser 5752 * @requires $log 5753 * @description 5754 * This object has two goals: 5755 * 5756 * - hide all the global state in the browser caused by the window object 5757 * - abstract away all the browser specific features and inconsistencies 5758 * 5759 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5760 * service, which can be used for convenient testing of the application without the interaction with 5761 * the real browser apis. 5762 */ 5763/** 5764 * @param {object} window The global window object. 5765 * @param {object} document jQuery wrapped document. 5766 * @param {object} $log window.console or an object with the same interface. 5767 * @param {object} $sniffer $sniffer service 5768 */ 5769function Browser(window, document, $log, $sniffer) { 5770 var self = this, 5771 rawDocument = document[0], 5772 location = window.location, 5773 history = window.history, 5774 setTimeout = window.setTimeout, 5775 clearTimeout = window.clearTimeout, 5776 pendingDeferIds = {}; 5777 5778 self.isMock = false; 5779 5780 var outstandingRequestCount = 0; 5781 var outstandingRequestCallbacks = []; 5782 5783 // TODO(vojta): remove this temporary api 5784 self.$$completeOutstandingRequest = completeOutstandingRequest; 5785 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5786 5787 /** 5788 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5789 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5790 */ 5791 function completeOutstandingRequest(fn) { 5792 try { 5793 fn.apply(null, sliceArgs(arguments, 1)); 5794 } finally { 5795 outstandingRequestCount--; 5796 if (outstandingRequestCount === 0) { 5797 while (outstandingRequestCallbacks.length) { 5798 try { 5799 outstandingRequestCallbacks.pop()(); 5800 } catch (e) { 5801 $log.error(e); 5802 } 5803 } 5804 } 5805 } 5806 } 5807 5808 function getHash(url) { 5809 var index = url.indexOf('#'); 5810 return index === -1 ? '' : url.substr(index); 5811 } 5812 5813 /** 5814 * @private 5815 * Note: this method is used only by scenario runner 5816 * TODO(vojta): prefix this method with $$ ? 5817 * @param {function()} callback Function that will be called when no outstanding request 5818 */ 5819 self.notifyWhenNoOutstandingRequests = function(callback) { 5820 if (outstandingRequestCount === 0) { 5821 callback(); 5822 } else { 5823 outstandingRequestCallbacks.push(callback); 5824 } 5825 }; 5826 5827 ////////////////////////////////////////////////////////////// 5828 // URL API 5829 ////////////////////////////////////////////////////////////// 5830 5831 var cachedState, lastHistoryState, 5832 lastBrowserUrl = location.href, 5833 baseElement = document.find('base'), 5834 pendingLocation = null; 5835 5836 cacheState(); 5837 lastHistoryState = cachedState; 5838 5839 /** 5840 * @name $browser#url 5841 * 5842 * @description 5843 * GETTER: 5844 * Without any argument, this method just returns current value of location.href. 5845 * 5846 * SETTER: 5847 * With at least one argument, this method sets url to new value. 5848 * If html5 history api supported, pushState/replaceState is used, otherwise 5849 * location.href/location.replace is used. 5850 * Returns its own instance to allow chaining 5851 * 5852 * NOTE: this api is intended for use only by the $location service. Please use the 5853 * {@link ng.$location $location service} to change url. 5854 * 5855 * @param {string} url New url (when used as setter) 5856 * @param {boolean=} replace Should new url replace current history record? 5857 * @param {object=} state object to use with pushState/replaceState 5858 */ 5859 self.url = function(url, replace, state) { 5860 // In modern browsers `history.state` is `null` by default; treating it separately 5861 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5862 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5863 if (isUndefined(state)) { 5864 state = null; 5865 } 5866 5867 // Android Browser BFCache causes location, history reference to become stale. 5868 if (location !== window.location) location = window.location; 5869 if (history !== window.history) history = window.history; 5870 5871 // setter 5872 if (url) { 5873 var sameState = lastHistoryState === state; 5874 5875 // Don't change anything if previous and current URLs and states match. This also prevents 5876 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5877 // See https://github.com/angular/angular.js/commit/ffb2701 5878 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5879 return self; 5880 } 5881 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5882 lastBrowserUrl = url; 5883 lastHistoryState = state; 5884 // Don't use history API if only the hash changed 5885 // due to a bug in IE10/IE11 which leads 5886 // to not firing a `hashchange` nor `popstate` event 5887 // in some cases (see #9143). 5888 if ($sniffer.history && (!sameBase || !sameState)) { 5889 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5890 cacheState(); 5891 // Do the assignment again so that those two variables are referentially identical. 5892 lastHistoryState = cachedState; 5893 } else { 5894 if (!sameBase || pendingLocation) { 5895 pendingLocation = url; 5896 } 5897 if (replace) { 5898 location.replace(url); 5899 } else if (!sameBase) { 5900 location.href = url; 5901 } else { 5902 location.hash = getHash(url); 5903 } 5904 if (location.href !== url) { 5905 pendingLocation = url; 5906 } 5907 } 5908 return self; 5909 // getter 5910 } else { 5911 // - pendingLocation is needed as browsers don't allow to read out 5912 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 5913 // https://openradar.appspot.com/22186109). 5914 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5915 return pendingLocation || location.href.replace(/%27/g,"'"); 5916 } 5917 }; 5918 5919 /** 5920 * @name $browser#state 5921 * 5922 * @description 5923 * This method is a getter. 5924 * 5925 * Return history.state or null if history.state is undefined. 5926 * 5927 * @returns {object} state 5928 */ 5929 self.state = function() { 5930 return cachedState; 5931 }; 5932 5933 var urlChangeListeners = [], 5934 urlChangeInit = false; 5935 5936 function cacheStateAndFireUrlChange() { 5937 pendingLocation = null; 5938 cacheState(); 5939 fireUrlChange(); 5940 } 5941 5942 function getCurrentState() { 5943 try { 5944 return history.state; 5945 } catch (e) { 5946 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5947 } 5948 } 5949 5950 // This variable should be used *only* inside the cacheState function. 5951 var lastCachedState = null; 5952 function cacheState() { 5953 // This should be the only place in $browser where `history.state` is read. 5954 cachedState = getCurrentState(); 5955 cachedState = isUndefined(cachedState) ? null : cachedState; 5956 5957 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5958 if (equals(cachedState, lastCachedState)) {
5959 cachedState = lastCachedState; 5960 } 5961 lastCachedState = cachedState; 5962 } 5963 5964 function fireUrlChange() { 5965 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5966 return; 5967 } 5968 5969 lastBrowserUrl = self.url(); 5970 lastHistoryState = cachedState; 5971 forEach(urlChangeListeners, function(listener) { 5972 listener(self.url(), cachedState); 5973 }); 5974 } 5975 5976 /** 5977 * @name $browser#onUrlChange 5978 * 5979 * @description 5980 * Register callback function that will be called, when url changes. 5981 * 5982 * It's only called when the url is changed from outside of angular: 5983 * - user types different url into address bar 5984 * - user clicks on history (forward/back) button 5985 * - user clicks on a link 5986 * 5987 * It's not called when url is changed by $browser.url() method 5988 * 5989 * The listener gets called with new url as parameter. 5990 * 5991 * NOTE: this api is intended for use only by the $location service. Please use the 5992 * {@link ng.$location $location service} to monitor url changes in angular apps. 5993 * 5994 * @param {function(string)} listener Listener function to be called when url changes. 5995 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5996 */ 5997 self.onUrlChange = function(callback) { 5998 // TODO(vojta): refactor to use node's syntax for events 5999 if (!urlChangeInit) { 6000 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 6001 // don't fire popstate when user change the address bar and don't fire hashchange when url 6002 // changed by push/replaceState 6003 6004 // html5 history api - popstate event 6005 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 6006 // hashchange event 6007 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 6008 6009 urlChangeInit = true; 6010 } 6011 6012 urlChangeListeners.push(callback); 6013 return callback; 6014 }; 6015 6016 /** 6017 * @private 6018 * Remove popstate and hashchange handler from window. 6019 * 6020 * NOTE: this api is intended for use only by $rootScope. 6021 */ 6022 self.$$applicationDestroyed = function() { 6023 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 6024 }; 6025 6026 /** 6027 * Checks whether the url has changed outside of Angular. 6028 * Needs to be exported to be able to check for changes that have been done in sync, 6029 * as hashchange/popstate events fire in async. 6030 */ 6031 self.$$checkUrlChange = fireUrlChange; 6032 6033 ////////////////////////////////////////////////////////////// 6034 // Misc API 6035 ////////////////////////////////////////////////////////////// 6036 6037 /** 6038 * @name $browser#baseHref 6039 * 6040 * @description 6041 * Returns current <base href> 6042 * (always relative - without domain) 6043 * 6044 * @returns {string} The current base href 6045 */ 6046 self.baseHref = function() { 6047 var href = baseElement.attr('href'); 6048 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 6049 }; 6050 6051 /** 6052 * @name $browser#defer 6053 * @param {function()} fn A function, who's execution should be deferred. 6054 * @param {number=} [delay=0] of milliseconds to defer the function execution. 6055 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 6056 * 6057 * @description 6058 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 6059 * 6060 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 6061 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 6062 * via `$browser.defer.flush()`. 6063 * 6064 */ 6065 self.defer = function(fn, delay) { 6066 var timeoutId; 6067 outstandingRequestCount++; 6068 timeoutId = setTimeout(function() { 6069 delete pendingDeferIds[timeoutId]; 6070 completeOutstandingRequest(fn); 6071 }, delay || 0); 6072 pendingDeferIds[timeoutId] = true; 6073 return timeoutId; 6074 }; 6075 6076 6077 /** 6078 * @name $browser#defer.cancel 6079 * 6080 * @description 6081 * Cancels a deferred task identified with `deferId`. 6082 * 6083 * @param {*} deferId Token returned by the `$browser.defer` function. 6084 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 6085 * canceled. 6086 */ 6087 self.defer.cancel = function(deferId) { 6088 if (pendingDeferIds[deferId]) {
vendor: 4,704 bytes, lines 6089-6231
6089 delete pendingDeferIds[deferId]; 6090 clearTimeout(deferId); 6091 completeOutstandingRequest(noop); 6092 return true; 6093 } 6094 return false; 6095 }; 6096 6097} 6098 6099function $BrowserProvider() { 6100 this.$get = ['$window', '$log', '$sniffer', '$document', 6101 function($window, $log, $sniffer, $document) { 6102 return new Browser($window, $document, $log, $sniffer); 6103 }]; 6104} 6105 6106/** 6107 * @ngdoc service 6108 * @name $cacheFactory 6109 * 6110 * @description 6111 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 6112 * them. 6113 * 6114 * ```js 6115 * 6116 * var cache = $cacheFactory('cacheId'); 6117 * expect($cacheFactory.get('cacheId')).toBe(cache); 6118 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 6119 * 6120 * cache.put("key", "value"); 6121 * cache.put("another key", "another value"); 6122 * 6123 * // We've specified no options on creation 6124 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 6125 * 6126 * ``` 6127 * 6128 * 6129 * @param {string} cacheId Name or id of the newly created cache. 6130 * @param {object=} options Options object that specifies the cache behavior. Properties: 6131 * 6132 * - `{number=}` `capacity` â turns the cache into LRU cache. 6133 * 6134 * @returns {object} Newly created cache object with the following set of methods: 6135 * 6136 * - `{object}` `info()` â Returns id, size, and options of cache. 6137 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 6138 * it. 6139 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 6140 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 6141 * - `{void}` `removeAll()` â Removes all cached values. 6142 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 6143 * 6144 * @example 6145 <example module="cacheExampleApp"> 6146 <file name="index.html"> 6147 <div ng-controller="CacheController"> 6148 <input ng-model="newCacheKey" placeholder="Key"> 6149 <input ng-model="newCacheValue" placeholder="Value"> 6150 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 6151 6152 <p ng-if="keys.length">Cached Values</p> 6153 <div ng-repeat="key in keys"> 6154 <span ng-bind="key"></span> 6155 <span>: </span> 6156 <b ng-bind="cache.get(key)"></b> 6157 </div> 6158 6159 <p>Cache Info</p> 6160 <div ng-repeat="(key, value) in cache.info()"> 6161 <span ng-bind="key"></span> 6162 <span>: </span> 6163 <b ng-bind="value"></b> 6164 </div> 6165 </div> 6166 </file> 6167 <file name="script.js"> 6168 angular.module('cacheExampleApp', []). 6169 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 6170 $scope.keys = []; 6171 $scope.cache = $cacheFactory('cacheId'); 6172 $scope.put = function(key, value) { 6173 if (angular.isUndefined($scope.cache.get(key))) { 6174 $scope.keys.push(key); 6175 } 6176 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 6177 }; 6178 }]); 6179 </file> 6180 <file name="style.css"> 6181 p { 6182 margin: 10px 0 3px; 6183 } 6184 </file> 6185 </example> 6186 */ 6187function $CacheFactoryProvider() { 6188 6189 this.$get = function() { 6190 var caches = {}; 6191 6192 function cacheFactory(cacheId, options) { 6193 if (cacheId in caches) { 6194 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 6195 } 6196 6197 var size = 0, 6198 stats = extend({}, options, {id: cacheId}), 6199 data = createMap(), 6200 capacity = (options && options.capacity) || Number.MAX_VALUE, 6201 lruHash = createMap(), 6202 freshEnd = null, 6203 staleEnd = null; 6204 6205 /** 6206 * @ngdoc type 6207 * @name $cacheFactory.Cache 6208 * 6209 * @description 6210 * A cache object used to store and retrieve data, primarily used by 6211 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 6212 * templates and other data. 6213 * 6214 * ```js 6215 * angular.module('superCache') 6216 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 6217 * return $cacheFactory('super-cache'); 6218 * }]); 6219 * ``` 6220 * 6221 * Example test: 6222 * 6223 * ```js 6224 * it('should behave like a cache', inject(function(superCache) { 6225 * superCache.put('key', 'value'); 6226 * superCache.put('another key', 'another value'); 6227 * 6228 * expect(superCache.info()).toEqual({ 6229 * id: 'super-cache', 6230 * size: 2 6231 * });
6232 * 6233 * superCache.remove('another key'); 6234 * expect(superCache.get('another key')).toBeUndefined(); 6235 * 6236 * superCache.removeAll(); 6237 * expect(superCache.info()).toEqual({ 6238 * id: 'super-cache', 6239 * size: 0 6240 * }); 6241 * })); 6242 * ``` 6243 */ 6244 return caches[cacheId] = { 6245 6246 /** 6247 * @ngdoc method 6248 * @name $cacheFactory.Cache#put 6249 * @kind function 6250 * 6251 * @description 6252 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 6253 * retrieved later, and incrementing the size of the cache if the key was not already 6254 * present in the cache. If behaving like an LRU cache, it will also remove stale 6255 * entries from the set. 6256 * 6257 * It will not insert undefined values into the cache. 6258 * 6259 * @param {string} key the key under which the cached data is stored. 6260 * @param {*} value the value to store alongside the key. If it is undefined, the key 6261 * will not be stored. 6262 * @returns {*} the value stored. 6263 */ 6264 put: function(key, value) { 6265 if (isUndefined(value)) return; 6266 if (capacity < Number.MAX_VALUE) { 6267 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 6268 6269 refresh(lruEntry); 6270 } 6271 6272 if (!(key in data)) size++; 6273 data[key] = value; 6274 6275 if (size > capacity) { 6276 this.remove(staleEnd.key); 6277 } 6278 6279 return value; 6280 }, 6281 6282 /** 6283 * @ngdoc method 6284 * @name $cacheFactory.Cache#get 6285 * @kind function 6286 * 6287 * @description 6288 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 6289 * 6290 * @param {string} key the key of the data to be retrieved 6291 * @returns {*} the value stored. 6292 */ 6293 get: function(key) { 6294 if (capacity < Number.MAX_VALUE) { 6295 var lruEntry = lruHash[key]; 6296 6297 if (!lruEntry) return; 6298 6299 refresh(lruEntry); 6300 } 6301 6302 return data[key]; 6303 }, 6304 6305 6306 /** 6307 * @ngdoc method 6308 * @name $cacheFactory.Cache#remove 6309 * @kind function 6310 * 6311 * @description 6312 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 6313 * 6314 * @param {string} key the key of the entry to be removed 6315 */ 6316 remove: function(key) { 6317 if (capacity < Number.MAX_VALUE) { 6318 var lruEntry = lruHash[key]; 6319 6320 if (!lruEntry) return; 6321 6322 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 6323 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 6324 link(lruEntry.n,lruEntry.p); 6325 6326 delete lruHash[key]; 6327 } 6328 6329 if (!(key in data)) return; 6330 6331 delete data[key]; 6332 size--; 6333 }, 6334 6335 6336 /** 6337 * @ngdoc method 6338 * @name $cacheFactory.Cache#removeAll 6339 * @kind function 6340 * 6341 * @description 6342 * Clears the cache object of any entries. 6343 */ 6344 removeAll: function() { 6345 data = createMap(); 6346 size = 0; 6347 lruHash = createMap(); 6348 freshEnd = staleEnd = null; 6349 }, 6350 6351 6352 /** 6353 * @ngdoc method 6354 * @name $cacheFactory.Cache#destroy 6355 * @kind function 6356 * 6357 * @description 6358 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 6359 * removing it from the {@link $cacheFactory $cacheFactory} set. 6360 */ 6361 destroy: function() { 6362 data = null; 6363 stats = null; 6364 lruHash = null; 6365 delete caches[cacheId]; 6366 }, 6367 6368 6369 /** 6370 * @ngdoc method 6371 * @name $cacheFactory.Cache#info 6372 * @kind function 6373 * 6374 * @description 6375 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 6376 * 6377 * @returns {object} an object with the following properties: 6378 * <ul> 6379 * <li>**id**: the id of the cache instance</li> 6380 * <li>**size**: the number of entries kept in the cache instance</li> 6381 * <li>**...**: any additional properties from the options object when creating the 6382 * cache.</li> 6383 * </ul> 6384 */ 6385 info: function() { 6386 return extend({}, stats, {size: size});
vendor: 6,110 bytes, lines 6387-6575
6387 } 6388 }; 6389 6390 6391 /** 6392 * makes the `entry` the freshEnd of the LRU linked list 6393 */ 6394 function refresh(entry) { 6395 if (entry != freshEnd) { 6396 if (!staleEnd) { 6397 staleEnd = entry; 6398 } else if (staleEnd == entry) { 6399 staleEnd = entry.n; 6400 } 6401 6402 link(entry.n, entry.p); 6403 link(entry, freshEnd); 6404 freshEnd = entry; 6405 freshEnd.n = null; 6406 } 6407 } 6408 6409 6410 /** 6411 * bidirectionally links two entries of the LRU linked list 6412 */ 6413 function link(nextEntry, prevEntry) { 6414 if (nextEntry != prevEntry) { 6415 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 6416 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 6417 } 6418 } 6419 } 6420 6421 6422 /** 6423 * @ngdoc method 6424 * @name $cacheFactory#info 6425 * 6426 * @description 6427 * Get information about all the caches that have been created 6428 * 6429 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6430 */ 6431 cacheFactory.info = function() { 6432 var info = {}; 6433 forEach(caches, function(cache, cacheId) { 6434 info[cacheId] = cache.info(); 6435 }); 6436 return info; 6437 }; 6438 6439 6440 /** 6441 * @ngdoc method 6442 * @name $cacheFactory#get 6443 * 6444 * @description 6445 * Get access to a cache object by the `cacheId` used when it was created. 6446 * 6447 * @param {string} cacheId Name or id of a cache to access. 6448 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6449 */ 6450 cacheFactory.get = function(cacheId) { 6451 return caches[cacheId]; 6452 }; 6453 6454 6455 return cacheFactory; 6456 }; 6457} 6458 6459/** 6460 * @ngdoc service 6461 * @name $templateCache 6462 * 6463 * @description 6464 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6465 * can load templates directly into the cache in a `script` tag, or by consuming the 6466 * `$templateCache` service directly. 6467 * 6468 * Adding via the `script` tag: 6469 * 6470 * ```html 6471 * <script type="text/ng-template" id="templateId.html"> 6472 * <p>This is the content of the template</p> 6473 * </script> 6474 * ``` 6475 * 6476 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6477 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6478 * element with ng-app attribute), otherwise the template will be ignored. 6479 * 6480 * Adding via the `$templateCache` service: 6481 * 6482 * ```js 6483 * var myApp = angular.module('myApp', []); 6484 * myApp.run(function($templateCache) { 6485 * $templateCache.put('templateId.html', 'This is the content of the template'); 6486 * }); 6487 * ``` 6488 * 6489 * To retrieve the template later, simply use it in your HTML: 6490 * ```html 6491 * <div ng-include=" 'templateId.html' "></div> 6492 * ``` 6493 * 6494 * or get it via Javascript: 6495 * ```js 6496 * $templateCache.get('templateId.html') 6497 * ``` 6498 * 6499 * See {@link ng.$cacheFactory $cacheFactory}. 6500 * 6501 */ 6502function $TemplateCacheProvider() { 6503 this.$get = ['$cacheFactory', function($cacheFactory) { 6504 return $cacheFactory('templates'); 6505 }]; 6506} 6507 6508/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6509 * Any commits to this file should be reviewed with security in mind. * 6510 * Changes to this file can potentially create security vulnerabilities. * 6511 * An approval from 2 Core members with history of modifying * 6512 * this file is required. * 6513 * * 6514 * Does the change somehow allow for arbitrary javascript to be executed? * 6515 * Or allows for someone to change the prototype of built-in objects? * 6516 * Or gives undesired access to variables likes document or window? * 6517 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6518 6519/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6520 * 6521 * DOM-related variables: 6522 * 6523 * - "node" - DOM Node 6524 * - "element" - DOM Element or Node 6525 * - "$node" or "$element" - jqLite-wrapped node or element 6526 * 6527 * 6528 * Compiler related stuff: 6529 * 6530 * - "linkFn" - linking fn of a single directive 6531 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6532 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6533 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6534 */ 6535 6536 6537/** 6538 * @ngdoc service 6539 * @name $compile 6540 * @kind function 6541 * 6542 * @description 6543 * Compiles an HTML string or DOM into a template and produces a template function, which 6544 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6545 * 6546 * The compilation is a process of walking the DOM tree and matching DOM elements to 6547 * {@link ng.$compileProvider#directive directives}. 6548 * 6549 * <div class="alert alert-warning"> 6550 * **Note:** This document is an in-depth reference of all directive options. 6551 * For a gentle introduction to directives with examples of common use cases, 6552 * see the {@link guide/directive directive guide}. 6553 * </div> 6554 * 6555 * ## Comprehensive Directive API 6556 * 6557 * There are many different options for a directive. 6558 * 6559 * The difference resides in the return value of the factory function. 6560 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6561 * or just the `postLink` function (all other properties will have the default values). 6562 * 6563 * <div class="alert alert-success"> 6564 * **Best Practice:** It's recommended to use the "directive definition object" form. 6565 * </div> 6566 * 6567 * Here's an example directive declared with a Directive Definition Object: 6568 * 6569 * ```js 6570 * var myModule = angular.module(...); 6571 * 6572 * myModule.directive('directiveName', function factory(injectables) { 6573 * var directiveDefinitionObject = { 6574 * priority: 0, 6575 * template: '<div></div>
6575', // or // function(tElement, tAttrs) { ... }, 6576 * // or 6577 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6578 * transclude: false, 6579 * restrict: 'A', 6580 * templateNamespace: 'html', 6581 * scope: false, 6582 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6583 * controllerAs: 'stringIdentifier', 6584 * bindToController: false, 6585 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6586 * compile: function compile(tElement, tAttrs, transclude) { 6587 * return { 6588 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6589 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6590 * } 6591 * // or 6592 * // return function postLink( ... ) { ... } 6593 * }, 6594 * // or 6595 * // link: { 6596 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6597 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6598 * // } 6599 * // or 6600 * // link: function postLink( ... ) { ... } 6601 * }; 6602 * return directiveDefinitionObject; 6603 * }); 6604 * ``` 6605 * 6606 * <div class="alert alert-warning"> 6607 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6608 * </div> 6609 * 6610 * Therefore the above can be simplified as: 6611 * 6612 * ```js 6613 * var myModule = angular.module(...); 6614 * 6615 * myModule.directive('directiveName', function factory(injectables) { 6616 * var directiveDefinitionObject = { 6617 * link: function postLink(scope, iElement, iAttrs) { ... } 6618 * }; 6619 * return directiveDefinitionObject; 6620 * // or 6621 * // return function postLink(scope, iElement, iAttrs) { ... } 6622 * }); 6623 * ``` 6624 * 6625 * 6626 * 6627 * ### Directive Definition Object 6628 * 6629 * The directive definition object provides instructions to the {@link ng.$compile 6630 * compiler}. The attributes are: 6631 * 6632 * #### `multiElement` 6633 * When this property is set to true, the HTML compiler will collect DOM nodes between 6634 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6635 * together as the directive elements. It is recommended that this feature be used on directives 6636 * which are not strictly behavioral (such as {@link ngClick}), and which 6637 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6638 * 6639 * #### `priority` 6640 * When there are multiple directives defined on a single DOM element, sometimes it 6641 * is necessary to specify the order in which the directives are applied. The `priority` is used 6642 * to sort the directives before their `compile` functions get called. Priority is defined as a 6643 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6644 * are also run in priority order, but post-link functions are run in reverse order. The order 6645 * of directives with the same priority is undefined. The default priority is `0`. 6646 * 6647 * #### `terminal` 6648 * If set to true then the current `priority` will be the last set of directives 6649 * which will execute (any directives at the current priority will still execute 6650 * as the order of execution on same `priority` is undefined). Note that expressions 6651 * and other directives used in the directive's template will also be excluded from execution. 6652 * 6653 * #### `scope` 6654 * The scope property can be `true`, an object or a falsy value: 6655 * 6656 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 6657 * 6658 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 6659 * the directive's element. If multiple directives on the same element request a new scope, 6660 * only one new scope is created. The new scope rule does not apply for the root of the template 6661 * since the root of the template always gets a new scope. 6662 * 6663 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 6664 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 6665 * scope. This is useful when creating reusable components, which should not accidentally read or modify 6666 * data in the parent scope. 6667 * 6668 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 6669 * directive's element. These local properties are useful for aliasing values for templates. The keys in 6670 * the object hash map to the name of the property on the isolate scope; the values define how the property 6671 * is bound to the parent scope, via matching attributes on the directive's element: 6672 * 6673 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 6674 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6675 * attribute name is assumed to be the same as the local name. 6676 * Given `<widget my-attr="hello {{name}}">` and widget definition 6677 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 6678 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 6679 * `localName` property on the widget scope. The `name` is read from the parent scope (not 6680 * component scope). 6681 * 6682 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 6683 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 6684 * name is specified then the attribute name is assumed to be the same as the local name. 6685 * Given `<widget my-attr="parentModel">` and widget definition of 6686 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 6687 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6688 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 6689 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 6690 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 6691 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 6692 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 6693 * 6694 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 6695 * If no `attr` name is specified then the attribute name is assumed to be the same as the 6696 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 6697 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 6698 * a function wrapper for the `count = count + value` expression. Often it's desirable to 6699 * pass data from the isolated scope via an expression to the parent scope, this can be 6700 * done by passing a map of local variable names and values into the expression wrapper fn. 6701 * For example, if the expression is `increment(amount)` then we can specify the amount value 6702 * by calling the `localFn` as `localFn({amount: 22})`. 6703 * 6704 * In general it's possible to apply more than one directive to one element, but there might be limitations 6705 * depending on the type of scope required by the directives. The following points will help explain these limitations. 6706 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 6707 * 6708 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 6709 * * **child scope** + **no scope** => Both directives will share one single child scope 6710 * * **child scope** + **child scope** => Both directives will share one single child scope 6711 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6712 * its parent's scope 6713 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 6714 * be applied to the same element. 6715 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 6716 * cannot be applied to the same element. 6717 * 6718 * 6719 * #### `bindToController` 6720 * This property is used to bind scope properties directly to the controller. It can be either 6721 * `true` or an object hash with the same format as the `scope` property. Additionally, a controller 6722 * alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller 6723 * definition: `controller: 'myCtrl as myAlias'`. 6724 * 6725 * When an isolate scope is used for a directive (see above), `bindToController: true` will 6726 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 6727 * is instantiated, the initial values of the isolate scope bindings will be available if the controller is not an ES6 class. 6728 * 6729 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 6730 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 6731 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 6732 * scope (useful for component directives). 6733 * 6734 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 6735 * 6736 * 6737 * #### `controller` 6738 * Controller constructor function. The controller is instantiated before the 6739 * pre-linking phase and can be accessed by other directives (see 6740 * `require` attribute). This allows the directives to communicate with each other and augment 6741 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6742 * 6743 * * `$scope` - Current scope associated with the element 6744 * * `$element` - Current element 6745 * * `$attrs` - Current attributes object for the element 6746 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6747 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 6748 * * `scope`: (optional) override the scope. 6749 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 6750 * * `futureParentElement` (optional): 6751 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6752 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6753 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6754 * and when the `cloneLinkinFn` is passed, 6755 * as those elements need to created and cloned in a special way when they are defined outside their 6756 * usual containers (e.g. like `<svg>`). 6757 * * See also the `directive.templateNamespace` property. 6758 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 6759 * then the default translusion is provided. 6760 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 6761 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 6762 * 6763 * #### `require` 6764 * Require another directive and inject its controller as the fourth argument to the linking function. The 6765 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 6766 * injected argument will be an array in corresponding order. If no such directive can be 6767 * found, or if the directive does not have a controller, then an error is raised (unless no link function 6768 * is specified, in which case error checking is skipped). The name can be prefixed with: 6769 * 6770 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6771 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6772 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6773 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6774 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6775 * `null` to the `link` fn if not found. 6776 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6777 * `null` to the `link` fn if not found. 6778 * 6779 * 6780 * #### `controllerAs` 6781 * Identifier name for a reference to the controller in the directive's scope. 6782 * This allows the controller to be referenced from the directive template. This is especially 6783 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 6784 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 6785 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 6786 * 6787 * 6788 * #### `restrict` 6789 * String of subset of `EACM` which restricts the directive to a specific directive 6790 * declaration style. If omitted, the defaults (elements and attributes) are used. 6791 * 6792 * * `E` - Element name (default): `<my-directive></my-directive>` 6793 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6794 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6795 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6796 * 6797 * 6798 * #### `templateNamespace` 6799 * String representing the document type used by the markup in the template. 6800 * AngularJS needs this information as those elements need to be created and cloned 6801 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6802 * 6803 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6804 * top-level elements such as `<svg>` or `<math>`. 6805 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6806 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6807 * 6808 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6809 * 6810 * #### `template` 6811 * HTML markup that may: 6812 * * Replace the contents of the directive's element (default). 6813 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6814 * * Wrap the contents of the directive's element (if `transclude` is true). 6815 * 6816 * Value may be: 6817 * 6818 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6819 * * A function which takes two arguments `tElement` and `tAttrs` (described
6819in the `compile` 6820 * function api below) and returns a string value. 6821 * 6822 * 6823 * #### `templateUrl` 6824 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6825 * 6826 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6827 * for later when the template has been resolved. In the meantime it will continue to compile and link 6828 * sibling and parent elements as though this element had not contained any directives. 6829 * 6830 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6831 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6832 * case when only one deeply nested directive has `templateUrl`. 6833 * 6834 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6835 * 6836 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 6837 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6838 * a string value representing the url. In either case, the template URL is passed through {@link 6839 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6840 * 6841 * 6842 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6843 * specify what the template should replace. Defaults to `false`. 6844 * 6845 * * `true` - the template will replace the directive's element. 6846 * * `false` - the template will replace the contents of the directive's element. 6847 * 6848 * The replacement process migrates all of the attributes / classes from the old element to the new 6849 * one. See the {@link guide/directive#template-expanding-directive 6850 * Directives Guide} for an example. 6851 * 6852 * There are very few scenarios where element replacement is required for the application function, 6853 * the main one being reusable custom components that are used within SVG contexts 6854 * (because SVG doesn't work with custom elements in the DOM tree). 6855 * 6856 * #### `transclude` 6857 * Extract the contents of the element where the directive appears and make it available to the directive. 6858 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6859 * {@link $compile#transclusion Transclusion} section below. 6860 * 6861 * 6862 * #### `compile` 6863 * 6864 * ```js 6865 * function compile(tElement, tAttrs, transclude) { ... } 6866 * ``` 6867 * 6868 * The compile function deals with transforming the template DOM. Since most directives do not do 6869 * template transformation, it is not used often. The compile function takes the following arguments: 6870 * 6871 * * `tElement` - template element - The element where the directive has been declared. It is 6872 * safe to do template transformation on the element and child elements only. 6873 * 6874 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6875 * between all directive compile functions. 6876 * 6877 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6878 * 6879 * <div class="alert alert-warning"> 6880 * **Note:** The template instance and the link instance may be different objects if the template has 6881 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6882 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 6883 * should be done in a linking function rather than in a compile function. 6884 * </div> 6885 6886 * <div class="alert alert-warning"> 6887 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6888 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6889 * stack overflow errors. 6890 * 6891 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6892 * a directive's template instead of relying on automatic template compilation via `template` or 6893 * `templateUrl` declaration or manual compilation inside the compile function. 6894 * </div> 6895 * 6896 * <div class="alert alert-danger"> 6897 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6898 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6899 * to the link function instead. 6900 * </div> 6901 6902 * A compile function can have a return value which can be either a function or an object. 6903 * 6904 * * returning a (post-link) function - is equivalent to registering the linking function via the 6905 * `link` property of the config object when the compile function is empty. 6906 * 6907 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6908 * control when a linking function should be called during the linking phase. See info about 6909 * pre-linking and post-linking functions below. 6910 * 6911 * 6912 * #### `link` 6913 * This property is used only if the `compile` property is not defined. 6914 * 6915 * ```js 6916 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6917 * ``` 6918 * 6919 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6920 * executed after the template has been cloned. This is where most of the directive logic will be 6921 * put. 6922 * 6923 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6924 * directive for registering {@link ng.$rootScope.Scope#$watch w
6924atches}. 6925 * 6926 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6927 * manipulate the children of the element only in `postLink` function since the children have 6928 * already been linked. 6929 * 6930 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6931 * between all directive linking functions. 6932 * 6933 * * `controller` - the directive's required controller instance(s) - Instances are shared 6934 * among all directives, which allows the directives to use the controllers as a communication 6935 * channel. The exact value depends on the directive's `require` property: 6936 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 6937 * * `string`: the controller instance 6938 * * `array`: array of controller instances 6939 * 6940 * If a required controller cannot be found, and it is optional, the instance is `null`, 6941 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 6942 * 6943 * Note that you can also require the directive's own controller - it will be made available like 6944 * any other controller. 6945 * 6946 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6947 * This is the same as the `$transclude` 6948 * parameter of directive controllers, see there for details. 6949 * `function([scope], cloneLinkingFn, futureParentElement)`. 6950 * 6951 * #### Pre-linking function 6952 * 6953 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6954 * compiler linking function will fail to locate the correct elements for linking. 6955 * 6956 * #### Post-linking function 6957 * 6958 * Executed after the child elements are linked. 6959 * 6960 * Note that child elements that contain `templateUrl` directives will not have been compiled 6961 * and linked since they are waiting for their template to load asynchronously and their own 6962 * compilation and linking has been suspended until that occurs. 6963 * 6964 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6965 * for their async templates to be resolved. 6966 * 6967 * 6968 * ### Transclusion 6969 * 6970 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 6971 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6972 * scope from where they were taken. 6973 * 6974 * Transclusion is used (often with {@link ngTransclude}) to insert the 6975 * original contents of a directive's element into a specified place in the template of the directive. 6976 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6977 * content has access to the properties on the scope from which it was taken, even if the directive 6978 * has isolated scope. 6979 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6980 * 6981 * This makes it possible for the widget to have private state for its template, while the transcluded 6982 * content has access to its originating scope. 6983 * 6984 * <div class="alert alert-warning"> 6985 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6986 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6987 * Testing Transclusion Directives}. 6988 * </div> 6989 * 6990 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 6991 * directive's element, the entire element or multiple parts of the element contents: 6992 * 6993 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6994 * * `'element'` - transclude the whole of the directive's element including any directives on this 6995 * element that defined at a lower priority than this directive. When used, the `template` 6996 * property is ignored. 6997 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 6998 * 6999 * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. 7000 * 7001 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 7002 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 7003 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 7004 * 7005 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7006 * 7007 * If the element selector is prefixed with a `?` then that slot is optional. 7008 * 7009 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 7010 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 7011 *
7012 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 7013 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 7014 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 7015 * injectable into the directive's controller. 7016 * 7017 * 7018 * #### Transclusion Functions 7019 * 7020 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 7021 * function** to the directive's `link` function and `controller`. This transclusion function is a special 7022 * **linking function** that will return the compiled contents linked to a new transclusion scope. 7023 * 7024 * <div class="alert alert-info"> 7025 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 7026 * ngTransclude will deal with it for us. 7027 * </div> 7028 * 7029 * If you want to manually control the insertion and removal of the transcluded content in your directive 7030 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 7031 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 7032 * 7033 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 7034 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 7035 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 7036 * 7037 * <div class="alert alert-info"> 7038 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 7039 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 7040 * </div> 7041 * 7042 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 7043 * attach function**: 7044 * 7045 * ```js 7046 * var transcludedContent, transclusionScope; 7047 * 7048 * $transclude(function(clone, scope) { 7049 * element.append(clone); 7050 * transcludedContent = clone; 7051 * transclusionScope = scope; 7052 * }); 7053 * ``` 7054 * 7055 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 7056 * associated transclusion scope: 7057 * 7058 * ```js 7059 * transcludedContent.remove(); 7060 * transclusionScope.$destroy(); 7061 * ``` 7062 * 7063 * <div class="alert alert-info"> 7064 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 7065 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 7066 * then you are also responsible for calling `$destroy` on the transclusion scope. 7067 * </div> 7068 * 7069 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 7070 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 7071 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 7072 * 7073 * 7074 * #### Transclusion Scopes 7075 * 7076 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 7077 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 7078 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 7079 * was taken. 7080 * 7081 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 7082 * like this: 7083 * 7084 * ```html 7085 * <div ng-app> 7086 * <div isolate> 7087 * <div transclusion> 7088 * </div> 7089 * </div> 7090 * </div> 7091 * ``` 7092 * 7093 * The `$parent` scope hierarchy will look like this: 7094 * 7095 ``` 7096 - $rootScope 7097 - isolate 7098 - transclusion 7099 ``` 7100 * 7101 * but the scopes will inherit prototypically from different scopes to their `$parent`. 7102 * 7103 ``` 7104 - $rootScope 7105 - transclusion 7106 - isolate 7107 ``` 7108 * 7109 * 7110 * ### Attributes 7111 * 7112 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 7113 * `link()` or `compile()` functions. It has a variety of uses. 7114 * 7115 * accessing *Normalized attribute names:* 7116 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 7117 * the attributes object allows for normalized access to 7118 * the attributes. 7119 * 7120 * * *Directive inter-communication:* All directives share the same instance of the attributes 7121 * object which allows the directives to use the attributes object as inter directive 7122 * communication. 7123 * 7124 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 7125 * allowing other directives to read the interpolated value. 7126 * 7127 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 7128 * that contain interpolation (e.g. `src="{{bar}}
7128"`). Not only is this very efficient but it's also 7129 * the only way to easily get the actual value because during the linking phase the interpolation 7130 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 7131 * 7132 * ```js 7133 * function linkingFn(scope, elm, attrs, ctrl) { 7134 * // get the attribute value 7135 * console.log(attrs.ngModel); 7136 * 7137 * // change the attribute 7138 * attrs.$set('ngModel', 'new value'); 7139 * 7140 * // observe changes to interpolated attribute 7141 * attrs.$observe('ngModel', function(value) { 7142 * console.log('ngModel has changed value to ' + value); 7143 * }); 7144 * } 7145 * ``` 7146 * 7147 * ## Example 7148 * 7149 * <div class="alert alert-warning"> 7150 * **Note**: Typically directives are registered with `module.directive`. The example below is 7151 * to illustrate how `$compile` works. 7152 * </div> 7153 * 7154 <example module="compileExample"> 7155 <file name="index.html"> 7156 <script> 7157 angular.module('compileExample', [], function($compileProvider) { 7158 // configure new 'compile' directive by passing a directive 7159 // factory function. The factory function injects the '$compile' 7160 $compileProvider.directive('compile', function($compile) { 7161 // directive factory creates a link function 7162 return function(scope, element, attrs) { 7163 scope.$watch( 7164 function(scope) { 7165 // watch the 'compile' expression for changes 7166 return scope.$eval(attrs.compile); 7167 }, 7168 function(value) { 7169 // when the 'compile' expression changes 7170 // assign it into the current DOM 7171 element.html(value); 7172 7173 // compile the new DOM and link it to the current 7174 // scope. 7175 // NOTE: we only compile .childNodes so that 7176 // we don't get into infinite loop compiling ourselves 7177 $compile(element.contents())(scope); 7178 } 7179 ); 7180 }; 7181 }); 7182 }) 7183 .controller('GreeterController', ['$scope', function($scope) { 7184 $scope.name = 'Angular'; 7185 $scope.html = 'Hello {{name}}'; 7186 }]); 7187 </script> 7188 <div ng-controller="GreeterController"> 7189 <input ng-model="name"> <br/> 7190 <textarea ng-model="html"></textarea> <br/> 7191 <div compile="html"></div> 7192 </div> 7193 </file> 7194 <file name="protractor.js" type="protractor"> 7195 it('should auto compile', function() { 7196 var textarea = $('textarea'); 7197 var output = $('div[compile]'); 7198 // The initial state reads 'Hello Angular'. 7199 expect(output.getText()).toBe('Hello Angular'); 7200 textarea.clear(); 7201 textarea.sendKeys('{{name}}!'); 7202 expect(output.getText()).toBe('Angular!'); 7203 }); 7204 </file> 7205 </example> 7206 7207 * 7208 * 7209 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 7210 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 7211 * 7212 * <div class="alert alert-danger"> 7213 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 7214 * e.g. will not use the right outer scope. Please pass the transclude function as a 7215 * `parentBoundTranscludeFn` to the link function instead. 7216 * </div> 7217 * 7218 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 7219 * root element(s), not their children) 7220 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 7221 * (a DOM element/tree) to a scope. Where: 7222 * 7223 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 7224 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 7225 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 7226 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 7227 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 7228 * 7229 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 7230 * * `scope` - is the current scope with which the linking function is working with. 7231 * 7232 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 7233 * keys may be used to control linking behavior: 7234 * 7235 * * `parentBoundTranscludeFn` - the transclude function made available to 7236 * directives; if given, it will be passed through to the link functions of 7237 * directives found in `element` during compilation. 7238 * * `transcludeControllers` - an object hash with keys that map controller names 7239 * to controller instances; if given, it will make the controllers 7240 * available to directives. 7241 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 7242 * the cloned elements; only needed for transcludes that are allowed to contain non html 7243 * elements (e.g. SVG elements). See also the directive.controller property. 7244 * 7245 * Calling the linking function returns the element of the template. It is either the original 7246 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 7247 * 7248 * After linking the view is not updated until after a call to $digest which typically is done by 7249 * Angular automatically. 7250 * 7251 * If you need access to the bound view, there are two ways to do it: 7252 * 7253 * - If you are not asking the linking function to clone the template, create the DOM element(s) 7254 * before you send them to the compiler and keep this reference around. 7255 * ```js 7256 * var element = $compile('<p>{{total}}</p>')(scope); 7257 * ``` 7258 * 7259 * - if on the other hand, you need the element to be cloned, the view reference from the original 7260 * example would not point to the clone, but rather to the original template that was cloned. In 7261 * this case, you can access the clone via the cloneAttachFn: 7262 * ```js 7263 * var templateElement = angular.element('<p>{{total}}</p>'), 7264 * scope = ....; 7265 * 7266 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 7267 * //attach the clone to DOM document at the right place 7268 * }); 7269 * 7270 * //now we have reference to the cloned DOM via `clonedElement` 7271 * ``` 7272 * 7273 * 7274 * For information on how the compiler works, see the 7275 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 7276 */ 7277 7278var $compileMinErr = minErr('$compile'); 7279 7280/** 7281 * @ngdoc provider 7282 * @name $compileProvider 7283 * 7284 * @description 7285 */
vendor: 8,227 bytes, lines 7286-7470
7286$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 7287function $CompileProvider($provide, $$sanitizeUriProvider) { 7288 var hasDirectives = {}, 7289 Suffix = 'Directive', 7290 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 7291 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 7292 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 7293 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 7294 7295 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 7296 // The assumption is that future DOM event attribute names will begin with 7297 // 'on' and be composed of only English letters. 7298 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 7299 7300 function parseIsolateBindings(scope, directiveName, isController) { 7301 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 7302 7303 var bindings = {}; 7304 7305 forEach(scope, function(definition, scopeName) { 7306 var match = definition.match(LOCAL_REGEXP); 7307 7308 if (!match) { 7309 throw $compileMinErr('iscp', 7310 "Invalid {3} for directive '{0}'." + 7311 " Definition: {... {1}: '{2}' ...}", 7312 directiveName, scopeName, definition, 7313 (isController ? "controller bindings definition" : 7314 "isolate scope definition")); 7315 } 7316 7317 bindings[scopeName] = { 7318 mode: match[1][0], 7319 collection: match[2] === '*', 7320 optional: match[3] === '?', 7321 attrName: match[4] || scopeName 7322 }; 7323 }); 7324 7325 return bindings; 7326 } 7327 7328 function parseDirectiveBindings(directive, directiveName) { 7329 var bindings = { 7330 isolateScope: null, 7331 bindToController: null 7332 }; 7333 if (isObject(directive.scope)) { 7334 if (directive.bindToController === true) { 7335 bindings.bindToController = parseIsolateBindings(directive.scope, 7336 directiveName, true); 7337 bindings.isolateScope = {}; 7338 } else { 7339 bindings.isolateScope = parseIsolateBindings(directive.scope, 7340 directiveName, false); 7341 } 7342 } 7343 if (isObject(directive.bindToController)) { 7344 bindings.bindToController = 7345 parseIsolateBindings(directive.bindToController, directiveName, true); 7346 } 7347 if (isObject(bindings.bindToController)) { 7348 var controller = directive.controller; 7349 var controllerAs = directive.controllerAs; 7350 if (!controller) { 7351 // There is no controller, there may or may not be a controllerAs property 7352 throw $compileMinErr('noctrl', 7353 "Cannot bind to controller without directive '{0}'s controller.", 7354 directiveName); 7355 } else if (!identifierForController(controller, controllerAs)) { 7356 // There is a controller, but no identifier or controllerAs property 7357 throw $compileMinErr('noident', 7358 "Cannot bind to controller without identifier for directive '{0}'.", 7359 directiveName); 7360 } 7361 } 7362 return bindings; 7363 } 7364 7365 function assertValidDirectiveName(name) { 7366 var letter = name.charAt(0); 7367 if (!letter || letter !== lowercase(letter)) { 7368 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name); 7369 } 7370 if (name !== name.trim()) { 7371 throw $compileMinErr('baddir', 7372 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 7373 name); 7374 } 7375 } 7376 7377 /** 7378 * @ngdoc method 7379 * @name $compileProvider#directive 7380 * @kind function 7381 * 7382 * @description 7383 * Register a new directive with the compiler. 7384 * 7385 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 7386 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 7387 * names and the values are the factories. 7388 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 7389 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 7390 * @returns {ng.$compileProvider} Self for chaining. 7391 */ 7392 this.directive = function registerDirective(name, directiveFactory) { 7393 assertNotHasOwnProperty(name, 'directive'); 7394 if (isString(name)) { 7395 assertValidDirectiveName(name); 7396 assertArg(directiveFactory, 'directiveFactory'); 7397 if (!hasDirectives.hasOwnProperty(name)) { 7398 hasDirectives[name] = []; 7399 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 7400 function($injector, $exceptionHandler) { 7401 var directives = []; 7402 forEach(hasDirectives[name], function(directiveFactory, index) { 7403 try { 7404 var directive = $injector.invoke(directiveFactory); 7405 if (isFunction(directive)) { 7406 directive = { compile: valueFn(directive) }; 7407 } else if (!directive.compile && directive.link) { 7408 directive.compile = valueFn(directive.link); 7409 } 7410 directive.priority = directive.priority || 0; 7411 directive.index = index; 7412 directive.name = directive.name || name; 7413 directive.require = directive.require || (directive.controller && directive.name); 7414 directive.restrict = directive.restrict || 'EA'; 7415 var bindings = directive.$$bindings = 7416 parseDirectiveBindings(directive, directive.name); 7417 if (isObject(bindings.isolateScope)) { 7418 directive.$$isolateBindings = bindings.isolateScope; 7419 } 7420 directive.$$moduleName = directiveFactory.$$moduleName; 7421 directives.push(directive); 7422 } catch (e) { 7423 $exceptionHandler(e); 7424 } 7425 }); 7426 return directives; 7427 }]); 7428 } 7429 hasDirectives[name].push(directiveFactory); 7430 } else { 7431 forEach(name, reverseParams(registerDirective)); 7432 } 7433 return this; 7434 }; 7435 7436 /** 7437 * @ngdoc method 7438 * @name $compileProvider#component 7439 * @module ng 7440 * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`) 7441 * @param {Object} options Component definition object (a simplified 7442 * {@link ng.$compile#directive-definition-object directive definition object}), 7443 * with the following properties (all optional): 7444 * 7445 * - `controller` â `{(string|function()=}` â controller constructor function that should be 7446 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 7447 * registered controller} if passed as a string. An empty `noop` function by default. 7448 * - `controllerAs` â `{string=}` â identifier name for to reference the controller in the component's scope. 7449 * If present, the controller will be published to scope under the `controllerAs` name. 7450 * If not present, this will default to be `$ctrl`. 7451 * - `template` â `{string=|function()=}` â html template as a string or a function that 7452 * returns an html template as a string which should be used as the contents of this component. 7453 * Empty string by default. 7454 * 7455 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 7456 * the following locals: 7457 * 7458 * - `$element` - Current element 7459 * - `$attrs` - Current attributes object for the element 7460 * 7461 * - `templateUrl` â `{string=|function()=}` â path or function that returns a path to an html 7462 * template that should be used as the contents of this component. 7463 * 7464 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 7465 * the following locals: 7466 * 7467 * - `$element` - Current element 7468 * - `$attrs` - Current attributes object for the element 7469 * 7470 * - `bindings` â `{object=}` â defines bindings between DOM attributes and component properties.
7471 * Component properties are always bound to the component controller and not to the scope. 7472 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 7473 * - `transclude` â `{boolean=}` â whether {@link $compile#transclusion content transclusion} is enabled. 7474 * Disabled by default. 7475 * - `$...` â `{function()=}` â additional annotations to provide to the directive factory function. 7476 * 7477 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 7478 * @description 7479 * Register a **component definition** with the compiler. This is a shorthand for registering a special 7480 * type of directive, which represents a self-contained UI component in your application. Such components 7481 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 7482 * 7483 * Component definitions are very simple and do not require as much configuration as defining general 7484 * directives. Component definitions usually consist only of a template and a controller backing it. 7485 * 7486 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 7487 * `bindToController`. They always have **isolate scope** and are restricted to elements. 7488 * 7489 * Here are a few examples of how you would usually define components: 7490 * 7491 * ```js 7492 * var myMod = angular.module(...); 7493 * myMod.component('myComp', { 7494 * template: '<div>My name is {{$ctrl.name}}</div>', 7495 * controller: function() { 7496 * this.name = 'shahar'; 7497 * } 7498 * }); 7499 * 7500 * myMod.component('myComp', { 7501 * template: '<div>My name is {{$ctrl.name}}</div>', 7502 * bindings: {name: '@'} 7503 * }); 7504 * 7505 * myMod.component('myComp', { 7506 * templateUrl: 'views/my-comp.html', 7507 * controller: 'MyCtrl as ctrl', 7508 * bindings: {name: '@'} 7509 * }); 7510 * 7511 * ``` 7512 * 7513 * <br /> 7514 * Components are also useful as route templates (e.g. when using 7515 * {@link ngRoute ngRoute}): 7516 * 7517 * ```js 7518 * var myMod = angular.module('myMod', ['ngRoute']); 7519 * 7520 * myMod.component('home', { 7521 * template: '<h1>Home</h1><p>Hello, {{ home.user.name }} !</p>', 7522 * controller: function() { 7523 * this.user = {name: 'world'}; 7524 * } 7525 * }); 7526 * 7527 * myMod.config(function($routeProvider) { 7528 * $routeProvider.when('/', { 7529 * template: '<home></home>' 7530 * }); 7531 * }); 7532 * ``` 7533 * 7534 * <br /> 7535 * When using {@link ngRoute.$routeProvider $routeProvider}, you can often avoid some 7536 * boilerplate, by assigning the resolved dependencies directly on the route scope: 7537 * 7538 * ```js 7539 * var myMod = angular.module('myMod', ['ngRoute']); 7540 * 7541 * myMod.component('home', { 7542 * template: '<h1>Home</h1><p>Hello, {{ home.user.name }} !</p>', 7543 * bindings: {user: '='} 7544 * }); 7545 * 7546 * myMod.config(function($routeProvider) { 7547 * $routeProvider.when('/', { 7548 * template: '<home user="$resolve.user"></home>', 7549 * resolve: {user: function($http) { return $http.get('...'); }} 7550 * }); 7551 * }); 7552 * ``` 7553 * 7554 * <br /> 7555 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 7556 */ 7557 this.component = function registerComponent(name, options) { 7558 var controller = options.controller || function() {}; 7559 7560 function factory($injector) { 7561 function makeInjectable(fn) { 7562 if (isFunction(fn) || isArray(fn)) {
vendor: 10,466 bytes, lines 7563-7838
7563 return function(tElement, tAttrs) { 7564 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 7565 }; 7566 } else { 7567 return fn; 7568 } 7569 } 7570 7571 var template = (!options.template && !options.templateUrl ? '' : options.template); 7572 return { 7573 controller: controller, 7574 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 7575 template: makeInjectable(template), 7576 templateUrl: makeInjectable(options.templateUrl), 7577 transclude: options.transclude, 7578 scope: {}, 7579 bindToController: options.bindings || {}, 7580 restrict: 'E' 7581 }; 7582 } 7583 7584 // Copy any annotation properties (starting with $) over to the factory function 7585 // These could be used by libraries such as the new component router 7586 forEach(options, function(val, key) { 7587 if (key.charAt(0) === '$') { 7588 factory[key] = val; 7589 } 7590 }); 7591 7592 factory.$inject = ['$injector']; 7593 7594 return this.directive(name, factory); 7595 }; 7596 7597 7598 /** 7599 * @ngdoc method 7600 * @name $compileProvider#aHrefSanitizationWhitelist 7601 * @kind function 7602 * 7603 * @description 7604 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7605 * urls during a[href] sanitization. 7606 * 7607 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 7608 * 7609 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 7610 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 7611 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7612 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7613 * 7614 * @param {RegExp=} regexp New regexp to whitelist urls with. 7615 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7616 * chaining otherwise. 7617 */ 7618 this.aHrefSanitizationWhitelist = function(regexp) { 7619 if (isDefined(regexp)) { 7620 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 7621 return this; 7622 } else { 7623 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 7624 } 7625 }; 7626 7627 7628 /** 7629 * @ngdoc method 7630 * @name $compileProvider#imgSrcSanitizationWhitelist 7631 * @kind function 7632 * 7633 * @description 7634 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7635 * urls during img[src] sanitization. 7636 * 7637 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 7638 * 7639 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 7640 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 7641 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7642 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7643 * 7644 * @param {RegExp=} regexp New regexp to whitelist urls with. 7645 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7646 * chaining otherwise. 7647 */ 7648 this.imgSrcSanitizationWhitelist = function(regexp) { 7649 if (isDefined(regexp)) { 7650 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 7651 return this; 7652 } else { 7653 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7654 } 7655 }; 7656 7657 /** 7658 * @ngdoc method 7659 * @name $compileProvider#debugInfoEnabled 7660 * 7661 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7662 * current debugInfoEnabled state 7663 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7664 * 7665 * @kind function 7666 * 7667 * @description 7668 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7669 * binding information and a reference to the current scope on to DOM elements. 7670 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7671 * * `ng-binding` CSS class 7672 * * `$binding` data property containing an array of the binding expressions 7673 * 7674 * You may want to disable this in production for a significant performance boost. See 7675 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7676 * 7677 * The default value is true. 7678 */ 7679 var debugInfoEnabled = true; 7680 this.debugInfoEnabled = function(enabled) { 7681 if (isDefined(enabled)) { 7682 debugInfoEnabled = enabled; 7683 return this; 7684 } 7685 return debugInfoEnabled; 7686 }; 7687 7688 this.$get = [ 7689 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7690 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 7691 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7692 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 7693 7694 var SIMPLE_ATTR_NAME = /^\w/; 7695 var specialAttrHolder = document.createElement('div'); 7696 var Attributes = function(element, attributesToCopy) { 7697 if (attributesToCopy) { 7698 var keys = Object.keys(attributesToCopy); 7699 var i, l, key; 7700 7701 for (i = 0, l = keys.length; i < l; i++) { 7702 key = keys[i]; 7703 this[key] = attributesToCopy[key]; 7704 } 7705 } else { 7706 this.$attr = {}; 7707 } 7708 7709 this.$$element = element; 7710 }; 7711 7712 Attributes.prototype = { 7713 /** 7714 * @ngdoc method 7715 * @name $compile.directive.Attributes#$normalize 7716 * @kind function 7717 * 7718 * @description 7719 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7720 * `data-`) to its normalized, camelCase form. 7721 * 7722 * Also there is special case for Moz prefix starting with upper case letter. 7723 * 7724 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7725 * 7726 * @param {string} name Name to normalize 7727 */ 7728 $normalize: directiveNormalize, 7729 7730 7731 /** 7732 * @ngdoc method 7733 * @name $compile.directive.Attributes#$addClass 7734 * @kind function 7735 * 7736 * @description 7737 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7738 * are enabled then an animation will be triggered for the class addition. 7739 * 7740 * @param {string} classVal The className value that will be added to the element 7741 */ 7742 $addClass: function(classVal) { 7743 if (classVal && classVal.length > 0) { 7744 $animate.addClass(this.$$element, classVal); 7745 } 7746 }, 7747 7748 /** 7749 * @ngdoc method 7750 * @name $compile.directive.Attributes#$removeClass 7751 * @kind function 7752 * 7753 * @description 7754 * Removes the CSS class value specified by the classVal parameter from the element. If 7755 * animations are enabled then an animation will be triggered for the class removal. 7756 * 7757 * @param {string} classVal The className value that will be removed from the element 7758 */ 7759 $removeClass: function(classVal) { 7760 if (classVal && classVal.length > 0) { 7761 $animate.removeClass(this.$$element, classVal); 7762 } 7763 }, 7764 7765 /** 7766 * @ngdoc method 7767 * @name $compile.directive.Attributes#$updateClass 7768 * @kind function 7769 * 7770 * @description 7771 * Adds and removes the appropriate CSS class values to the element based on the difference 7772 * between the new and old CSS class values (specified as newClasses and oldClasses). 7773 * 7774 * @param {string} newClasses The current CSS className value 7775 * @param {string} oldClasses The former CSS className value 7776 */ 7777 $updateClass: function(newClasses, oldClasses) { 7778 var toAdd = tokenDifference(newClasses, oldClasses); 7779 if (toAdd && toAdd.length) { 7780 $animate.addClass(this.$$element, toAdd); 7781 } 7782 7783 var toRemove = tokenDifference(oldClasses, newClasses); 7784 if (toRemove && toRemove.length) { 7785 $animate.removeClass(this.$$element, toRemove); 7786 } 7787 }, 7788 7789 /** 7790 * Set a normalized attribute on the element in a way such that all directives 7791 * can share the attribute. This function properly handles boolean attributes. 7792 * @param {string} key Normalized key. (ie ngAttribute) 7793 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7794 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7795 * Defaults to true. 7796 * @param {string=} attrName Optional none normalized name. Defaults to key. 7797 */ 7798 $set: function(key, value, writeAttr, attrName) { 7799 // TODO: decide whether or not to throw an error if "class" 7800 //is set through this function since it may cause $updateClass to 7801 //become unstable. 7802 7803 var node = this.$$element[0], 7804 booleanKey = getBooleanAttrName(node, key), 7805 aliasedKey = getAliasedAttrName(key), 7806 observer = key, 7807 nodeName; 7808 7809 if (booleanKey) { 7810 this.$$element.prop(key, value); 7811 attrName = booleanKey; 7812 } else if (aliasedKey) { 7813 this[aliasedKey] = value; 7814 observer = aliasedKey; 7815 } 7816 7817 this[key] = value; 7818 7819 // translate normalized key to actual key 7820 if (attrName) { 7821 this.$attr[key] = attrName; 7822 } else { 7823 attrName = this.$attr[key]; 7824 if (!attrName) { 7825 this.$attr[key] = attrName = snake_case(key, '-'); 7826 } 7827 } 7828 7829 nodeName = nodeName_(this.$$element); 7830 7831 if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || 7832 (nodeName === 'img' && key === 'src')) { 7833 // sanitize a[href] and img[src] values 7834 this[key] = value = $$sanitizeUri(value, key === 'src'); 7835 } else if (nodeName === 'img' && key === 'srcset') { 7836 // sanitize img[srcset] values 7837 var result = ""; 7838
7839 // first check if there are spaces because it's not the same pattern 7840 var trimmedSrcset = trim(value); 7841 // ( 999x ,| 999w ,| ,|, ) 7842 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 7843 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 7844 7845 // split srcset into tuple of uri and descriptor except for the last item 7846 var rawUris = trimmedSrcset.split(pattern); 7847 7848 // for each tuples 7849 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 7850 for (var i = 0; i < nbrUrisWith2parts; i++) { 7851 var innerIdx = i * 2; 7852 // sanitize the uri 7853 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 7854 // add the descriptor 7855 result += (" " + trim(rawUris[innerIdx + 1])); 7856 } 7857 7858 // split the last item into uri and descriptor 7859 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 7860 7861 // sanitize the last uri 7862 result += $$sanitizeUri(trim(lastTuple[0]), true); 7863 7864 // and add the last descriptor if any 7865 if (lastTuple.length === 2) { 7866 result += (" " + trim(lastTuple[1])); 7867 } 7868 this[key] = value = result; 7869 } 7870 7871 if (writeAttr !== false) { 7872 if (value === null || isUndefined(value)) { 7873 this.$$element.removeAttr(attrName); 7874 } else { 7875 if (SIMPLE_ATTR_NAME.test(attrName)) { 7876 this.$$element.attr(attrName, value); 7877 } else { 7878 setSpecialAttr(this.$$element[0], attrName, value); 7879 } 7880 } 7881 } 7882 7883 // fire observers 7884 var $$observers = this.$$observers; 7885 $$observers && forEach($$observers[observer], function(fn) { 7886 try { 7887 fn(value); 7888 } catch (e) { 7889 $exceptionHandler(e); 7890 } 7891 }); 7892 }, 7893 7894 7895 /** 7896 * @ngdoc method 7897 * @name $compile.directive.Attributes#$observe 7898 * @kind function 7899 * 7900 * @description 7901 * Observes an interpolated attribute. 7902 * 7903 * The observer function will be invoked once during the next `$digest` following 7904 * compilation. The observer is then invoked whenever the interpolated value 7905 * changes. 7906 * 7907 * @param {string} key Normalized key. (ie ngAttribute) . 7908 * @param {function(interpolatedValue)} fn Function that will be called whenever 7909 the interpolated value of the attribute changes. 7910 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 7911 * guide} for more info. 7912 * @returns {function()} Returns a deregistration function for this observer. 7913 */ 7914 $observe: function(key, fn) { 7915 var attrs = this, 7916 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 7917 listeners = ($$observers[key] || ($$observers[key] = [])); 7918 7919 listeners.push(fn); 7920 $rootScope.$evalAsync(function() { 7921 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 7922 // no one registered attribute interpolation function, so lets call it manually 7923 fn(attrs[key]); 7924 } 7925 }); 7926 7927 return function() { 7928 arrayRemove(listeners, fn); 7929 }; 7930 } 7931 }; 7932 7933 function setSpecialAttr(element, attrName, value) { 7934 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 7935 // so we have to jump through some hoops to get such an attribute 7936 // https://github.com/angular/angular.js/pull/13318 7937 specialAttrHolder.innerHTML = "<span " + attrName + ">"; 7938 var attributes = specialAttrHolder.firstChild.attributes; 7939 var attribute = attributes[0]; 7940 // We have to remove the attribute from its container element before we can add it to the destination element 7941 attributes.removeNamedItem(attribute.name); 7942 attribute.value = value; 7943 element.attributes.setNamedItem(attribute); 7944 } 7945 7946 function safeAddClass($element, className) {
vendor: 10,122 bytes, lines 7947-8176
7947 try { 7948 $element.addClass(className); 7949 } catch (e) { 7950 // ignore, since it means that we are trying to set class on 7951 // SVG element, where class name is read-only. 7952 } 7953 } 7954 7955 7956 var startSymbol = $interpolate.startSymbol(), 7957 endSymbol = $interpolate.endSymbol(), 7958 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 7959 ? identity 7960 : function denormalizeTemplate(template) { 7961 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 7962 }, 7963 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 7964 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 7965 7966 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 7967 var bindings = $element.data('$binding') || []; 7968 7969 if (isArray(binding)) { 7970 bindings = bindings.concat(binding); 7971 } else { 7972 bindings.push(binding); 7973 } 7974 7975 $element.data('$binding', bindings); 7976 } : noop; 7977 7978 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 7979 safeAddClass($element, 'ng-binding'); 7980 } : noop; 7981 7982 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 7983 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 7984 $element.data(dataName, scope); 7985 } : noop; 7986 7987 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 7988 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 7989 } : noop; 7990 7991 return compile; 7992 7993 //================================ 7994 7995 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 7996 previousCompileContext) { 7997 if (!($compileNodes instanceof jqLite)) { 7998 // jquery always rewraps, whereas we need to preserve the original selector so that we can 7999 // modify it. 8000 $compileNodes = jqLite($compileNodes); 8001 } 8002 // We can not compile top level text elements since text nodes can be merged and we will 8003 // not be able to attach scope data to them, so we will wrap them in <span> 8004 forEach($compileNodes, function(node, index) { 8005 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */) { 8006 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 8007 } 8008 }); 8009 var compositeLinkFn = 8010 compileNodes($compileNodes, transcludeFn, $compileNodes, 8011 maxPriority, ignoreDirective, previousCompileContext); 8012 compile.$$addScopeClass($compileNodes); 8013 var namespace = null; 8014 return function publicLinkFn(scope, cloneConnectFn, options) { 8015 assertArg(scope, 'scope'); 8016 8017 if (previousCompileContext && previousCompileContext.needsNewScope) { 8018 // A parent directive did a replace and a directive on this element asked 8019 // for transclusion, which caused us to lose a layer of element on which 8020 // we could hold the new transclusion scope, so we will create it manually 8021 // here. 8022 scope = scope.$parent.$new(); 8023 } 8024 8025 options = options || {}; 8026 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 8027 transcludeControllers = options.transcludeControllers, 8028 futureParentElement = options.futureParentElement; 8029 8030 // When `parentBoundTranscludeFn` is passed, it is a 8031 // `controllersBoundTransclude` function (it was previously passed 8032 // as `transclude` to directive.link) so we must unwrap it to get 8033 // its `boundTranscludeFn` 8034 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 8035 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 8036 } 8037 8038 if (!namespace) { 8039 namespace = detectNamespaceForChildElements(futureParentElement); 8040 } 8041 var $linkNode; 8042 if (namespace !== 'html') { 8043 // When using a directive with replace:true and templateUrl the $compileNodes 8044 // (or a child element inside of them) 8045 // might change, so we need to recreate the namespace adapted compileNodes 8046 // for call to the link function. 8047 // Note: This will already clone the nodes... 8048 $linkNode = jqLite( 8049 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 8050 ); 8051 } else if (cloneConnectFn) { 8052 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 8053 // and sometimes changes the structure of the DOM. 8054 $linkNode = JQLitePrototype.clone.call($compileNodes); 8055 } else { 8056 $linkNode = $compileNodes; 8057 } 8058 8059 if (transcludeControllers) { 8060 for (var controllerName in transcludeControllers) { 8061 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 8062 } 8063 } 8064 8065 compile.$$addScopeInfo($linkNode, scope); 8066 8067 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 8068 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 8069 return $linkNode; 8070 }; 8071 } 8072 8073 function detectNamespaceForChildElements(parentElement) { 8074 // TODO: Make this detect MathML as well... 8075 var node = parentElement && parentElement[0]; 8076 if (!node) { 8077 return 'html'; 8078 } else { 8079 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 8080 } 8081 } 8082 8083 /** 8084 * Compile function matches each node in nodeList against the directives. Once all directives 8085 * for a particular node are collected their compile functions are executed. The compile 8086 * functions return values - the linking functions - are combined into a composite linking 8087 * function, which is the a linking function for the node. 8088 * 8089 * @param {NodeList} nodeList an array of nodes or NodeList to compile 8090 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 8091 * scope argument is auto-generated to the new child of the transcluded parent scope. 8092 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 8093 * the rootElement must be set the jqLite collection of the compile root. This is 8094 * needed so that the jqLite collection items can be replaced with widgets. 8095 * @param {number=} maxPriority Max directive priority. 8096 * @returns {Function} A composite linking function of all of the matched directives or null. 8097 */ 8098 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 8099 previousCompileContext) { 8100 var linkFns = [], 8101 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 8102 8103 for (var i = 0; i < nodeList.length; i++) { 8104 attrs = new Attributes(); 8105 8106 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 8107 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 8108 ignoreDirective); 8109 8110 nodeLinkFn = (directives.length) 8111 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 8112 null, [], [], previousCompileContext) 8113 : null; 8114 8115 if (nodeLinkFn && nodeLinkFn.scope) { 8116 compile.$$addScopeClass(attrs.$$element); 8117 } 8118 8119 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 8120 !(childNodes = nodeList[i].childNodes) || 8121 !childNodes.length) 8122 ? null 8123 : compileNodes(childNodes, 8124 nodeLinkFn ? ( 8125 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 8126 && nodeLinkFn.transclude) : transcludeFn); 8127 8128 if (nodeLinkFn || childLinkFn) { 8129 linkFns.push(i, nodeLinkFn, childLinkFn); 8130 linkFnFound = true; 8131 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 8132 } 8133 8134 //use the previous context only for the first element in the virtual group 8135 previousCompileContext = null; 8136 } 8137 8138 // return a linking function if we have found anything, null otherwise 8139 return linkFnFound ? compositeLinkFn : null; 8140 8141 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 8142 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 8143 var stableNodeList; 8144 8145 8146 if (nodeLinkFnFound) { 8147 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 8148 // offsets don't get screwed up 8149 var nodeListLength = nodeList.length; 8150 stableNodeList = new Array(nodeListLength); 8151 8152 // create a sparse array by only copying the elements which have a linkFn 8153 for (i = 0; i < linkFns.length; i+=3) { 8154 idx = linkFns[i]; 8155 stableNodeList[idx] = nodeList[idx]; 8156 } 8157 } else { 8158 stableNodeList = nodeList; 8159 } 8160 8161 for (i = 0, ii = linkFns.length; i < ii;) { 8162 node = stableNodeList[linkFns[i++]]; 8163 nodeLinkFn = linkFns[i++]; 8164 childLinkFn = linkFns[i++]; 8165 8166 if (nodeLinkFn) { 8167 if (nodeLinkFn.scope) { 8168 childScope = scope.$new(); 8169 compile.$$addScopeInfo(jqLite(node), childScope); 8170 } else { 8171 childScope = scope; 8172 } 8173 8174 if (nodeLinkFn.transcludeOnThisElement) { 8175 childBoundTranscludeFn = createBoundTranscludeFn( 8176 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
8177 8178 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 8179 childBoundTranscludeFn = parentBoundTranscludeFn; 8180 8181 } else if (!parentBoundTranscludeFn && transcludeFn) { 8182 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 8183 8184 } else { 8185 childBoundTranscludeFn = null; 8186 } 8187 8188 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 8189 8190 } else if (childLinkFn) { 8191 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 8192 } 8193 } 8194 } 8195 } 8196 8197 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 8198 8199 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 8200 8201 if (!transcludedScope) { 8202 transcludedScope = scope.$new(false, containingScope); 8203 transcludedScope.$$transcluded = true; 8204 } 8205 8206 return transcludeFn(transcludedScope, cloneFn, { 8207 parentBoundTranscludeFn: previousBoundTranscludeFn, 8208 transcludeControllers: controllers, 8209 futureParentElement: futureParentElement 8210 }); 8211 }; 8212 8213 // We need to attach the transclusion slots onto the `boundTranscludeFn` 8214 // so that they are available inside the `controllersBoundTransclude` function 8215 var boundSlots = boundTranscludeFn.$$slots = createMap(); 8216 for (var slotName in transcludeFn.$$slots) { 8217 if (transcludeFn.$$slots[slotName]) { 8218 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 8219 } else { 8220 boundSlots[slotName] = null; 8221 } 8222 } 8223 8224 return boundTranscludeFn; 8225 } 8226 8227 /** 8228 * Looks for directives on the given node and adds them to the directive collection which is 8229 * sorted. 8230 * 8231 * @param node Node to search. 8232 * @param directives An array to which the directives are added to. This array is sorted before 8233 * the function returns. 8234 * @param attrs The shared attrs object which is used to populate the normalized attributes. 8235 * @param {number=} maxPriority Max directive priority. 8236 */ 8237 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 8238 var nodeType = node.nodeType, 8239 attrsMap = attrs.$attr, 8240 match, 8241 className; 8242 8243 switch (nodeType) { 8244 case NODE_TYPE_ELEMENT: /* Element */ 8245 // use the node name: <directive> 8246 addDirective(directives, 8247 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 8248 8249 // iterate over the attributes 8250 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 8251 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 8252 var attrStartName = false; 8253 var attrEndName = false; 8254 8255 attr = nAttrs[j]; 8256 name = attr.name; 8257 value = trim(attr.value); 8258 8259 // support ngAttr attribute binding 8260 ngAttrName = directiveNormalize(name); 8261 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 8262 name = name.replace(PREFIX_REGEXP, '') 8263 .substr(8).replace(/_(.)/g, function(match, letter) { 8264 return letter.toUpperCase(); 8265 }); 8266 } 8267 8268 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); 8269 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { 8270 attrStartName = name; 8271 attrEndName = name.substr(0, name.length - 5) + 'end'; 8272 name = name.substr(0, name.length - 6); 8273 } 8274 8275 nName = directiveNormalize(name.toLowerCase()); 8276 attrsMap[nName] = name; 8277 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 8278 attrs[nName] = value; 8279 if (getBooleanAttrName(node, nName)) { 8280 attrs[nName] = true; // presence means true 8281 } 8282 } 8283 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 8284 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 8285 attrEndName); 8286 } 8287 8288 // use class as directive
8289 className = node.className; 8290 if (isObject(className)) { 8291 // Maybe SVGAnimatedString 8292 className = className.animVal; 8293 } 8294 if (isString(className) && className !== '') { 8295 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 8296 nName = directiveNormalize(match[2]); 8297 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 8298 attrs[nName] = trim(match[3]); 8299 } 8300 className = className.substr(match.index + match[0].length); 8301 } 8302 } 8303 break; 8304 case NODE_TYPE_TEXT: /* Text Node */ 8305 if (msie === 11) { 8306 // Workaround for #11781 8307 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 8308 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 8309 node.parentNode.removeChild(node.nextSibling); 8310 } 8311 } 8312 addTextInterpolateDirective(directives, node.nodeValue); 8313 break; 8314 case NODE_TYPE_COMMENT: /* Comment */ 8315 try { 8316 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 8317 if (match) { 8318 nName = directiveNormalize(match[1]); 8319 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 8320 attrs[nName] = trim(match[2]); 8321 } 8322 } 8323 } catch (e) { 8324 // turns out that under some circumstances IE9 throws errors when one attempts to read 8325 // comment's node value. 8326 // Just ignore it and continue. (Can't seem to reproduce in test case.) 8327 } 8328 break; 8329 } 8330 8331 directives.sort(byPriority); 8332 return directives; 8333 } 8334 8335 /** 8336 * Given a node with an directive-start it collects all of the siblings until it finds 8337 * directive-end. 8338 * @param node 8339 * @param attrStart 8340 * @param attrEnd 8341 * @returns {*} 8342 */ 8343 function groupScan(node, attrStart, attrEnd) { 8344 var nodes = []; 8345 var depth = 0; 8346 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 8347 do { 8348 if (!node) { 8349 throw $compileMinErr('uterdir', 8350 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 8351 attrStart, attrEnd); 8352 } 8353 if (node.nodeType == NODE_TYPE_ELEMENT) { 8354 if (node.hasAttribute(attrStart)) depth++; 8355 if (node.hasAttribute(attrEnd)) depth--; 8356 } 8357 nodes.push(node); 8358 node = node.nextSibling; 8359 } while (depth > 0); 8360 } else { 8361 nodes.push(node); 8362 } 8363 8364 return jqLite(nodes); 8365 } 8366 8367 /** 8368 * Wrapper for linking function which converts normal linking function into a grouped 8369 * linking function. 8370 * @param linkFn 8371 * @param attrStart 8372 * @param attrEnd 8373 * @returns {Function} 8374 */ 8375 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 8376 return function(scope, element, attrs, controllers, transcludeFn) { 8377 element = groupScan(element[0], attrStart, attrEnd); 8378 return linkFn(scope, element, attrs, controllers, transcludeFn); 8379 }; 8380 } 8381 8382 /** 8383 * A function generator that is used to support both eager and lazy compilation 8384 * linking function. 8385 * @param eager 8386 * @param $compileNodes 8387 * @param transcludeFn 8388 * @param maxPriority 8389 * @param ignoreDirective 8390 * @param previousCompileContext 8391 * @returns {Function} 8392 */ 8393 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 8394 if (eager) { 8395 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 8396 } 8397 8398 var compiled; 8399 8400 return function() { 8401 if (!compiled) { 8402 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 8403 8404 // Null out all of these references in order to make them eligible for garbage collection 8405 // since this is a potentially long lived closure 8406 $compileNodes = transcludeFn = previousCompileContext = null; 8407 } 8408 8409 return compiled.apply(this, arguments); 8410 }; 8411 } 8412 8413 /** 8414 * Once the directives have been collected, their compile functions are executed. This method 8415 * is responsible for inlining directive templates as well as terminating the application 8416 * of the directives if the terminal directive has been reached. 8417 * 8418 * @param {Array} directives Array of collected directives to execute their compile function. 8419 * this needs to be pre-sorted by priority order. 8420 * @param {Node} compileNode The raw DOM node to apply the compile functions to 8421 * @param {Object} templateAttrs The shared attribute function 8422 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 8423 * scope argument is auto-generated to the new 8424 * child of the transcluded parent scope. 8425 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 8426 * argument has the root jqLite array so that we can replace nodes 8427 * on it. 8428 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 8429 * compiling the transclusion. 8430 * @param {Array.<Function>} preLinkFns 8431 * @param {Array.<Function>} postLinkFns 8432 * @param {Object} previousCompileContext Context used for previous compilation of the current 8433 * node 8434 * @returns {Function} linkFn 8435 */ 8436 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 8437 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 8438 previousCompileContext) { 8439 previousCompileContext = previousCompileContext || {}; 8440 8441 var terminalPriority = -Number.MAX_VALUE, 8442 newScopeDirective = previousCompileContext.newScopeDirective, 8443 controllerDirectives = previousCompileContext.controllerDirectives, 8444 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 8445 templateDirective = previousCompileContext.templateDirective, 8446 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 8447 hasTranscludeDirective = false, 8448 hasTemplate = false, 8449 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 8450 $compileNode = templateAttrs.$$element = jqLite(compileNode), 8451 directive, 8452 directiveName, 8453 $template, 8454 replaceDirective = originalReplaceDirective, 8455 childTranscludeFn = transcludeFn, 8456 linkFn, 8457 didScanForMultipleTransclusion = false, 8458 mightHaveMultipleTransclusionError = false, 8459 directiveValue; 8460 8461 // executes all directives on the current element 8462 for (var i = 0, ii = directives.length; i < ii; i++) { 8463 directive = directives[i]; 8464 var attrStart = directive.$$start; 8465 var attrEnd = directive.$$end; 8466 8467 // collect multiblock sections 8468 if (attrStart) { 8469 $compileNode = groupScan(compileNode, attrStart, attrEnd); 8470 } 8471 $template = undefined; 8472 8473 if (terminalPriority > directive.priority) { 8474 break; // prevent further processing of directives 8475 } 8476 8477 if (directiveValue = directive.scope) { 8478 8479 // skip the check for directives with async templates, we'll check the derived sync 8480 // directive when the template arrives 8481 if (!directive.templateUrl) { 8482 if (isObject(directiveValue)) { 8483 // This directive is trying to add an isolated scope. 8484 // Check that there is no scope of any kind already 8485 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 8486 directive, $compileNode); 8487 newIsolateScopeDirective = directive; 8488 } else { 8489 // This directive is trying to add a child scope. 8490 // Check that there is no isolated scope already 8491 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 8492 $compileNode); 8493 } 8494 } 8495 8496 newScopeDirective = newScopeDirective || directive; 8497 } 8498 8499 directiveName = directive.name; 8500 8501 // If we encounter a condition that can result in transclusion on the directive, 8502 // then scan ahead in the remaining directives for others that may cause a multiple 8503 // transclusion error to be thrown during the compilation process. If a matching directive 8504 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 8505 // compile the `transclude` function rather than doing it lazily in order to throw 8506 // exceptions at the correct time 8507 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 8508 || (directive.transclude && !directive.$$tlb))) { 8509 var candidateDirective; 8510 8511 for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) { 8512 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 8513 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 8514 mightHaveMultipleTransclusionError = true; 8515 break; 8516 } 8517 } 8518 8519 didScanForMultipleTransclusion = true; 8520 } 8521 8522 if (!directive.templateUrl && directive.controller) { 8523 directiveValue = directive.controller; 8524 controllerDirectives = controllerDirectives || createMap();
8525 assertNoDuplicate("'" + directiveName + "' controller", 8526 controllerDirectives[directiveName], directive, $compileNode); 8527 controllerDirectives[directiveName] = directive; 8528 } 8529 8530 if (directiveValue = directive.transclude) { 8531 hasTranscludeDirective = true; 8532 8533 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 8534 // This option should only be used by directives that know how to safely handle element transclusion, 8535 // where the transcluded nodes are added or replaced after linking. 8536 if (!directive.$$tlb) { 8537 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 8538 nonTlbTranscludeDirective = directive; 8539 } 8540 8541 if (directiveValue == 'element') { 8542 hasElementTranscludeDirective = true; 8543 terminalPriority = directive.priority; 8544 $template = $compileNode; 8545 $compileNode = templateAttrs.$$element = 8546 jqLite(document.createComment(' ' + directiveName + ': ' + 8547 templateAttrs[directiveName] + ' ')); 8548 compileNode = $compileNode[0]; 8549 replaceWith(jqCollection, sliceArgs($template), compileNode); 8550 8551 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 8552 replaceDirective && replaceDirective.name, { 8553 // Don't pass in: 8554 // - controllerDirectives - otherwise we'll create duplicates controllers 8555 // - newIsolateScopeDirective or templateDirective - comb
vendor: 9,807 bytes, lines 8555-8773
8555ining templates with 8556 // element transclusion doesn't make sense. 8557 // 8558 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 8559 // on the same element more than once. 8560 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8561 }); 8562 } else { 8563 8564 var slots = createMap(); 8565 8566 $template = jqLite(jqLiteClone(compileNode)).contents(); 8567 8568 if (isObject(directiveValue)) { 8569 8570 // We have transclusion slots, 8571 // collect them up, compile them and store their transclusion functions 8572 $template = []; 8573 8574 var slotMap = createMap(); 8575 var filledSlots = createMap(); 8576 8577 // Parse the element selectors 8578 forEach(directiveValue, function(elementSelector, slotName) { 8579 // If an element selector starts with a ? then it is optional 8580 var optional = (elementSelector.charAt(0) === '?'); 8581 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 8582 8583 slotMap[elementSelector] = slotName; 8584 8585 // We explicitly assign `null` since this implies that a slot was defined but not filled. 8586 // Later when calling boundTransclusion functions with a slot name we only error if the 8587 // slot is `undefined` 8588 slots[slotName] = null; 8589 8590 // filledSlots contains `true` for all slots that are either optional or have been 8591 // filled. This is used to check that we have not missed any required slots 8592 filledSlots[slotName] = optional; 8593 }); 8594 8595 // Add the matching elements into their slot 8596 forEach($compileNode.contents(), function(node) { 8597 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 8598 if (slotName) { 8599 filledSlots[slotName] = true; 8600 slots[slotName] = slots[slotName] || []; 8601 slots[slotName].push(node); 8602 } else { 8603 $template.push(node); 8604 } 8605 }); 8606 8607 // Check for required slots that were not filled 8608 forEach(filledSlots, function(filled, slotName) { 8609 if (!filled) { 8610 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 8611 } 8612 }); 8613 8614 for (var slotName in slots) { 8615 if (slots[slotName]) { 8616 // Only define a transclusion function if the slot was filled 8617 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); 8618 } 8619 } 8620 } 8621 8622 $compileNode.empty(); // clear contents 8623 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 8624 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 8625 childTranscludeFn.$$slots = slots; 8626 } 8627 } 8628 8629 if (directive.template) { 8630 hasTemplate = true; 8631 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8632 templateDirective = directive; 8633 8634 directiveValue = (isFunction(directive.template)) 8635 ? directive.template($compileNode, templateAttrs) 8636 : directive.template; 8637 8638 directiveValue = denormalizeTemplate(directiveValue); 8639 8640 if (directive.replace) { 8641 replaceDirective = directive; 8642 if (jqLiteIsTextNode(directiveValue)) { 8643 $template = []; 8644 } else { 8645 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 8646 } 8647 compileNode = $template[0]; 8648 8649 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8650 throw $compileMinErr('tplrt', 8651 "Template for directive '{0}' must have exactly one root element. {1}", 8652 directiveName, ''); 8653 } 8654 8655 replaceWith(jqCollection, $compileNode, compileNode); 8656 8657 var newTemplateAttrs = {$attr: {}}; 8658 8659 // combine directives from the original node and from the template: 8660 // - take the array of directives for this element 8661 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 8662 // - collect directives from the template and sort them by priority 8663 // - combine directives as: processed + template + unprocessed 8664 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 8665 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 8666 8667 if (newIsolateScopeDirective || newScopeDirective) { 8668 // The original directive caused the current element to be replaced but this element 8669 // also needs to have a new scope, so we need to tell the template directives 8670 // that they would need to get their scope from further up, if they require transclusion 8671 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 8672 } 8673 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 8674 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 8675 8676 ii = directives.length; 8677 } else { 8678 $compileNode.html(directiveValue); 8679 } 8680 } 8681 8682 if (directive.templateUrl) { 8683 hasTemplate = true; 8684 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8685 templateDirective = directive; 8686 8687 if (directive.replace) { 8688 replaceDirective = directive; 8689 } 8690 8691 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 8692 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 8693 controllerDirectives: controllerDirectives, 8694 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 8695 newIsolateScopeDirective: newIsolateScopeDirective, 8696 templateDirective: templateDirective, 8697 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8698 }); 8699 ii = directives.length; 8700 } else if (directive.compile) { 8701 try { 8702 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 8703 if (isFunction(linkFn)) { 8704 addLinkFns(null, linkFn, attrStart, attrEnd); 8705 } else if (linkFn) { 8706 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 8707 } 8708 } catch (e) { 8709 $exceptionHandler(e, startingTag($compileNode)); 8710 } 8711 } 8712 8713 if (directive.terminal) { 8714 nodeLinkFn.terminal = true; 8715 terminalPriority = Math.max(terminalPriority, directive.priority); 8716 } 8717 8718 } 8719 8720 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 8721 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 8722 nodeLinkFn.templateOnThisElement = hasTemplate; 8723 nodeLinkFn.transclude = childTranscludeFn; 8724 8725 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 8726 8727 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 8728 return nodeLinkFn; 8729 8730 //////////////////// 8731 8732 function addLinkFns(pre, post, attrStart, attrEnd) { 8733 if (pre) { 8734 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 8735 pre.require = directive.require; 8736 pre.directiveName = directiveName; 8737 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8738 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 8739 } 8740 preLinkFns.push(pre); 8741 } 8742 if (post) { 8743 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 8744 post.require = directive.require; 8745 post.directiveName = directiveName; 8746 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8747 post = cloneAndAnnotateFn(post, {isolateScope: true}); 8748 } 8749 postLinkFns.push(post); 8750 } 8751 } 8752 8753 8754 function getControllers(directiveName, require, $element, elementControllers) { 8755 var value; 8756 8757 if (isString(require)) { 8758 var match = require.match(REQUIRE_PREFIX_REGEXP); 8759 var name = require.substring(match[0].length); 8760 var inheritType = match[1] || match[3]; 8761 var optional = match[2] === '?'; 8762 8763 //If only parents then start at the parent element 8764 if (inheritType === '^^') { 8765 $element = $element.parent(); 8766 //Otherwise attempt getting the controller from elementControllers in case 8767 //the element is transcluded (and has no data) and to avoid .data if possible 8768 } else { 8769 value = elementControllers && elementControllers[name]; 8770 value = value && value.instance; 8771 } 8772 8773 if (!value) {
8774 var dataName = '$' + name + 'Controller'; 8775 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 8776 } 8777 8778 if (!value && !optional) { 8779 throw $compileMinErr('ctreq', 8780 "Controller '{0}', required by directive '{1}', can't be found!", 8781 name, directiveName); 8782 } 8783 } else if (isArray(require)) { 8784 value = []; 8785 for (var i = 0, ii = require.length; i < ii; i++) { 8786 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 8787 } 8788 } 8789 8790 return value || null; 8791 } 8792 8793 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { 8794 var elementControllers = createMap(); 8795 for (var controllerKey in controllerDirectives) { 8796 var directive = controllerDirectives[controllerKey]; 8797 var locals = { 8798 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 8799 $element: $element, 8800 $attrs: attrs, 8801 $transclude: transcludeFn 8802 }; 8803 8804 var controller = directive.controller; 8805 if (controller == '@') { 8806 controller = attrs[directive.name]; 8807 } 8808 8809 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 8810 8811 // For directives with element transclusion the element is a comment, 8812 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 8813 // clean up (http://bugs.jquery.com/ticket/8335). 8814 // Instead, we save the controllers for the element in a local hash and attach to .data 8815 // later, once we have the actual element. 8816 elementControllers[directive.name] = controllerInstance; 8817 if (!hasElementTranscludeDirective) { 8818 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 8819 } 8820 } 8821 return elementControllers; 8822 } 8823 8824 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 8825 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 8826 attrs, removeScopeBindingWatches, removeControllerBindingWatches; 8827 8828 if (compileNode === linkNode) { 8829 attrs = templateAttrs; 8830 $element = templateAttrs.$$element; 8831 } else { 8832 $element = jqLite(linkNode); 8833 attrs = new Attributes($element, templateAttrs); 8834 } 8835 8836 controllerScope = scope; 8837 if (newIsolateScopeDirective) { 8838 isolateScope = scope.$new(true); 8839 } else if (newScopeDirective) { 8840 controllerScope = scope.$parent; 8841 } 8842 8843 if (boundTranscludeFn) { 8844 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8845 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8846 transcludeFn = controllersBoundTransclude; 8847 transcludeFn.$$boundTransclude = boundTranscludeFn; 8848 // expose the slots on the `$transclude` function 8849 transcludeFn.isSlotFilled = function(slotName) { 8850 return !!boundTranscludeFn.$$slots[slotName]; 8851 }; 8852 } 8853 8854 if (controllerDirectives) { 8855 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); 8856 } 8857 8858 if (newIsolateScopeDirective) { 8859 // Initialize isolate scope bindings for new isolate scope directive. 8860 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 8861 templateDirective === newIsolateScopeDirective.$$originalDirective))); 8862 compile.$$addScopeClass($element, true); 8863 isolateScope.$$isolateBindings = 8864 newIsolateScopeDirective.$$isolateBindings; 8865 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope, 8866 isolateScope.$$isolateBindings, 8867 newIsolateScopeDirective); 8868 if (removeScopeBindingWatches) { 8869 isolateScope.$on('$destroy', removeScopeBindingWatches); 8870 } 8871 } 8872 8873 // Initialize bindToController bindings 8874 for (var name in elementControllers) { 8875 var controllerDirective = controllerDirectives[name]; 8876 var controller = elementControllers[name]; 8877 var bindings = controllerDirective.$$bindings.bindToController; 8878 8879 if (controller.identifier && bindings) { 8880 removeControllerBindingWatches = 8881 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 8882 } 8883 8884 var controllerResult = controller(); 8885 if (controllerResult !== controller.instance) { 8886 // If the controller constructor has a return value, overwrite the instance 8887 // from setupControllers 8888 controller.instance = controllerResult; 8889 $element.data('$' + controllerDirective.name + 'Controller', controllerResult); 8890 removeControllerBindingWatches && removeControllerBindingWatches(); 8891 removeControllerBindingWatches = 8892 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 8893 } 8894 } 8895 8896 // PRELINKING 8897 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 8898 linkFn = preLinkFns[i]; 8899 invokeLinkFn(linkFn, 8900 linkFn.isolateScope ? isolateScope : scope, 8901 $element, 8902 attrs, 8903 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8904 transcludeFn 8905 ); 8906 } 8907 8908 // RECURSION 8909 // We only pass the isolate scope, if the isolate directive has a template, 8910 // otherwise the child elements do not belong to the isolate directive. 8911 var scopeToChild = scope; 8912 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 8913 scopeToChild = isolateScope; 8914 } 8915 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
vendor: 5,754 bytes, lines 8916-9052
8916 8917 // POSTLINKING 8918 for (i = postLinkFns.length - 1; i >= 0; i--) { 8919 linkFn = postLinkFns[i]; 8920 invokeLinkFn(linkFn, 8921 linkFn.isolateScope ? isolateScope : scope, 8922 $element, 8923 attrs, 8924 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8925 transcludeFn 8926 ); 8927 } 8928 8929 // This is the function that is injected as `$transclude`. 8930 // Note: all arguments are optional! 8931 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 8932 var transcludeControllers; 8933 // No scope passed in: 8934 if (!isScope(scope)) { 8935 slotName = futureParentElement; 8936 futureParentElement = cloneAttachFn; 8937 cloneAttachFn = scope; 8938 scope = undefined; 8939 } 8940 8941 if (hasElementTranscludeDirective) { 8942 transcludeControllers = elementControllers; 8943 } 8944 if (!futureParentElement) { 8945 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 8946 } 8947 if (slotName) { 8948 // slotTranscludeFn can be one of three things: 8949 // * a transclude function - a filled slot 8950 // * `null` - an optional slot that was not filled 8951 // * `undefined` - a slot that was not declared (i.e. invalid) 8952 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 8953 if (slotTranscludeFn) { 8954 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8955 } else if (isUndefined(slotTranscludeFn)) { 8956 throw $compileMinErr('noslot', 8957 'No parent directive that requires a transclusion with slot name "{0}". ' + 8958 'Element: {1}', 8959 slotName, startingTag($element)); 8960 } 8961 } else { 8962 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8963 } 8964 } 8965 } 8966 } 8967 8968 // Depending upon the context in which a directive finds itself it might need to have a new isolated 8969 // or child scope created. For instance: 8970 // * if the directive has been pulled into a template because another directive with a higher priority 8971 // asked for element transclusion 8972 // * if the directive itself asks for transclusion but it is at the root of a template and the original 8973 // element was replaced. See https://github.com/angular/angular.js/issues/12936 8974 function markDirectiveScope(directives, isolateScope, newScope) { 8975 for (var j = 0, jj = directives.length; j < jj; j++) { 8976 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 8977 } 8978 } 8979 8980 /** 8981 * looks up the directive and decorates it with exception handling and proper parameters. We 8982 * call this the boundDirective. 8983 * 8984 * @param {string} name name of the directive to look up. 8985 * @param {string} location The directive must be found in specific format. 8986 * String containing any of theses characters: 8987 * 8988 * * `E`: element name 8989 * * `A': attribute 8990 * * `C`: class 8991 * * `M`: comment 8992 * @returns {boolean} true if directive was added. 8993 */ 8994 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 8995 endAttrName) { 8996 if (name === ignoreDirective) return null; 8997 var match = null; 8998 if (hasDirectives.hasOwnProperty(name)) { 8999 for (var directive, directives = $injector.get(name + Suffix), 9000 i = 0, ii = directives.length; i < ii; i++) { 9001 try { 9002 directive = directives[i]; 9003 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 9004 directive.restrict.indexOf(location) != -1) { 9005 if (startAttrName) { 9006 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 9007 } 9008 tDirectives.push(directive); 9009 match = directive; 9010 } 9011 } catch (e) { $exceptionHandler(e); } 9012 } 9013 } 9014 return match; 9015 } 9016 9017 9018 /** 9019 * looks up the directive and returns true if it is a multi-element directive, 9020 * and therefore requires DOM nodes between -start and -end markers to be grouped 9021 * together. 9022 * 9023 * @param {string} name name of the directive to look up. 9024 * @returns true if directive was registered as multi-element. 9025 */ 9026 function directiveIsMultiElement(name) { 9027 if (hasDirectives.hasOwnProperty(name)) { 9028 for (var directive, directives = $injector.get(name + Suffix), 9029 i = 0, ii = directives.length; i < ii; i++) { 9030 directive = directives[i]; 9031 if (directive.multiElement) { 9032 return true; 9033 } 9034 } 9035 } 9036 return false; 9037 } 9038 9039 /** 9040 * When the element is replaced with HTML template then the new attributes 9041 * on the template need to be merged with the existing attributes in the DOM. 9042 * The desired effect is to have both of the attributes present. 9043 * 9044 * @param {object} dst destination attributes (original DOM) 9045 * @param {object} src source attributes (from the directive template) 9046 */ 9047 function mergeTemplateAttributes(dst, src) { 9048 var srcAttr = src.$attr, 9049 dstAttr = dst.$attr, 9050 $element = dst.$$element; 9051 9052 // reapply the old attributes to the new element
9053 forEach(dst, function(value, key) { 9054 if (key.charAt(0) != '$') { 9055 if (src[key] && src[key] !== value) { 9056 value += (key === 'style' ? ';' : ' ') + src[key]; 9057 } 9058 dst.$set(key, value, true, srcAttr[key]); 9059 } 9060 }); 9061 9062 // copy the new attributes on the old attrs object 9063 forEach(src, function(value, key) { 9064 if (key == 'class') { 9065 safeAddClass($element, value); 9066 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 9067 } else if (key == 'style') { 9068 $element.attr('style', $element.attr('style') + ';' + value); 9069 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 9070 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 9071 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 9072 // have an attribute like "has-own-property" or "data-has-own-property", etc. 9073 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 9074 dst[key] = value; 9075 dstAttr[key] = srcAttr[key]; 9076 } 9077 }); 9078 } 9079 9080 9081 function compileTemplateUrl(directives, $compileNode, tAttrs, 9082 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 9083 var linkQueue = [], 9084 afterTemplateNodeLinkFn, 9085 afterTemplateChildLinkFn, 9086 beforeTemplateCompileNode = $compileNode[0], 9087 origAsyncDirective = directives.shift(), 9088 derivedSyncDirective = inherit(origAsyncDirective, { 9089 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 9090 }), 9091 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 9092 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 9093 : origAsyncDirective.templateUrl, 9094 templateNamespace = origAsyncDirective.templateNamespace; 9095 9096 $compileNode.empty(); 9097 9098 $templateRequest(templateUrl) 9099 .then(function(content) { 9100 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 9101 9102 content = denormalizeTemplate(content); 9103 9104 if (origAsyncDirective.replace) { 9105 if (jqLiteIsTextNode(content)) { 9106 $template = []; 9107 } else { 9108 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 9109 } 9110 compileNode = $template[0]; 9111 9112 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 9113 throw $compileMinErr('tplrt', 9114 "Template for directive '{0}' must have exactly one root element. {1}", 9115 origAsyncDirective.name, templateUrl); 9116 } 9117 9118 tempTemplateAttrs = {$attr: {}}; 9119 replaceWith($rootElement, $compileNode, compileNode); 9120 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 9121 9122 if (isObject(origAsyncDirective.scope)) { 9123 // the original directive that caused the template to be loaded async required 9124 // an isolate scope 9125 markDirectiveScope(templateDirectives, true); 9126 } 9127 directives = templateDirectives.concat(directives); 9128 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 9129 } else { 9130 compileNode = beforeTemplateCompileNode; 9131 $compileNode.html(content); 9132 } 9133 9134 directives.unshift(derivedSyncDirective); 9135 9136 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 9137 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 9138 previousCompileContext); 9139 forEach($rootElement, function(node, i) { 9140 if (node == compileNode) { 9141 $rootElement[i] = $compileNode[0]; 9142 } 9143 }); 9144 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 9145 9146 while (linkQueue.length) { 9147 var scope = linkQueue.shift(), 9148 beforeTemplateLinkNode = linkQueue.shift(), 9149 linkRootElement = linkQueue.shift(), 9150 boundTranscludeFn = linkQueue.shift(), 9151 linkNode = $compileNode[0]; 9152 9153 if (scope.$$destroyed) continue; 9154 9155 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
vendor: 11,577 bytes, lines 9156-9432
9156 var oldClasses = beforeTemplateLinkNode.className; 9157 9158 if (!(previousCompileContext.hasElementTranscludeDirective && 9159 origAsyncDirective.replace)) { 9160 // it was cloned therefore we have to clone as well. 9161 linkNode = jqLiteClone(compileNode); 9162 } 9163 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 9164 9165 // Copy in CSS classes from original node 9166 safeAddClass(jqLite(linkNode), oldClasses); 9167 } 9168 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 9169 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 9170 } else { 9171 childBoundTranscludeFn = boundTranscludeFn; 9172 } 9173 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 9174 childBoundTranscludeFn); 9175 } 9176 linkQueue = null; 9177 }); 9178 9179 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 9180 var childBoundTranscludeFn = boundTranscludeFn; 9181 if (scope.$$destroyed) return; 9182 if (linkQueue) { 9183 linkQueue.push(scope, 9184 node, 9185 rootElement, 9186 childBoundTranscludeFn); 9187 } else { 9188 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 9189 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 9190 } 9191 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 9192 } 9193 }; 9194 } 9195 9196 9197 /** 9198 * Sorting function for bound directives. 9199 */ 9200 function byPriority(a, b) { 9201 var diff = b.priority - a.priority; 9202 if (diff !== 0) return diff; 9203 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 9204 return a.index - b.index; 9205 } 9206 9207 function assertNoDuplicate(what, previousDirective, directive, element) { 9208 9209 function wrapModuleNameIfDefined(moduleName) { 9210 return moduleName ? 9211 (' (module: ' + moduleName + ')') : 9212 ''; 9213 } 9214 9215 if (previousDirective) { 9216 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 9217 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 9218 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 9219 } 9220 } 9221 9222 9223 function addTextInterpolateDirective(directives, text) { 9224 var interpolateFn = $interpolate(text, true); 9225 if (interpolateFn) { 9226 directives.push({ 9227 priority: 0, 9228 compile: function textInterpolateCompileFn(templateNode) { 9229 var templateNodeParent = templateNode.parent(), 9230 hasCompileParent = !!templateNodeParent.length; 9231 9232 // When transcluding a template that has bindings in the root 9233 // we don't have a parent and thus need to add the class during linking fn. 9234 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 9235 9236 return function textInterpolateLinkFn(scope, node) { 9237 var parent = node.parent(); 9238 if (!hasCompileParent) compile.$$addBindingClass(parent); 9239 compile.$$addBindingInfo(parent, interpolateFn.expressions); 9240 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 9241 node[0].nodeValue = value; 9242 }); 9243 }; 9244 } 9245 }); 9246 } 9247 } 9248 9249 9250 function wrapTemplate(type, template) { 9251 type = lowercase(type || 'html'); 9252 switch (type) { 9253 case 'svg': 9254 case 'math': 9255 var wrapper = document.createElement('div'); 9256 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 9257 return wrapper.childNodes[0].childNodes; 9258 default: 9259 return template; 9260 } 9261 } 9262 9263 9264 function getTrustedContext(node, attrNormalizedName) { 9265 if (attrNormalizedName == "srcdoc") { 9266 return $sce.HTML; 9267 } 9268 var tag = nodeName_(node); 9269 // maction[xlink:href] can source SVG. It's not limited to <maction>. 9270 if (attrNormalizedName == "xlinkHref" || 9271 (tag == "form" && attrNormalizedName == "action") || 9272 (tag != "img" && (attrNormalizedName == "src" || 9273 attrNormalizedName == "ngSrc"))) { 9274 return $sce.RESOURCE_URL; 9275 } 9276 } 9277 9278 9279 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 9280 var trustedContext = getTrustedContext(node, name); 9281 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 9282 9283 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 9284 9285 // no interpolation found -> ignore 9286 if (!interpolateFn) return; 9287 9288 9289 if (name === "multiple" && nodeName_(node) === "select") { 9290 throw $compileMinErr("selmulti", 9291 "Binding to the 'multiple' attribute is not supported. Element: {0}", 9292 startingTag(node)); 9293 } 9294 9295 directives.push({ 9296 priority: 100, 9297 compile: function() { 9298 return { 9299 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 9300 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 9301 9302 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 9303 throw $compileMinErr('nodomevents', 9304 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 9305 "ng- versions (such as ng-click instead of onclick) instead."); 9306 } 9307 9308 // If the attribute has changed since last $interpolate()ed 9309 var newValue = attr[name]; 9310 if (newValue !== value) { 9311 // we need to interpolate again since the attribute value has been updated 9312 // (e.g. by another directive's compile function) 9313 // ensure unset/empty values make interpolateFn falsy 9314 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 9315 value = newValue; 9316 } 9317 9318 // if attribute was updated so that there is no interpolation going on we don't want to 9319 // register any observers 9320 if (!interpolateFn) return; 9321 9322 // initialize attr object so that it's ready in case we need the value for isolate 9323 // scope initialization, otherwise the value would not be available from isolate 9324 // directive's linking fn during linking phase 9325 attr[name] = interpolateFn(scope); 9326 9327 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 9328 (attr.$$observers && attr.$$observers[name].$$scope || scope). 9329 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 9330 //special case for class attribute addition + removal 9331 //so that class changes can tap into the animation 9332 //hooks provided by the $animate service. Be sure to 9333 //skip animations when the first digest occurs (when 9334 //both the new and the old values are the same) since 9335 //the CSS classes are the non-interpolated values 9336 if (name === 'class' && newValue != oldValue) { 9337 attr.$updateClass(newValue, oldValue); 9338 } else { 9339 attr.$set(name, newValue); 9340 } 9341 }); 9342 } 9343 }; 9344 } 9345 }); 9346 } 9347 9348 9349 /** 9350 * This is a special jqLite.replaceWith, which can replace items which 9351 * have no parents, provided that the containing jqLite collection is provided. 9352 * 9353 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 9354 * in the root of the tree. 9355 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 9356 * the shell, but replace its DOM node reference. 9357 * @param {Node} newNode The new DOM node. 9358 */ 9359 function replaceWith($rootElement, elementsToRemove, newNode) { 9360 var firstElementToRemove = elementsToRemove[0], 9361 removeCount = elementsToRemove.length, 9362 parent = firstElementToRemove.parentNode, 9363 i, ii; 9364 9365 if ($rootElement) { 9366 for (i = 0, ii = $rootElement.length; i < ii; i++) { 9367 if ($rootElement[i] == firstElementToRemove) { 9368 $rootElement[i++] = newNode; 9369 for (var j = i, j2 = j + removeCount - 1, 9370 jj = $rootElement.length; 9371 j < jj; j++, j2++) { 9372 if (j2 < jj) { 9373 $rootElement[j] = $rootElement[j2]; 9374 } else { 9375 delete $rootElement[j]; 9376 } 9377 } 9378 $rootElement.length -= removeCount - 1; 9379 9380 // If the replaced element is also the jQuery .context then replace it 9381 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 9382 // http://api.jquery.com/context/ 9383 if ($rootElement.context === firstElementToRemove) { 9384 $rootElement.context = newNode; 9385 } 9386 break; 9387 } 9388 } 9389 } 9390 9391 if (parent) { 9392 parent.replaceChild(newNode, firstElementToRemove); 9393 } 9394 9395 // Append all the `elementsToRemove` to a fragment. This will... 9396 // - remove them from the DOM 9397 // - allow them to still be traversed with .nextSibling 9398 // - allow a single fragment.qSA to fetch all elements being removed 9399 var fragment = document.createDocumentFragment(); 9400 for (i = 0; i < removeCount; i++) { 9401 fragment.appendChild(elementsToRemove[i]); 9402 } 9403 9404 if (jqLite.hasData(firstElementToRemove)) { 9405 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 9406 // data here because there's no public interface in jQuery to do that and copying over 9407 // event listeners (which is the main use of private data) wouldn't work anyway. 9408 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 9409 9410 // Remove $destroy event listeners from `firstElementToRemove` 9411 jqLite(firstElementToRemove).off('$destroy'); 9412 } 9413 9414 // Cleanup any data/listeners on the elements and children. 9415 // This includes invoking the $destroy event on any elements with listeners. 9416 jqLite.cleanData(fragment.querySelectorAll('*')); 9417 9418 // Update the jqLite collection to only contain the `newNode` 9419 for (i = 1; i < removeCount; i++) { 9420 delete elementsToRemove[i]; 9421 } 9422 elementsToRemove[0] = newNode; 9423 elementsToRemove.length = 1; 9424 } 9425 9426 9427 function cloneAndAnnotateFn(fn, annotation) { 9428 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 9429 } 9430 9431 9432 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
9433 try { 9434 linkFn(scope, $element, attrs, controllers, transcludeFn); 9435 } catch (e) { 9436 $exceptionHandler(e, startingTag($element)); 9437 } 9438 } 9439 9440 9441 // Set up $watches for isolate scope and controller bindings. This process 9442 // only occurs for isolate scopes and new scopes with controllerAs. 9443 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 9444 var removeWatchCollection = []; 9445 forEach(bindings, function(definition, scopeName) { 9446 var attrName = definition.attrName, 9447 optional = definition.optional, 9448 mode = definition.mode, // @, =, or & 9449 lastValue, 9450 parentGet, parentSet, compare; 9451 9452 switch (mode) { 9453 9454 case '@': 9455 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 9456 destination[scopeName] = attrs[attrName] = void 0; 9457 } 9458 attrs.$observe(attrName, function(value) { 9459 if (isString(value)) { 9460 destination[scopeName] = value; 9461 } 9462 }); 9463 attrs.$$observers[attrName].$$scope = scope; 9464 if (isString(attrs[attrName])) { 9465 // If the attribute has been provided then we trigger an interpolation to ensure 9466 // the value is there for use in the link fn 9467 destination[scopeName] = $interpolate(attrs[attrName])(scope); 9468 } 9469 break; 9470 9471 case '=': 9472 if (!hasOwnProperty.call(attrs, attrName)) { 9473 if (optional) break; 9474 attrs[attrName] = void 0; 9475 } 9476 if (optional && !attrs[attrName]) break; 9477 9478 parentGet = $parse(attrs[attrName]); 9479 if (parentGet.literal) { 9480 compare = equals; 9481 } else { 9482 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 9483 } 9484 parentSet = parentGet.assign || function() { 9485 // reset the change, or we will throw this exception on every $digest 9486 lastValue = destination[scopeName] = parentGet(scope); 9487 throw $compileMinErr('nonassign', 9488 "Expression '{0}' used with directive '{1}' is non-assignable!", 9489 attrs[attrName], directive.name); 9490 }; 9491 lastValue = destination[scopeName] = parentGet(scope); 9492 var parentValueWatch = function parentValueWatch(parentValue) { 9493 if (!compare(parentValue, destination[scopeName])) { 9494 // we are out of sync and need to copy 9495 if (!compare(parentValue, lastValue)) { 9496 // parent changed and it has precedence 9497 destination[scopeName] = parentValue; 9498 } else { 9499 // if the parent can be assigned then do so 9500 parentSet(scope, parentValue = destination[scopeName]); 9501 } 9502 } 9503 return lastValue = parentValue; 9504 }; 9505 parentValueWatch.$stateful = true; 9506 var removeWatch; 9507 if (definition.collection) { 9508 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 9509 } else { 9510 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 9511 } 9512 removeWatchCollection.push(removeWatch); 9513 break; 9514 9515 case '&': 9516 // Don't assign Object.prototype method to scope 9517 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 9518 9519 // Don't assign noop to destination if expression is not valid 9520 if (parentGet === noop && optional) break; 9521 9522 destination[scopeName] = function(locals) { 9523 return parentGet(scope, locals); 9524 }; 9525 break; 9526 } 9527 }); 9528 9529 return removeWatchCollection.length && function removeWatches() { 9530 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 9531 removeWatchCollection[i]();
vendor: 9,465 bytes, lines 9532-9821
9532 } 9533 }; 9534 } 9535 }]; 9536} 9537 9538var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 9539/** 9540 * Converts all accepted directives format into proper directive name. 9541 * @param name Name to normalize 9542 */ 9543function directiveNormalize(name) { 9544 return camelCase(name.replace(PREFIX_REGEXP, '')); 9545} 9546 9547/** 9548 * @ngdoc type 9549 * @name $compile.directive.Attributes 9550 * 9551 * @description 9552 * A shared object between directive compile / linking functions which contains normalized DOM 9553 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 9554 * needed since all of these are treated as equivalent in Angular: 9555 * 9556 * ``` 9557 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 9558 * ``` 9559 */ 9560 9561/** 9562 * @ngdoc property 9563 * @name $compile.directive.Attributes#$attr 9564 * 9565 * @description 9566 * A map of DOM element attribute names to the normalized name. This is 9567 * needed to do reverse lookup from normalized name back to actual name. 9568 */ 9569 9570 9571/** 9572 * @ngdoc method 9573 * @name $compile.directive.Attributes#$set 9574 * @kind function 9575 * 9576 * @description 9577 * Set DOM element attribute value. 9578 * 9579 * 9580 * @param {string} name Normalized element attribute name of the property to modify. The name is 9581 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 9582 * property to the original name. 9583 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 9584 */ 9585 9586 9587 9588/** 9589 * Closure compiler type information 9590 */ 9591 9592function nodesetLinkingFn( 9593 /* angular.Scope */ scope, 9594 /* NodeList */ nodeList, 9595 /* Element */ rootElement, 9596 /* function(Function) */ boundTranscludeFn 9597) {} 9598 9599function directiveLinkingFn( 9600 /* nodesetLinkingFn */ nodesetLinkingFn, 9601 /* angular.Scope */ scope, 9602 /* Node */ node, 9603 /* Element */ rootElement, 9604 /* function(Function) */ boundTranscludeFn 9605) {} 9606 9607function tokenDifference(str1, str2) { 9608 var values = '', 9609 tokens1 = str1.split(/\s+/), 9610 tokens2 = str2.split(/\s+/); 9611 9612 outer: 9613 for (var i = 0; i < tokens1.length; i++) { 9614 var token = tokens1[i]; 9615 for (var j = 0; j < tokens2.length; j++) { 9616 if (token == tokens2[j]) continue outer; 9617 } 9618 values += (values.length > 0 ? ' ' : '') + token; 9619 } 9620 return values; 9621} 9622 9623function removeComments(jqNodes) { 9624 jqNodes = jqLite(jqNodes); 9625 var i = jqNodes.length; 9626 9627 if (i <= 1) { 9628 return jqNodes; 9629 } 9630 9631 while (i--) { 9632 var node = jqNodes[i]; 9633 if (node.nodeType === NODE_TYPE_COMMENT) { 9634 splice.call(jqNodes, i, 1); 9635 } 9636 } 9637 return jqNodes; 9638} 9639 9640var $controllerMinErr = minErr('$controller'); 9641 9642 9643var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 9644function identifierForController(controller, ident) { 9645 if (ident && isString(ident)) return ident; 9646 if (isString(controller)) { 9647 var match = CNTRL_REG.exec(controller); 9648 if (match) return match[3]; 9649 } 9650} 9651 9652 9653/** 9654 * @ngdoc provider 9655 * @name $controllerProvider 9656 * @description 9657 * The {@link ng.$controller $controller service} is used by Angular to create new 9658 * controllers. 9659 * 9660 * This provider allows controller registration via the 9661 * {@link ng.$controllerProvider#register register} method. 9662 */ 9663function $ControllerProvider() { 9664 var controllers = {}, 9665 globals = false; 9666 9667 /** 9668 * @ngdoc method 9669 * @name $controllerProvider#register 9670 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 9671 * the names and the values are the constructors. 9672 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 9673 * annotations in the array notation). 9674 */ 9675 this.register = function(name, constructor) { 9676 assertNotHasOwnProperty(name, 'controller'); 9677 if (isObject(name)) { 9678 extend(controllers, name); 9679 } else { 9680 controllers[name] = constructor; 9681 } 9682 }; 9683 9684 /** 9685 * @ngdoc method 9686 * @name $controllerProvider#allowGlobals 9687 * @description If called, allows `$controller` to find controller constructors on `window` 9688 */ 9689 this.allowGlobals = function() { 9690 globals = true; 9691 }; 9692 9693 9694 this.$get = ['$injector', '$window', function($injector, $window) { 9695 9696 /** 9697 * @ngdoc service 9698 * @name $controller 9699 * @requires $injector 9700 * 9701 * @param {Function|string} constructor If called with a function then it's considered to be the 9702 * controller constructor function. Otherwise it's considered to be a string which is used 9703 * to retrieve the controller constructor using the following steps: 9704 * 9705 * * check if a controller with given name is registered via `$controllerProvider` 9706 * * check if evaluating the string on the current scope returns a constructor 9707 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 9708 * `window` object (not recommended) 9709 * 9710 * The string can use the `controller as property` syntax, where the controller instance is published 9711 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 9712 * to work correctly. 9713 * 9714 * @param {Object} locals Injection locals for Controller. 9715 * @return {Object} Instance of given controller. 9716 * 9717 * @description 9718 * `$controller` service is responsible for instantiating controllers. 9719 * 9720 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 9721 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 9722 */ 9723 return function(expression, locals, later, ident) { 9724 // PRIVATE API: 9725 // param `later` --- indicates that the controller's constructor is invoked at a later time. 9726 // If true, $controller will allocate the object with the correct 9727 // prototype chain, but will not invoke the controller until a returned 9728 // callback is invoked. 9729 // param `ident` --- An optional label which overrides the label parsed from the controller 9730 // expression, if any. 9731 var instance, match, constructor, identifier; 9732 later = later === true; 9733 if (ident && isString(ident)) { 9734 identifier = ident; 9735 } 9736 9737 if (isString(expression)) { 9738 match = expression.match(CNTRL_REG); 9739 if (!match) { 9740 throw $controllerMinErr('ctrlfmt', 9741 "Badly formed controller string '{0}'. " + 9742 "Must match `__name__ as __id__` or `__name__`.", expression); 9743 } 9744 constructor = match[1], 9745 identifier = identifier || match[3]; 9746 expression = controllers.hasOwnProperty(constructor) 9747 ? controllers[constructor] 9748 : getter(locals.$scope, constructor, true) || 9749 (globals ? getter($window, constructor, true) : undefined); 9750 9751 assertArgFn(expression, constructor, true); 9752 } 9753 9754 if (later) { 9755 // Instantiate controller later: 9756 // This machinery is used to create an instance of the object before calling the 9757 // controller's constructor itself. 9758 // 9759 // This allows properties to be added to the controller before the constructor is 9760 // invoked. Primarily, this is used for isolate scope bindings in $compile. 9761 // 9762 // This feature is not intended for use by applications, and is thus not documented 9763 // publicly. 9764 // Object creation: http://jsperf.com/create-constructor/2 9765 var controllerPrototype = (isArray(expression) ? 9766 expression[expression.length - 1] : expression).prototype; 9767 instance = Object.create(controllerPrototype || null); 9768 9769 if (identifier) { 9770 addIdentifier(locals, identifier, instance, constructor || expression.name); 9771 } 9772 9773 var instantiate; 9774 return instantiate = extend(function() { 9775 var result = $injector.invoke(expression, instance, locals, constructor); 9776 if (result !== instance && (isObject(result) || isFunction(result))) { 9777 instance = result; 9778 if (identifier) { 9779 // If result changed, re-assign controllerAs value to scope. 9780 addIdentifier(locals, identifier, instance, constructor || expression.name); 9781 } 9782 } 9783 return instance; 9784 }, { 9785 instance: instance, 9786 identifier: identifier 9787 }); 9788 } 9789 9790 instance = $injector.instantiate(expression, locals, constructor); 9791 9792 if (identifier) { 9793 addIdentifier(locals, identifier, instance, constructor || expression.name); 9794 } 9795 9796 return instance; 9797 }; 9798 9799 function addIdentifier(locals, identifier, instance, name) { 9800 if (!(locals && isObject(locals.$scope))) { 9801 throw minErr('$controller')('noscp', 9802 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 9803 name, identifier); 9804 } 9805 9806 locals.$scope[identifier] = instance; 9807 } 9808 }]; 9809} 9810 9811/** 9812 * @ngdoc service 9813 * @name $document 9814 * @requires $window 9815 * 9816 * @description 9817 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 9818 * 9819 * @example 9820 <example module="documentExample"> 9821 <file name="index.html">
9822 <div ng-controller="ExampleController"> 9823 <p>$document title: <b ng-bind="title"></b></p> 9824 <p>window.document title: <b ng-bind="windowTitle"></b></p> 9825 </div> 9826 </file> 9827 <file name="script.js"> 9828 angular.module('documentExample', []) 9829 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 9830 $scope.title = $document[0].title; 9831 $scope.windowTitle = angular.element(window.document)[0].title; 9832 }]); 9833 </file> 9834 </example> 9835 */ 9836function $DocumentProvider() { 9837 this.$get = ['$window', function(window) { 9838 return jqLite(window.document); 9839 }]; 9840} 9841 9842/** 9843 * @ngdoc service 9844 * @name $exceptionHandler 9845 * @requires ng.$log 9846 * 9847 * @description 9848 * Any uncaught exception in angular expressions is delegated to this service. 9849 * The default implementation simply delegates to `$log.error` which logs it into 9850 * the browser console. 9851 * 9852 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 9853 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 9854 * 9855 * ## Example: 9856 * 9857 * ```js 9858 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 9859 * return function(exception, cause) { 9860 * exception.message += ' (caused by "' + cause + '")'; 9861 * throw exception; 9862 * }; 9863 * }); 9864 * ``` 9865 * 9866 * This example will override the normal action of `$exceptionHandler`, to make angular 9867 * exceptions fail hard when they happen, instead of just logging to the console. 9868 * 9869 * <hr /> 9870 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 9871 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 9872 * (unless executed during a digest). 9873 * 9874 * If you wish, you can manually delegate exceptions, e.g. 9875 * `try { ... } catch(e) { $exceptionHandler(e); }` 9876 * 9877 * @param {Error} exception Exception associated with the error. 9878 * @param {string=} cause optional information about the context in which 9879 * the error was thrown. 9880 * 9881 */ 9882function $ExceptionHandlerProvider() { 9883 this.$get = ['$log', function($log) { 9884 return function(exception, cause) { 9885 $log.error.apply($log, arguments); 9886 }; 9887 }]; 9888} 9889 9890var $$ForceReflowProvider = function() { 9891 this.$get = ['$document', function($document) { 9892 return function(domNode) { 9893 //the line below will force the browser to perform a repaint so 9894 //that all the animated elements within the animation frame will 9895 //be properly updated and drawn on screen. This is required to 9896 //ensure that the preparation animation is properly flushed so that 9897 //the active state picks up from there. DO NOT REMOVE THIS LINE. 9898 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 9899 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 9900 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 9901 if (domNode) { 9902 if (!domNode.nodeType && domNode instanceof jqLite) { 9903 domNode = domNode[0]; 9904 } 9905 } else { 9906 domNode = $document[0].body; 9907 } 9908 return domNode.offsetWidth + 1; 9909 }; 9910 }]; 9911}; 9912 9913var APPLICATION_JSON = 'application/json'; 9914var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 9915var JSON_START = /^\[|^\{(?!\{)/; 9916var JSON_ENDS = { 9917 '[': /]$/, 9918 '{': /}$/ 9919}; 9920var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 9921var $httpMinErr = minErr('$http'); 9922var $httpMinErrLegacyFn = function(method) { 9923 return function() { 9924 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 9925 }; 9926}; 9927 9928function serializeValue(v) { 9929 if (isObject(v)) { 9930 return isDate(v) ? v.toISOString() : toJson(v); 9931 } 9932 return v; 9933} 9934 9935 9936function $HttpParamSerializerProvider() { 9937 /** 9938 * @ngdoc service 9939 * @name $httpParamSerializer 9940 * @description 9941 * 9942 * Default {@link $http `$http`} params serializer that converts objects to strings 9943 * according to the following rules: 9944 * 9945 * * `{'foo': 'bar'}` results in `foo=bar` 9946 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 9947 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 9948 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 9949 * 9950 * Note that serializer will sort the request parameters alphabetically. 9951 * */ 9952 9953 this.$get = function() { 9954 return function ngParamSerializer(params) { 9955 if (!params) return ''; 9956 var parts = [];
vendor: 3,809 bytes, lines 9957-10083
9957 forEachSorted(params, function(value, key) { 9958 if (value === null || isUndefined(value)) return; 9959 if (isArray(value)) { 9960 forEach(value, function(v, k) { 9961 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 9962 }); 9963 } else { 9964 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 9965 } 9966 }); 9967 9968 return parts.join('&'); 9969 }; 9970 }; 9971} 9972 9973function $HttpParamSerializerJQLikeProvider() { 9974 /** 9975 * @ngdoc service 9976 * @name $httpParamSerializerJQLike 9977 * @description 9978 * 9979 * Alternative {@link $http `$http`} params serializer that follows 9980 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 9981 * The serializer will also sort the params alphabetically. 9982 * 9983 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 9984 * 9985 * ```js 9986 * $http({ 9987 * url: myUrl, 9988 * method: 'GET', 9989 * params: myParams, 9990 * paramSerializer: '$httpParamSerializerJQLike' 9991 * }); 9992 * ``` 9993 * 9994 * It is also possible to set it as the default `paramSerializer` in the 9995 * {@link $httpProvider#defaults `$httpProvider`}. 9996 * 9997 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 9998 * form data for submission: 9999 * 10000 * ```js 10001 * .controller(function($http, $httpParamSerializerJQLike) { 10002 * //... 10003 * 10004 * $http({ 10005 * url: myUrl, 10006 * method: 'POST', 10007 * data: $httpParamSerializerJQLike(myData), 10008 * headers: { 10009 * 'Content-Type': 'application/x-www-form-urlencoded' 10010 * } 10011 * }); 10012 * 10013 * }); 10014 * ``` 10015 * 10016 * */ 10017 this.$get = function() { 10018 return function jQueryLikeParamSerializer(params) { 10019 if (!params) return ''; 10020 var parts = []; 10021 serialize(params, '', true); 10022 return parts.join('&'); 10023 10024 function serialize(toSerialize, prefix, topLevel) { 10025 if (toSerialize === null || isUndefined(toSerialize)) return; 10026 if (isArray(toSerialize)) { 10027 forEach(toSerialize, function(value, index) { 10028 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 10029 }); 10030 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 10031 forEachSorted(toSerialize, function(value, key) { 10032 serialize(value, prefix + 10033 (topLevel ? '' : '[') + 10034 key + 10035 (topLevel ? '' : ']')); 10036 }); 10037 } else { 10038 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 10039 } 10040 } 10041 }; 10042 }; 10043} 10044 10045function defaultHttpResponseTransform(data, headers) { 10046 if (isString(data)) { 10047 // Strip json vulnerability protection prefix and trim whitespace 10048 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 10049 10050 if (tempData) { 10051 var contentType = headers('Content-Type'); 10052 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 10053 data = fromJson(tempData); 10054 } 10055 } 10056 } 10057 10058 return data; 10059} 10060 10061function isJsonLike(str) { 10062 var jsonStart = str.match(JSON_START); 10063 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 10064} 10065 10066/** 10067 * Parse headers into key value object 10068 * 10069 * @param {string} headers Raw headers as a string 10070 * @returns {Object} Parsed headers as key value object 10071 */ 10072function parseHeaders(headers) { 10073 var parsed = createMap(), i; 10074 10075 function fillInParsed(key, val) { 10076 if (key) { 10077 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 10078 } 10079 } 10080 10081 if (isString(headers)) { 10082 forEach(headers.split('\n'), function(line) { 10083 i = line.indexOf(':');
vendor: 5,581 bytes, lines 10084-10251
10084 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 10085 }); 10086 } else if (isObject(headers)) { 10087 forEach(headers, function(headerVal, headerKey) { 10088 fillInParsed(lowercase(headerKey), trim(headerVal)); 10089 }); 10090 } 10091 10092 return parsed; 10093} 10094 10095 10096/** 10097 * Returns a function that provides access to parsed headers. 10098 * 10099 * Headers are lazy parsed when first requested. 10100 * @see parseHeaders 10101 * 10102 * @param {(string|Object)} headers Headers to provide access to. 10103 * @returns {function(string=)} Returns a getter function which if called with: 10104 * 10105 * - if called with single an argument returns a single header value or null 10106 * - if called with no arguments returns an object containing all headers. 10107 */ 10108function headersGetter(headers) { 10109 var headersObj; 10110 10111 return function(name) { 10112 if (!headersObj) headersObj = parseHeaders(headers); 10113 10114 if (name) { 10115 var value = headersObj[lowercase(name)]; 10116 if (value === void 0) { 10117 value = null; 10118 } 10119 return value; 10120 } 10121 10122 return headersObj; 10123 }; 10124} 10125 10126 10127/** 10128 * Chain all given functions 10129 * 10130 * This function is used for both request and response transforming 10131 * 10132 * @param {*} data Data to transform. 10133 * @param {function(string=)} headers HTTP headers getter fn. 10134 * @param {number} status HTTP status code of the response. 10135 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 10136 * @returns {*} Transformed data. 10137 */ 10138function transformData(data, headers, status, fns) { 10139 if (isFunction(fns)) { 10140 return fns(data, headers, status); 10141 } 10142 10143 forEach(fns, function(fn) { 10144 data = fn(data, headers, status); 10145 }); 10146 10147 return data; 10148} 10149 10150 10151function isSuccess(status) { 10152 return 200 <= status && status < 300; 10153} 10154 10155 10156/** 10157 * @ngdoc provider 10158 * @name $httpProvider 10159 * @description 10160 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 10161 * */ 10162function $HttpProvider() { 10163 /** 10164 * @ngdoc property 10165 * @name $httpProvider#defaults 10166 * @description 10167 * 10168 * Object containing default values for all {@link ng.$http $http} requests. 10169 * 10170 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 10171 * that will provide the cache for all requests who set their `cache` property to `true`. 10172 * If you set the `defaults.cache = false` then only requests that specify their own custom 10173 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 10174 * 10175 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 10176 * Defaults value is `'XSRF-TOKEN'`. 10177 * 10178 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 10179 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 10180 * 10181 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 10182 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 10183 * setting default headers. 10184 * - **`defaults.headers.common`** 10185 * - **`defaults.headers.post`** 10186 * - **`defaults.headers.put`** 10187 * - **`defaults.headers.patch`** 10188 * 10189 * 10190 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 10191 * used to the prepare string representation of request parameters (specified as an object). 10192 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 10193 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 10194 * 10195 **/ 10196 var defaults = this.defaults = { 10197 // transform incoming response data 10198 transformResponse: [defaultHttpResponseTransform], 10199 10200 // transform outgoing request data 10201 transformRequest: [function(d) { 10202 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 10203 }], 10204 10205 // default headers 10206 headers: { 10207 common: { 10208 'Accept': 'application/json, text/plain, */*' 10209 }, 10210 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 10211 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 10212 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 10213 }, 10214 10215 xsrfCookieName: 'XSRF-TOKEN', 10216 xsrfHeaderName: 'X-XSRF-TOKEN', 10217 10218 paramSerializer: '$httpParamSerializer' 10219 }; 10220 10221 var useApplyAsync = false; 10222 /** 10223 * @ngdoc method 10224 * @name $httpProvider#useApplyAsync 10225 * @description 10226 * 10227 * Configure $http service to combine processing of multiple http responses received at around 10228 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 10229 * significant performance improvement for bigger applications that make many HTTP requests 10230 * concurrently (common during application bootstrap). 10231 * 10232 * Defaults to false. If no value is specified, returns the current configured value. 10233 * 10234 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 10235 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 10236 * to load and share the same digest cycle. 10237 * 10238 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 10239 * otherwise, returns the current configured value. 10240 **/ 10241 this.useApplyAsync = function(value) { 10242 if (isDefined(value)) { 10243 useApplyAsync = !!value; 10244 return this; 10245 } 10246 return useApplyAsync; 10247 }; 10248 10249 var useLegacyPromise = true; 10250 /** 10251 * @ngdoc method
10252 * @name $httpProvider#useLegacyPromiseExtensions 10253 * @description 10254 * 10255 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 10256 * This should be used to make sure that applications work without these methods. 10257 * 10258 * Defaults to true. If no value is specified, returns the current configured value. 10259 * 10260 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. 10261 * 10262 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 10263 * otherwise, returns the current configured value. 10264 **/ 10265 this.useLegacyPromiseExtensions = function(value) { 10266 if (isDefined(value)) { 10267 useLegacyPromise = !!value; 10268 return this; 10269 } 10270 return useLegacyPromise; 10271 }; 10272 10273 /** 10274 * @ngdoc property 10275 * @name $httpProvider#interceptors 10276 * @description 10277 * 10278 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 10279 * pre-processing of request or postprocessing of responses. 10280 * 10281 * These service factories are ordered by request, i.e. they are applied in the same order as the 10282 * array, on request, but reverse order, on response. 10283 * 10284 * {@link ng.$http#interceptors Interceptors detailed info} 10285 **/ 10286 var interceptorFactories = this.interceptors = []; 10287 10288 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 10289 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 10290 10291 var defaultCache = $cacheFactory('$http'); 10292 10293 /** 10294 * Make sure that default param serializer is exposed as a function 10295 */ 10296 defaults.paramSerializer = isString(defaults.paramSerializer) ? 10297 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 10298 10299 /** 10300 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 10301 * The reversal is needed so that we can build up the interception chain around the 10302 * server request. 10303 */ 10304 var reversedInterceptors = []; 10305 10306 forEach(interceptorFactories, function(interceptorFactory) { 10307 reversedInterceptors.unshift(isString(interceptorFactory) 10308 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 10309 }); 10310 10311 /** 10312 * @ngdoc service 10313 * @kind function 10314 * @name $http 10315 * @requires ng.$httpBackend 10316 * @requires $cacheFactory 10317 * @requires $rootScope 10318 * @requires $q 10319 * @requires $injector 10320 * 10321 * @description 10322 * The `$http` service is a core Angular service that facilitates communication with the remote 10323 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 10324 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 10325 * 10326 * For unit testing applications that use `$http` service, see 10327 * {@link ngMock.$httpBackend $httpBackend mock}. 10328 * 10329 * For a higher level of abstraction, please check out the {@link ngResource.$resource 10330 * $resource} service. 10331 * 10332 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 10333 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 10334 * it is important to familiarize yourself with these APIs and the guarantees they provide. 10335 * 10336 * 10337 * ## General usage 10338 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 10339 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 10340 * 10341 * ```js 10342 * // Simple GET request example: 10343 * $http({ 10344 * method: 'GET', 10345 * url: '/someUrl' 10346 * }).then(function successCallback(response) { 10347 * // this callback will be called asynchronously 10348 * // when the response is available 10349 * }, function errorCallback(response) { 10350 * // called asynchronously if an error occurs 10351 * // or server returns response with an error status. 10352 * }); 10353 * ``` 10354 * 10355 * The response object has these properties: 10356 * 10357 * - **data** â `{string|Object}` â The response body transformed with the transform 10358 * functions. 10359 * - **status** â `{number}` â HTTP status code of the response. 10360 * - **headers** â `{function([headerName])}` â Header getter function. 10361 * - **config** â `{Object}` â The configuration object that was used to generate the request. 10362 * - **statusText** â `{string}` â HTTP status text of the response. 10363 * 10364 * A response status code between 200 and 299 is considered a success status and 10365 * will result in the success callback being called. Note that if the response is a redirect, 10366 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 10367 * called for such responses. 10368 * 10369 * 10370 * ## Shortcut methods 10371 * 10372 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 10373 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 10374 * last argument. 10375 * 10376 * ```js 10377 * $http.get('/someUrl', config).then(successCallback, errorCallback); 10378 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 10379 * ``` 10380 * 10381 * Complete list of shortcut methods: 10382 * 10383 * - {@link ng.$http#get $http.get} 10384 * - {@link ng.$http#head $http.head} 10385 * - {@link ng.$http#post $http.post} 10386 * - {@link ng.$http#put $http.put} 10387 * - {@link ng.$http#delete $http.delete} 10388 * - {@link ng.$http#jsonp $http.jsonp} 10389 * - {@link ng.$http#patch $http.patch} 10390 * 10391 * 10392 * ## Writing Unit Tests that use $http 10393 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 10394 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 10395 * request using trained responses. 10396 * 10397 * ``` 10398 * $httpBackend.expectGET(...); 10399 * $http.get(...); 10400 * $httpBackend.flush(); 10401 * ``` 10402 * 10403 * ## Deprecation Notice 10404 * <div class="alert alert-danger"> 10405 * The `$http` legacy promise methods `success` and `error` have been deprecated. 10406 * Use the standard `then` method instead.
10407 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 10408 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 10409 * </div> 10410 * 10411 * ## Setting HTTP Headers 10412 * 10413 * The $http service will automatically add certain HTTP headers to all requests. These defaults 10414 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 10415 * object, which currently contains this default configuration: 10416 * 10417 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 10418 * - `Accept: application/json, text/plain, * / *` 10419 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 10420 * - `Content-Type: application/json` 10421 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 10422 * - `Content-Type: application/json` 10423 * 10424 * To add or overwrite these defaults, simply add or remove a property from these configuration 10425 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 10426 * with the lowercased HTTP method name as the key, e.g. 10427 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 10428 * 10429 * The defaults can also be set at runtime via the `$http.defaults` object in the same 10430 * fashion. For example: 10431 * 10432 * ``` 10433 * module.run(function($http) { 10434 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 10435 * }); 10436 * ``` 10437 * 10438 * In addition, you can supply a `headers` property in the config object passed when 10439 * calling `$http(config)`, which overrides the defaults without changing them globally. 10440 * 10441 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 10442 * Use the `headers` property, setting the desired header to `undefined`. For example: 10443 * 10444 * ```js 10445 * var req = { 10446 * method: 'POST', 10447 * url: 'http://example.com', 10448 * headers: { 10449 * 'Content-Type': undefined 10450 * }, 10451 * data: { test: 'test' } 10452 * } 10453 * 10454 * $http(req).then(function(){...}, function(){...}); 10455 * ``` 10456 * 10457 * ## Transforming Requests and Responses 10458 * 10459 * Both requests and responses can be transformed using transformation functions: `transformRequest` 10460 * and `transformResponse`. These properties can be a single function that returns 10461 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 10462 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 10463 * 10464 * ### Default Transformations 10465 * 10466 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 10467 * `defaults.transformResponse` properties. If a request does not provide its own transformations 10468 * then these will be applied. 10469 * 10470 * You can augment or replace the default transformations by modifying these properties by adding to or 10471 * replacing the array. 10472 * 10473 * Angular provides the following default transformations: 10474 * 10475 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 10476 * 10477 * - If the `data` property of the request configuration object contains an object, serialize it 10478 * into JSON format. 10479 * 10480 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 10481 * 10482 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 10483 * - If JSON response is detected, deserialize it using a JSON parser. 10484 * 10485 * 10486 * ### Overriding the Default Transformations Per Request 10487 * 10488 * If you wish override the request/response transformations only for a single request then provide 10489 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 10490 * into `$http`. 10491 * 10492 * Note that if you provide these properties on the config object the default transformations will be 10493 * overwritten. If you wish to augment the default transformations then you must include them in your 10494 * local transformation array. 10495 * 10496 * The following code demonstrates adding a new response transformation to be run after the default response 10497 * transformations have been run. 10498 * 10499 * ```js 10500 * function appendTransform(defaults, transform) { 10501 * 10502 * // We can't guarantee that the default transformation is an array 10503 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 10504 * 10505 * // Append the new transformation to the defaults 10506 * return defaults.concat(transform); 10507 * } 10508 * 10509 * $http({ 10510 * url: '...', 10511 * method: 'GET', 10512 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 10513 * return doTransform(value); 10514 * }) 10515 * }); 10516 * ``` 10517 * 10518 * 10519 * ## Caching 10520 *
10521 * To enable caching, set the request configuration `cache` property to `true` (to use default 10522 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 10523 * When the cache is enabled, `$http` stores the response from the server in the specified 10524 * cache. The next time the same request is made, the response is served from the cache without 10525 * sending a request to the server. 10526 * 10527 * Note that even if the response is served from cache, delivery of the data is asynchronous in 10528 * the same way that real requests are. 10529 * 10530 * If there are multiple GET requests for the same URL that should be cached using the same 10531 * cache, but the cache is not populated yet, only one request to the server will be made and 10532 * the remaining requests will be fulfilled using the response from the first request. 10533 * 10534 * You can change the default cache to a new object (built with 10535 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 10536 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 10537 * their `cache` property to `true` will now use this cache object. 10538 * 10539 * If you set the default cache to `false` then only requests that specify their own custom 10540 * cache object will be cached. 10541 * 10542 * ## Interceptors 10543 * 10544 * Before you start creating interceptors, be sure to understand the 10545 * {@link ng.$q $q and deferred/promise APIs}. 10546 * 10547 * For purposes of global error handling, authentication, or any kind of synchronous or 10548 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 10549 * able to intercept requests before they are handed to the server and 10550 * responses before they are handed over to the application code that 10551 * initiated these requests. The interceptors leverage the {@link ng.$q 10552 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 10553 * 10554 * The interceptors are service factories that are registered with the `$httpProvider` by 10555 * adding them to the `$httpProvider.interceptors` array. The factory is called and 10556 * injected with dependencies (if specified) and returns the interceptor. 10557 * 10558 * There are two kinds of interceptors (and two kinds of rejection interceptors): 10559 * 10560 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 10561 * modify the `config` object or create a new one. The function needs to return the `config` 10562 * object directly, or a promise containing the `config` or a new `config` object. 10563 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 10564 * resolved with a rejection. 10565 * * `response`: interceptors get called with http `response` object. The function is free to 10566 * modify the `response` object or create a new one. The function needs to return the `response` 10567 * object directly, or as a promise containing the `response` or a new `response` object. 10568 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 10569 * resolved with a rejection. 10570 * 10571 * 10572 * ```js 10573 * // register the interceptor as a service 10574 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 10575 * return { 10576 * // optional method 10577 * 'request': function(config) { 10578 * // do something on success 10579 * return config; 10580 * }, 10581 * 10582 * // optional method 10583 * 'requestError': function(rejection) { 10584 * // do something on error 10585 * if (canRecover(rejection)) { 10586 * return responseOrNewPromise 10587 * } 10588 * return $q.reject(rejection); 10589 * }, 10590 * 10591 * 10592 * 10593 * // optional method 10594 * 'response': function(response) { 10595 * // do something on success 10596 * return response; 10597 * }, 10598 * 10599 * // optional method 10600 * 'responseError': function(rejection) {
10601 * // do something on error 10602 * if (canRecover(rejection)) { 10603 * return responseOrNewPromise 10604 * } 10605 * return $q.reject(rejection); 10606 * } 10607 * }; 10608 * }); 10609 * 10610 * $httpProvider.interceptors.push('myHttpInterceptor'); 10611 * 10612 * 10613 * // alternatively, register the interceptor via an anonymous factory 10614 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 10615 * return { 10616 * 'request': function(config) { 10617 * // same as above 10618 * }, 10619 * 10620 * 'response': function(response) { 10621 * // same as above 10622 * } 10623 * }; 10624 * }); 10625 * ``` 10626 * 10627 * ## Security Considerations 10628 * 10629 * When designing web applications, consider security threats from: 10630 * 10631 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10632 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 10633 * 10634 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 10635 * pre-configured with strategies that address these issues, but for this to work backend server 10636 * cooperation is required. 10637 * 10638 * ### JSON Vulnerability Protection 10639 * 10640 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10641 * allows third party website to turn your JSON resource URL into 10642 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 10643 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 10644 * Angular will automatically strip the prefix before processing it as JSON. 10645 * 10646 * For example if your server needs to return: 10647 * ```js 10648 * ['one','two'] 10649 * ``` 10650 * 10651 * which is vulnerable to attack, your server can return: 10652 * ```js 10653 * )]}', 10654 * ['one','two'] 10655 * ``` 10656 * 10657 * Angular will strip the prefix, before processing the JSON. 10658 * 10659 * 10660 * ### Cross Site Request Forgery (XSRF) Protection 10661 * 10662 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 10663 * an unauthorized site can gain your user's private data. Angular provides a mechanism 10664 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 10665 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 10666 * JavaScript that runs on your domain could read the cookie, your server can be assured that 10667 * the XHR came from JavaScript running on your domain. The header will not be set for 10668 * cross-domain requests. 10669 * 10670 * To take advantage of this, your server needs to set a token in a JavaScript readable session 10671 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 10672 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 10673 * that only JavaScript running on your domain could have sent the request. The token must be 10674 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 10675 * making up its own tokens). We recommend that the token is a digest of your site's 10676 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 10677 * for added security. 10678 * 10679 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 10680 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 10681 * or the per-request config object. 10682 * 10683 * In order to prevent collisions in environments where multiple Angular apps share the 10684 * same domain or subdomain, we recommend that each application uses unique cookie name. 10685 * 10686 * @param {object} config Object describing the request to be made and how it should be 10687 * processed. The object has following properties: 10688 * 10689 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 10690 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 10691 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 10692 * with the `paramSerializer` and appended as GET parameters. 10693 * - **data** â `{string|Object}` â Data to be sent as the request message data. 10694 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 10695 * HTTP headers to send to the server. If the return value of a function is null, the 10696 * header will not be sent. Functions accept a config object as an argument. 10697 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 10698 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 10699 * - **transformRequest** â 10700 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
10700}` â 10701 * transform function or an array of such functions. The transform function takes the http 10702 * request body and headers and returns its transformed (typically serialized) version. 10703 * See {@link ng.$http#overriding-the-default-transformations-per-request 10704 * Overriding the Default Transformations} 10705 * - **transformResponse** â 10706 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 10707 * transform function or an array of such functions. The transform function takes the http 10708 * response body, headers and status and returns its transformed (typically deserialized) version. 10709 * See {@link ng.$http#overriding-the-default-transformations-per-request 10710 * Overriding the Default TransformationjqLiks} 10711 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 10712 * prepare the string representation of request parameters (specified as an object). 10713 * If specified as string, it is interpreted as function registered with the 10714 * {@link $injector $injector}, which means you can create your own serializer 10715 * by registering it as a {@link auto.$provide#service service}. 10716 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 10717 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 10718 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 10719 * GET request, otherwise if a cache instance built with 10720 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 10721 * caching. 10722 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 10723 * that should abort the request when resolved. 10724 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 10725 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 10726 * for more information. 10727 * - **responseType** - `{string}` - see 10728 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 10729 * 10730 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 10731 * when the request succeeds or fails. 10732 * 10733 * 10734 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 10735 * requests. This is primarily meant to be used for debugging purposes. 10736 * 10737 * 10738 * @example 10739<example module="httpExample"> 10740<file name="index.html"> 10741 <div ng-controller="FetchController"> 10742 <select ng-model="method" aria-label="Request method"> 10743 <option>GET</option> 10744 <option>JSONP</option> 10745 </select> 10746 <input type="text" ng-model="url" size="80" aria-label="URL" /> 10747 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 10748 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 10749 <button id="samplejsonpbtn" 10750 ng-click="updateModel('JSONP', 10751 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 10752 Sample JSONP 10753 </button> 10754 <button id="invalidjsonpbtn" 10755 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 10756 Invalid JSONP 10757 </button> 10758 <pre>http status code: {{status}}</pre> 10759 <pre>http response data: {{data}}</pre> 10760 </div> 10761</file> 10762<file name="script.js"> 10763 angular.module('httpExample', []) 10764 .controller('FetchController', ['$scope', '$http', '$templateCache', 10765 function($scope, $http, $templateCache) { 10766 $scope.method = 'GET'; 10767 $scope.url = 'http-hello.html'; 10768 10769 $scope.fetch = function() { 10770 $scope.code = null; 10771 $scope.response = null; 10772 10773 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 10774 then(function(response) { 10775 $scope.status = response.status; 10776 $scope.data = response.data; 10777 }, function(response) { 10778 $scope.data = response.data || "Request failed"; 10779 $scope.status = response.status; 10780 }); 10781 }; 10782 10783 $scope.updateModel = function(method, url) { 10784 $scope.method = method; 10785 $scope.url = url; 10786 }; 10787 }]); 10788</file> 10789<file name="http-hello.html"> 10790 Hello, $http! 10791</file> 10792<file name="protractor.js" type="protractor"> 10793 var status = element(by.binding('status')); 10794 var data = element(by.binding('data')); 10795 var fetchBtn = element(by.id('fetchbtn')); 10796 var sampleGetBtn = element(by.id('samplegetbtn')); 10797 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 10798 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 10799 10800 it('should make an xhr GET request', function() { 10801 sampleGetBtn.click(); 10802 fetchBtn.click(); 10803 expect(status.getText()).toMatch('200'); 10804 expect(data.getText()).toMatch(/Hello, \$http!/); 10805 }); 10806 10807// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 10808// it('should make a JSONP request to angularjs.org', function() { 10809// sampleJsonpBtn.click();
10810// fetchBtn.click(); 10811// expect(status.getText()).toMatch('200'); 10812// expect(data.getText()).toMatch(/Super Hero!/); 10813// }); 10814 10815 it('should make JSONP request to invalid URL and invoke the error handler', 10816 function() { 10817 invalidJsonpBtn.click(); 10818 fetchBtn.click(); 10819 expect(status.getText()).toMatch('0'); 10820 expect(data.getText()).toMatch('Request failed'); 10821 }); 10822</file> 10823</example> 10824 */ 10825 function $http(requestConfig) { 10826 10827 if (!isObject(requestConfig)) { 10828 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 10829 } 10830 10831 if (!isString(requestConfig.url)) { 10832 throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); 10833 } 10834 10835 var config = extend({ 10836 method: 'get', 10837 transformRequest: defaults.transformRequest, 10838 transformResponse: defaults.transformResponse, 10839 paramSerializer: defaults.paramSerializer 10840 }, requestConfig); 10841 10842 config.headers = mergeHeaders(requestConfig); 10843 config.method = uppercase(config.method); 10844 config.paramSerializer = isString(config.paramSerializer) ? 10845 $injector.get(config.paramSerializer) : config.paramSerializer; 10846 10847 var serverRequest = function(config) { 10848 var headers = config.headers; 10849 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 10850 10851 // strip content-type if data is undefined 10852 if (isUndefined(reqData)) { 10853 forEach(headers, function(value, header) { 10854 if (lowercase(header) === 'content-type') { 10855 delete headers[header]; 10856 } 10857 }); 10858 } 10859 10860 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 10861 config.withCredentials = defaults.withCredentials; 10862 } 10863 10864 // send request 10865 return sendReq(config, reqData).then(transformResponse, transformResponse); 10866 }; 10867 10868 var chain = [serverRequest, undefined]; 10869 var promise = $q.when(config); 10870 10871 // apply interceptors 10872 forEach(reversedInterceptors, function(interceptor) { 10873 if (interceptor.request || interceptor.requestError) { 10874 chain.unshift(interceptor.request, interceptor.requestError); 10875 } 10876 if (interceptor.response || interceptor.responseError) { 10877 chain.push(interceptor.response, interceptor.responseError); 10878 } 10879 }); 10880 10881 while (chain.length) { 10882 var thenFn = chain.shift(); 10883 var rejectFn = chain.shift(); 10884 10885 promise = promise.then(thenFn, rejectFn); 10886 } 10887 10888 if (useLegacyPromise) { 10889 promise.success = function(fn) { 10890 assertArgFn(fn, 'fn'); 10891 10892 promise.then(function(response) { 10893 fn(response.data, response.status, response.headers, config); 10894 }); 10895 return promise; 10896 }; 10897 10898 promise.error = function(fn) { 10899 assertArgFn(fn, 'fn'); 10900 10901 promise.then(null, function(response) { 10902 fn(response.data, response.status, response.headers, config); 10903 }); 10904 return promise; 10905 }; 10906 } else { 10907 promise.success = $httpMinErrLegacyFn('success'); 10908 promise.error = $httpMinErrLegacyFn('error'); 10909 } 10910 10911 return promise; 10912 10913 function transformResponse(response) { 10914 // make a copy since the response must be cacheable 10915 var resp = extend({}, response); 10916 resp.data = transformData(response.data, response.headers, response.status, 10917 config.transformResponse); 10918 return (isSuccess(response.status)) 10919 ? resp 10920 : $q.reject(resp); 10921 } 10922 10923 function executeHeaderFns(headers, config) { 10924 var headerContent, processedHeaders = {}; 10925 10926 forEach(headers, function(headerFn, header) { 10927 if (isFunction(headerFn)) { 10928 headerContent = headerFn(config); 10929 if (headerContent != null) { 10930 processedHeaders[header] = headerContent; 10931 } 10932 } else { 10933 processedHeaders[header] = headerFn; 10934 } 10935 }); 10936 10937 return processedHeaders; 10938 } 10939 10940 function mergeHeaders(config) { 10941 var defHeaders = defaults.headers, 10942 reqHeaders = extend({}, config.headers), 10943 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 10944 10945 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 10946
vendor: 9,400 bytes, lines 10947-11247
10947 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 10948 defaultHeadersIteration: 10949 for (defHeaderName in defHeaders) { 10950 lowercaseDefHeaderName = lowercase(defHeaderName); 10951 10952 for (reqHeaderName in reqHeaders) { 10953 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 10954 continue defaultHeadersIteration; 10955 } 10956 } 10957 10958 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 10959 } 10960 10961 // execute if header value is a function for merged headers 10962 return executeHeaderFns(reqHeaders, shallowCopy(config)); 10963 } 10964 } 10965 10966 $http.pendingRequests = []; 10967 10968 /** 10969 * @ngdoc method 10970 * @name $http#get 10971 * 10972 * @description 10973 * Shortcut method to perform `GET` request. 10974 * 10975 * @param {string} url Relative or absolute URL specifying the destination of the request 10976 * @param {Object=} config Optional configuration object 10977 * @returns {HttpPromise} Future object 10978 */ 10979 10980 /** 10981 * @ngdoc method 10982 * @name $http#delete 10983 * 10984 * @description 10985 * Shortcut method to perform `DELETE` request. 10986 * 10987 * @param {string} url Relative or absolute URL specifying the destination of the request 10988 * @param {Object=} config Optional configuration object 10989 * @returns {HttpPromise} Future object 10990 */ 10991 10992 /** 10993 * @ngdoc method 10994 * @name $http#head 10995 * 10996 * @description 10997 * Shortcut method to perform `HEAD` request. 10998 * 10999 * @param {string} url Relative or absolute URL specifying the destination of the request 11000 * @param {Object=} config Optional configuration object 11001 * @returns {HttpPromise} Future object 11002 */ 11003 11004 /** 11005 * @ngdoc method 11006 * @name $http#jsonp 11007 * 11008 * @description 11009 * Shortcut method to perform `JSONP` request. 11010 * 11011 * @param {string} url Relative or absolute URL specifying the destination of the request. 11012 * The name of the callback should be the string `JSON_CALLBACK`. 11013 * @param {Object=} config Optional configuration object 11014 * @returns {HttpPromise} Future object 11015 */ 11016 createShortMethods('get', 'delete', 'head', 'jsonp'); 11017 11018 /** 11019 * @ngdoc method 11020 * @name $http#post 11021 * 11022 * @description 11023 * Shortcut method to perform `POST` request. 11024 * 11025 * @param {string} url Relative or absolute URL specifying the destination of the request 11026 * @param {*} data Request content 11027 * @param {Object=} config Optional configuration object 11028 * @returns {HttpPromise} Future object 11029 */ 11030 11031 /** 11032 * @ngdoc method 11033 * @name $http#put 11034 * 11035 * @description 11036 * Shortcut method to perform `PUT` request. 11037 * 11038 * @param {string} url Relative or absolute URL specifying the destination of the request 11039 * @param {*} data Request content 11040 * @param {Object=} config Optional configuration object 11041 * @returns {HttpPromise} Future object 11042 */ 11043 11044 /** 11045 * @ngdoc method 11046 * @name $http#patch 11047 * 11048 * @description 11049 * Shortcut method to perform `PATCH` request. 11050 * 11051 * @param {string} url Relative or absolute URL specifying the destination of the request 11052 * @param {*} data Request content 11053 * @param {Object=} config Optional configuration object 11054 * @returns {HttpPromise} Future object 11055 */ 11056 createShortMethodsWithData('post', 'put', 'patch'); 11057 11058 /** 11059 * @ngdoc property 11060 * @name $http#defaults 11061 * 11062 * @description 11063 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 11064 * default headers, withCredentials as well as request and response transformations. 11065 * 11066 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 11067 */ 11068 $http.defaults = defaults; 11069 11070 11071 return $http; 11072 11073 11074 function createShortMethods(names) { 11075 forEach(arguments, function(name) { 11076 $http[name] = function(url, config) { 11077 return $http(extend({}, config || {}, { 11078 method: name, 11079 url: url 11080 })); 11081 }; 11082 }); 11083 } 11084 11085 11086 function createShortMethodsWithData(name) { 11087 forEach(arguments, function(name) { 11088 $http[name] = function(url, data, config) { 11089 return $http(extend({}, config || {}, { 11090 method: name, 11091 url: url, 11092 data: data 11093 })); 11094 }; 11095 }); 11096 } 11097 11098 11099 /** 11100 * Makes the request. 11101 * 11102 * !!! ACCESSES CLOSURE VARS: 11103 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 11104 */ 11105 function sendReq(config, reqData) { 11106 var deferred = $q.defer(), 11107 promise = deferred.promise, 11108 cache, 11109 cachedResp, 11110 reqHeaders = config.headers, 11111 url = buildUrl(config.url, config.paramSerializer(config.params)); 11112 11113 $http.pendingRequests.push(config); 11114 promise.then(removePendingReq, removePendingReq); 11115 11116 11117 if ((config.cache || defaults.cache) && config.cache !== false && 11118 (config.method === 'GET' || config.method === 'JSONP')) { 11119 cache = isObject(config.cache) ? config.cache 11120 : isObject(defaults.cache) ? defaults.cache 11121 : defaultCache; 11122 } 11123 11124 if (cache) { 11125 cachedResp = cache.get(url); 11126 if (isDefined(cachedResp)) { 11127 if (isPromiseLike(cachedResp)) { 11128 // cached request has already been sent, but there is no response yet 11129 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 11130 } else { 11131 // serving from cache 11132 if (isArray(cachedResp)) { 11133 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 11134 } else { 11135 resolvePromise(cachedResp, 200, {}, 'OK'); 11136 } 11137 } 11138 } else { 11139 // put the promise for the non-transformed response into cache as a placeholder 11140 cache.put(url, promise); 11141 } 11142 } 11143 11144 11145 // if we won't have the response in cache, set the xsrf headers and 11146 // send the request to the backend 11147 if (isUndefined(cachedResp)) { 11148 var xsrfValue = urlIsSameOrigin(config.url) 11149 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 11150 : undefined; 11151 if (xsrfValue) { 11152 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 11153 } 11154 11155 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 11156 config.withCredentials, config.responseType); 11157 } 11158 11159 return promise; 11160 11161 11162 /** 11163 * Callback registered to $httpBackend(): 11164 * - caches the response if desired 11165 * - resolves the raw $http promise 11166 * - calls $apply 11167 */ 11168 function done(status, response, headersString, statusText) { 11169 if (cache) { 11170 if (isSuccess(status)) { 11171 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 11172 } else { 11173 // remove promise from the cache 11174 cache.remove(url); 11175 } 11176 } 11177 11178 function resolveHttpPromise() { 11179 resolvePromise(response, status, headersString, statusText); 11180 } 11181 11182 if (useApplyAsync) { 11183 $rootScope.$applyAsync(resolveHttpPromise); 11184 } else { 11185 resolveHttpPromise(); 11186 if (!$rootScope.$$phase) $rootScope.$apply(); 11187 } 11188 } 11189 11190 11191 /** 11192 * Resolves the raw $http promise. 11193 */ 11194 function resolvePromise(response, status, headers, statusText) { 11195 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 11196 status = status >= -1 ? status : 0; 11197 11198 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 11199 data: response, 11200 status: status, 11201 headers: headersGetter(headers), 11202 config: config, 11203 statusText: statusText 11204 }); 11205 } 11206 11207 function resolvePromiseWithResult(result) { 11208 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 11209 } 11210 11211 function removePendingReq() { 11212 var idx = $http.pendingRequests.indexOf(config); 11213 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 11214 } 11215 } 11216 11217 11218 function buildUrl(url, serializedParams) { 11219 if (serializedParams.length > 0) { 11220 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 11221 } 11222 return url; 11223 } 11224 }]; 11225} 11226 11227/** 11228 * @ngdoc service 11229 * @name $xhrFactory 11230 * 11231 * @description 11232 * Factory function used to create XMLHttpRequest objects. 11233 * 11234 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 11235 * 11236 * ``` 11237 * angular.module('myApp', []) 11238 * .factory('$xhrFactory', function() { 11239 * return function createXhr(method, url) { 11240 * return new window.XMLHttpRequest({mozSystem: true}); 11241 * }; 11242 * }); 11243 * ``` 11244 * 11245 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 11246 * @param {string} url URL of the request. 11247 */
11248function $xhrFactoryProvider() { 11249 this.$get = function() { 11250 return function createXhr() { 11251 return new window.XMLHttpRequest(); 11252 }; 11253 }; 11254} 11255 11256/** 11257 * @ngdoc service 11258 * @name $httpBackend 11259 * @requires $window 11260 * @requires $document 11261 * @requires $xhrFactory 11262 * 11263 * @description 11264 * HTTP backend used by the {@link ng.$http service} that delegates to 11265 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 11266 * 11267 * You should never need to use this service directly, instead use the higher-level abstractions: 11268 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 11269 * 11270 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 11271 * $httpBackend} which can be trained with responses. 11272 */ 11273function $HttpBackendProvider() { 11274 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 11275 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 11276 }]; 11277} 11278 11279function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 11280 // TODO(vojta): fix the signature 11281 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 11282 $browser.$$incOutstandingRequestCount(); 11283 url = url || $browser.url(); 11284 11285 if (lowercase(method) == 'jsonp') { 11286 var callbackId = '_' + (callbacks.counter++).toString(36); 11287 callbacks[callbackId] = function(data) { 11288 callbacks[callbackId].data = data; 11289 callbacks[callbackId].called = true; 11290 }; 11291 11292 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 11293 callbackId, function(status, text) { 11294 completeRequest(callback, status, callbacks[callbackId].data, "", text); 11295 callbacks[callbackId] = noop; 11296 }); 11297 } else { 11298 11299 var xhr = createXhr(method, url); 11300 11301 xhr.open(method, url, true); 11302 forEach(headers, function(value, key) { 11303 if (isDefined(value)) { 11304 xhr.setRequestHeader(key, value); 11305 } 11306 }); 11307 11308 xhr.onload = function requestLoaded() { 11309 var statusText = xhr.statusText || ''; 11310 11311 // responseText is the old-school way of retrieving response (supported by IE9) 11312 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 11313 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 11314 11315 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 11316 var status = xhr.status === 1223 ? 204 : xhr.status; 11317 11318 // fix status code when it is 0 (0 status is undocumented). 11319 // Occurs when accessing file resources or on Android 4.1 stock browser 11320 // while retrieving files from application cache. 11321 if (status === 0) { 11322 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 11323 } 11324 11325 completeRequest(callback, 11326 status, 11327 response, 11328 xhr.getAllResponseHeaders(), 11329 statusText); 11330 }; 11331 11332 var requestError = function() { 11333 // The response is always empty 11334 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 11335 completeRequest(callback, -1, null, null, ''); 11336 }; 11337 11338 xhr.onerror = requestError; 11339 xhr.onabort = requestError; 11340 11341 if (withCredentials) { 11342 xhr.withCredentials = true; 11343 } 11344 11345 if (responseType) { 11346 try { 11347 xhr.responseType = responseType; 11348 } catch (e) { 11349 // WebKit added support for the json responseType value on 09/03/2013 11350 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 11351 // known to throw when setting the value "json" as the response type. Other older 11352 // browsers implementing the responseType 11353 // 11354 // The json response type can be ignored if not supported, because JSON payloads are 11355 // parsed on the client-side regardless. 11356 if (responseType !== 'json') { 11357 throw e; 11358 } 11359 } 11360 } 11361
11362 xhr.send(isUndefined(post) ? null : post); 11363 } 11364 11365 if (timeout > 0) { 11366 var timeoutId = $browserDefer(timeoutRequest, timeout); 11367 } else if (isPromiseLike(timeout)) { 11368 timeout.then(timeoutRequest); 11369 } 11370 11371 11372 function timeoutRequest() { 11373 jsonpDone && jsonpDone(); 11374 xhr && xhr.abort(); 11375 } 11376 11377 function completeRequest(callback, status, response, headersString, statusText) { 11378 // cancel timeout and subsequent timeout promise resolution 11379 if (isDefined(timeoutId)) { 11380 $browserDefer.cancel(timeoutId); 11381 } 11382 jsonpDone = xhr = null; 11383 11384 callback(status, response, headersString, statusText); 11385 $browser.$$completeOutstandingRequest(noop); 11386 } 11387 }; 11388 11389 function jsonpReq(url, callbackId, done) { 11390 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 11391 // - fetches local scripts via XHR and evals them 11392 // - adds and immediately removes script elements from the document 11393 var script = rawDocument.createElement('script'), callback = null; 11394 script.type = "text/javascript"; 11395 script.src = url; 11396 script.async = true; 11397 11398 callback = function(event) { 11399 removeEventListenerFn(script, "load", callback); 11400 removeEventListenerFn(script, "error", callback); 11401 rawDocument.body.removeChild(script); 11402 script = null; 11403 var status = -1; 11404 var text = "unknown"; 11405 11406 if (event) { 11407 if (event.type === "load" && !callbacks[callbackId].called) { 11408 event = { type: "error" }; 11409 } 11410 text = event.type; 11411 status = event.type === "error" ? 404 : 200; 11412 } 11413 11414 if (done) { 11415 done(status, text); 11416 } 11417 }; 11418 11419 addEventListenerFn(script, "load", callback); 11420 addEventListenerFn(script, "error", callback); 11421 rawDocument.body.appendChild(script); 11422 return callback; 11423 } 11424} 11425 11426var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 11427$interpolateMinErr.throwNoconcat = function(text) { 11428 throw $interpolateMinErr('noconcat', 11429 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 11430 "interpolations that concatenate multiple expressions when a trusted value is " + 11431 "required. See http://docs.angularjs.org/api/ng.$sce", text); 11432}; 11433 11434$interpolateMinErr.interr = function(text, err) { 11435 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 11436}; 11437 11438/** 11439 * @ngdoc provider 11440 * @name $interpolateProvider 11441 * 11442 * @description 11443 *
11444 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 11445 * 11446 * <div class="alert alert-danger"> 11447 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular 11448 * template within a Python Jinja template (or any other template language). Mixing templating 11449 * languages is **very dangerous**. The embedding template language will not safely escape Angular 11450 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 11451 * security bugs! 11452 * </div> 11453 * 11454 * @example 11455<example name="custom-interpolation-markup" module="customInterpolationApp"> 11456<file name="index.html"> 11457<script> 11458 var customInterpolationApp = angular.module('customInterpolationApp', []); 11459 11460 customInterpolationApp.config(function($interpolateProvider) { 11461 $interpolateProvider.startSymbol('//'); 11462 $interpolateProvider.endSymbol('//'); 11463 }); 11464 11465 11466 customInterpolationApp.controller('DemoController', function() { 11467 this.label = "This binding is brought you by // interpolation symbols."; 11468 }); 11469</script> 11470<div ng-controller="DemoController as demo"> 11471 //demo.label// 11472</div> 11473</file> 11474<file name="protractor.js" type="protractor"> 11475 it('should interpolate binding with custom symbols', function() { 11476 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 11477 }); 11478</file> 11479</example> 11480 */ 11481function $InterpolateProvider() { 11482 var startSymbol = '{{'; 11483 var endSymbol = '}}'; 11484 11485 /** 11486 * @ngdoc method 11487 * @name $interpolateProvider#startSymbol 11488 * @description 11489 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 11490 * 11491 * @param {string=} value new value to set the starting symbol to. 11492 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11493 */ 11494 this.startSymbol = function(value) { 11495 if (value) { 11496 startSymbol = value; 11497 return this; 11498 } else { 11499 return startSymbol; 11500 } 11501 }; 11502 11503 /** 11504 * @ngdoc method 11505 * @name $interpolateProvider#endSymbol 11506 * @description 11507 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11508 * 11509 * @param {string=} value new value to set the ending symbol to. 11510 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11511 */ 11512 this.endSymbol = function(value) { 11513 if (value) { 11514 endSymbol = value; 11515 return this; 11516 } else { 11517 return endSymbol; 11518 } 11519 }; 11520 11521 11522 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 11523 var startSymbolLength = startSymbol.length, 11524 endSymbolLength = endSymbol.length, 11525 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 11526 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 11527 11528 function escape(ch) { 11529 return '\\\\\\' + ch; 11530 } 11531 11532 function unescapeText(text) { 11533 return text.replace(escapedStartRegexp, startSymbol). 11534 replace(escapedEndRegexp, endSymbol); 11535 } 11536 11537 function stringify(value) { 11538 if (value == null) { // null || undefined 11539 return ''; 11540 } 11541 switch (typeof value) { 11542 case 'string': 11543 break; 11544 case 'number': 11545 value = '' + value; 11546 break; 11547 default: 11548 value = toJson(value); 11549 } 11550 11551 return value; 11552 } 11553 11554 //TODO: this is the same as the constantWatchDelegate in parse.js 11555 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 11556 var unwatch; 11557 return unwatch = scope.$watch(function constantInterpolateWatch(scope) { 11558 unwatch(); 11559 return constantInterp(scope); 11560 }, listener, objectEquality); 11561 } 11562 11563 /** 11564 * @ngdoc service 11565 * @name $interpolate 11566 * @kind function 11567 * 11568 * @requires $parse 11569 * @requires $sce 11570 * 11571 * @description 11572 * 11573 * Compiles a string with markup into an interpolation function. This service is used by the 11574 * HTML {@link ng.$compile $compile} service for data binding. See 11575 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 11576 * interpolation markup. 11577 * 11578 * 11579 * ```js 11580 * var $interpolate = ...; // injected 11581 * var exp = $interpolate('Hello {{name | uppercase}}!'); 11582 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 11583 * ``` 11584 * 11585 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 11586 * `true`, the interpolation function will return `undefined` unless all embedded expressions 11587 * evaluate to a value other than `undefined`. 11588 * 11589 * ```js 11590 * var $interpolate = ...; // injected 11591 * var context = {greeting: 'Hello', name: undefined }; 11592 * 11593 * // default "forgiving" mode 11594 * var exp = $interpolate('{{greeting}} {{name}}!'); 11595 * expect(exp(context)).toEqual('Hello !'); 11596 * 11597 * // "allOrNothing" mode 11598 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 11599 * expect(exp(context)).toBeUndefined(); 11600 * context.name = 'Angular'; 11601 * expect(exp(context)).toEqual('Hello Angular!'); 11602 * ``` 11603 * 11604 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 11605 * 11606 * ####Escaped Interpolation 11607 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 11608 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 11609 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 11610 * or binding. 11611 * 11612 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 11613 * degree, while also enabling code examples to work without relying on the 11614 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 11615 * 11616 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 11617 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 11618 * interpolation start/end markers with their escaped counterparts.** 11619 * 11620 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 11621 * output when the $interpolate service processes the text. So, for HTML elements interpolated 11622 * by {@link ng.$compile $compile}
11622, or otherwise interpolated with the `mustHaveExpression` parameter 11623 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 11624 * this is typically useful only when user-data is used in rendering a template from the server, or 11625 * when otherwise untrusted data is used by a directive. 11626 * 11627 * <example> 11628 * <file name="index.html"> 11629 * <div ng-init="username='A user'"> 11630 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 11631 * </p> 11632 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 11633 * application, but fails to accomplish their task, because the server has correctly 11634 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 11635 * characters.</p> 11636 * <p>Instead, the result of the attempted script injection is visible, and can be removed 11637 * from the database by an administrator.</p> 11638 * </div> 11639 * </file> 11640 * </example> 11641 * 11642 * @param {string} text The text with markup to interpolate. 11643 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 11644 * embedded expression in order to return an interpolation function. Strings with no 11645 * embedded expression will return null for the interpolation function. 11646 * @param {string=} trustedContext when provided, the returned function passes the interpolated 11647 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 11648 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 11649 * provides Strict Contextual Escaping for details. 11650 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 11651 * unless all embedded expressions evaluate to a value other than `undefined`. 11652 * @returns {function(context)} an interpolation function which is used to compute the 11653 * interpolated string. The function has these parameters: 11654 * 11655 * - `context`: evaluation context for all expressions embedded in the interpolated text 11656 */ 11657 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 11658 // Provide a quick exit and simplified result function for text with no interpolation 11659 if (!text.length || text.indexOf(startSymbol) === -1) { 11660 var constantInterp; 11661 if (!mustHaveExpression) { 11662 var unescapedText = unescapeText(text); 11663 constantInterp = valueFn(unescapedText); 11664 constantInterp.exp = text; 11665 constantInterp.expressions = []; 11666 constantInterp.$$watchDelegate = constantWatchDelegate; 11667 } 11668 return constantInterp; 11669 } 11670 11671 allOrNothing = !!allOrNothing; 11672 var startIndex, 11673 endIndex, 11674 index = 0, 11675 expressions = [], 11676 parseFns = [], 11677 textLength = text.length, 11678 exp, 11679 concat = [], 11680 expressionPositions = []; 11681 11682 while (index < textLength) { 11683 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 11684 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 11685 if (index !== startIndex) { 11686 concat.push(unescapeText(text.substring(index, startIndex))); 11687 } 11688 exp = text.substring(startIndex + startSymbolLength, endIndex); 11689 expressions.push(exp); 11690 parseFns.push($parse(exp, parseStringifyInterceptor)); 11691 index = endIndex + endSymbolLength; 11692 expressionPositions.push(concat.length); 11693 concat.push(''); 11694 } else { 11695 // we did not find an interpolation, so we have to add the remainder to the separators array 11696 if (index !== textLength) { 11697 concat.push(unescapeText(text.substring(index))); 11698 } 11699 break; 11700 } 11701 } 11702 11703 // Concatenating expressions makes it hard to reason about whether some combination of 11704 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 11705 // single expression be used for iframe[src], object[src], etc., we ensure that the value 11706 // that's used is assigned or constructed by some JS code somewhere that is more testable or 11707 // make it obvious that you bound the value to some user controlled value. This helps reduce 11708 // the load when auditing for XSS issues. 11709 if (trustedContext && concat.length > 1) { 11710 $interpolateMinErr.throwNoconcat(text); 11711 } 11712 11713 if (!mustHaveExpression || expressions.length) { 11714 var compute = function(values) { 11715 for (var i = 0, ii = expressions.length; i < ii; i++) { 11716 if (allOrNothing && isUndefined(values[i])) return; 11717 concat[expressionPositions[i]] = values[i]; 11718 } 11719 return concat.join(''); 11720 }; 11721 11722 var getValue = function(value) { 11723 return trustedContext ? 11724 $sce.getTrusted(trustedContext, value) : 11725 $sce.valueOf(value); 11726 }; 11727 11728 return extend(function interpolationFn(context) { 11729 var i = 0; 11730 var ii = expressions.length; 11731 var values = new Array(ii); 11732 11733 try { 11734 for (; i < ii; i++) { 11735 values[i] = parseFns[i](context); 11736 } 11737 11738 return compute(values); 11739 } catch (err) { 11740 $exceptionHandler($interpolateMinErr.interr(text, err));
vendor: 4,299 bytes, lines 11741-11851
11741 } 11742 11743 }, { 11744 // all of these properties are undocumented for now 11745 exp: text, //just for compatibility with regular watchers created via $watch 11746 expressions: expressions, 11747 $$watchDelegate: function(scope, listener) { 11748 var lastValue; 11749 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 11750 var currValue = compute(values); 11751 if (isFunction(listener)) { 11752 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 11753 } 11754 lastValue = currValue; 11755 }); 11756 } 11757 }); 11758 } 11759 11760 function parseStringifyInterceptor(value) { 11761 try { 11762 value = getValue(value); 11763 return allOrNothing && !isDefined(value) ? value : stringify(value); 11764 } catch (err) { 11765 $exceptionHandler($interpolateMinErr.interr(text, err)); 11766 } 11767 } 11768 } 11769 11770 11771 /** 11772 * @ngdoc method 11773 * @name $interpolate#startSymbol 11774 * @description 11775 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 11776 * 11777 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 11778 * the symbol. 11779 * 11780 * @returns {string} start symbol. 11781 */ 11782 $interpolate.startSymbol = function() { 11783 return startSymbol; 11784 }; 11785 11786 11787 /** 11788 * @ngdoc method 11789 * @name $interpolate#endSymbol 11790 * @description 11791 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11792 * 11793 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 11794 * the symbol. 11795 * 11796 * @returns {string} end symbol. 11797 */ 11798 $interpolate.endSymbol = function() { 11799 return endSymbol; 11800 }; 11801 11802 return $interpolate; 11803 }]; 11804} 11805 11806function $IntervalProvider() { 11807 this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', 11808 function($rootScope, $window, $q, $$q, $browser) { 11809 var intervals = {}; 11810 11811 11812 /** 11813 * @ngdoc service 11814 * @name $interval 11815 * 11816 * @description 11817 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 11818 * milliseconds. 11819 * 11820 * The return value of registering an interval function is a promise. This promise will be 11821 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 11822 * run indefinitely if `count` is not defined. The value of the notification will be the 11823 * number of iterations that have run. 11824 * To cancel an interval, call `$interval.cancel(promise)`. 11825 * 11826 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 11827 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 11828 * time. 11829 * 11830 * <div class="alert alert-warning"> 11831 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 11832 * with them. In particular they are not automatically destroyed when a controller's scope or a 11833 * directive's element are destroyed. 11834 * You should take this into consideration and make sure to always cancel the interval at the 11835 * appropriate moment. See the example below for more details on how and when to do this. 11836 * </div> 11837 * 11838 * @param {function()} fn A function that should be called repeatedly. 11839 * @param {number} delay Number of milliseconds between each function call. 11840 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 11841 * indefinitely. 11842 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 11843 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 11844 * @param {...*=} Pass additional parameters to the executed function. 11845 * @returns {promise} A promise which will be notified on each iteration. 11846 * 11847 * @example 11848 * <example module="intervalExample"> 11849 * <file name="index.html"> 11850 * <script> 11851 * angular.module('intervalExample', [])
11852 * .controller('ExampleController', ['$scope', '$interval', 11853 * function($scope, $interval) { 11854 * $scope.format = 'M/d/yy h:mm:ss a'; 11855 * $scope.blood_1 = 100; 11856 * $scope.blood_2 = 120; 11857 * 11858 * var stop; 11859 * $scope.fight = function() { 11860 * // Don't start a new fight if we are already fighting 11861 * if ( angular.isDefined(stop) ) return; 11862 * 11863 * stop = $interval(function() { 11864 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 11865 * $scope.blood_1 = $scope.blood_1 - 3; 11866 * $scope.blood_2 = $scope.blood_2 - 4; 11867 * } else { 11868 * $scope.stopFight(); 11869 * } 11870 * }, 100); 11871 * }; 11872 * 11873 * $scope.stopFight = function() { 11874 * if (angular.isDefined(stop)) { 11875 * $interval.cancel(stop); 11876 * stop = undefined; 11877 * } 11878 * }; 11879 * 11880 * $scope.resetFight = function() { 11881 * $scope.blood_1 = 100; 11882 * $scope.blood_2 = 120; 11883 * }; 11884 * 11885 * $scope.$on('$destroy', function() { 11886 * // Make sure that the interval is destroyed too 11887 * $scope.stopFight(); 11888 * }); 11889 * }]) 11890 * // Register the 'myCurrentTime' directive factory method. 11891 * // We inject $interval and dateFilter service since the factory method is DI. 11892 * .directive('myCurrentTime', ['$interval', 'dateFilter', 11893 * function($interval, dateFilter) { 11894 * // return the directive link function. (compile function not needed) 11895 * return function(scope, element, attrs) { 11896 * var format, // date format 11897 * stopTime; // so that we can cancel the time updates 11898 * 11899 * // used to update the UI 11900 * function updateTime() { 11901 * element.text(dateFilter(new Date(), format)); 11902 * } 11903 * 11904 * // watch the expression, and update the UI on change. 11905 * scope.$watch(attrs.myCurrentTime, function(value) { 11906 * format = value; 11907 * updateTime(); 11908 * }); 11909 * 11910 * stopTime = $interval(updateTime, 1000); 11911 * 11912 * // listen on DOM destroy (removal) event, and cancel the next UI update 11913 * // to prevent updating time after the DOM element was removed. 11914 * element.on('$destroy', function() { 11915 * $interval.cancel(stopTime); 11916 * }); 11917 * } 11918 * }]); 11919 * </script> 11920 * 11921 * <div>
11922 * <div ng-controller="ExampleController"> 11923 * <label>Date format: <input ng-model="format"></label> <hr/> 11924 * Current time is: <span my-current-time="format"></span> 11925 * <hr/> 11926 * Blood 1 : <font color='red'>{{blood_1}}</font> 11927 * Blood 2 : <font color='red'>{{blood_2}}</font> 11928 * <button type="button" data-ng-click="fight()">Fight</button> 11929 * <button type="button" data-ng-click="stopFight()">StopFight</button> 11930 * <button type="button" data-ng-click="resetFight()">resetFight</button> 11931 * </div> 11932 * </div> 11933 * 11934 * </file> 11935 * </example> 11936 */ 11937 function interval(fn, delay, count, invokeApply) { 11938 var hasParams = arguments.length > 4, 11939 args = hasParams ? sliceArgs(arguments, 4) : [], 11940 setInterval = $window.setInterval, 11941 clearInterval = $window.clearInterval, 11942 iteration = 0, 11943 skipApply = (isDefined(invokeApply) && !invokeApply), 11944 deferred = (skipApply ? $$q : $q).defer(), 11945 promise = deferred.promise; 11946 11947 count = isDefined(count) ? count : 0; 11948 11949 promise.$$intervalId = setInterval(function tick() { 11950 if (skipApply) { 11951 $browser.defer(callback); 11952 } else { 11953 $rootScope.$evalAsync(callback); 11954 } 11955 deferred.notify(iteration++); 11956 11957 if (count > 0 && iteration >= count) { 11958 deferred.resolve(iteration); 11959 clearInterval(promise.$$intervalId); 11960 delete intervals[promise.$$intervalId]; 11961 } 11962 11963 if (!skipApply) $rootScope.$apply(); 11964 11965 }, delay); 11966 11967 intervals[promise.$$intervalId] = deferred; 11968 11969 return promise; 11970 11971 function callback() { 11972 if (!hasParams) { 11973 fn(iteration); 11974 } else { 11975 fn.apply(null, args); 11976 } 11977 } 11978 } 11979 11980 11981 /** 11982 * @ngdoc method 11983 * @name $interval#cancel 11984 * 11985 * @description 11986 * Cancels a task associated with the `promise`. 11987 * 11988 * @param {Promise=} promise returned by the `$interval` function. 11989 * @returns {boolean} Returns `true` if the task was successfully canceled. 11990 */ 11991 interval.cancel = function(promise) { 11992 if (promise && promise.$$intervalId in intervals) { 11993 intervals[promise.$$intervalId].reject('canceled'); 11994 $window.clearInterval(promise.$$intervalId); 11995 delete intervals[promise.$$intervalId]; 11996 return true; 11997 } 11998 return false; 11999 }; 12000 12001 return interval; 12002 }]; 12003} 12004 12005/** 12006 * @ngdoc service 12007 * @name $locale 12008 * 12009 * @description 12010 * $locale service provides localization rules for various Angular components. As of right now the 12011 * only public api is: 12012 * 12013 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 12014 */ 12015 12016var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 12017 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 12018var $locationMinErr = minErr('$location'); 12019 12020 12021/** 12022 * Encode path using encodeUriSegment, ignoring forward slashes 12023 * 12024 * @param {string} path Path to encode 12025 * @returns {string} 12026 */ 12027function encodePath(path) { 12028 var segments = path.split('/'), 12029 i = segments.length; 12030 12031 while (i--) { 12032 segments[i] = encodeUriSegment(segments[i]); 12033 } 12034 12035 return segments.join('/'); 12036} 12037 12038function parseAbsoluteUrl(absoluteUrl, locationObj) { 12039 var parsedUrl = urlResolve(absoluteUrl); 12040 12041 locationObj.$$protocol = parsedUrl.protocol; 12042 locationObj.$$host = parsedUrl.hostname; 12043 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 12044} 12045 12046 12047function parseAppUrl(relativeUrl, locationObj) { 12048 var prefixed = (relativeUrl.charAt(0) !== '/'); 12049 if (prefixed) { 12050 relativeUrl = '/' + relativeUrl; 12051 } 12052 var match = urlResolve(relativeUrl); 12053 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 12054 match.pathname.substring(1) : match.pathname); 12055 locationObj.$$search = parseKeyValue(match.search); 12056 locationObj.$$hash = decodeURIComponent(match.hash); 12057 12058 // make sure path starts with '/'; 12059 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 12060 locationObj.$$path = '/' + locationObj.$$path; 12061 } 12062} 12063 12064 12065/** 12066 * 12067 * @param {string} begin 12068 * @param {string} whole 12069 * @returns {string} returns text from whole after begin or undefined if it does not begin with 12070 * expected string. 12071 */ 12072function beginsWith(begin, whole) { 12073 if (whole.indexOf(begin) === 0) { 12074 return whole.substr(begin.length); 12075 } 12076} 12077 12078 12079function stripHash(url) { 12080 var index = url.indexOf('#'); 12081 return index == -1 ? url : url.substr(0, index); 12082} 12083 12084function trimEmptyHash(url) { 12085 return url.replace(/(#.+)|#$/, '$1'); 12086} 12087 12088 12089function stripFile(url) { 12090 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 12091} 12092 12093/* return the server only (scheme://host:port) */ 12094function serverBase(url) { 12095 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 12096} 12097 12098 12099/** 12100 * LocationHtml5Url represents an url 12101 * This object is exposed as $location service when HTML5 mode is enabled and supported 12102 * 12103 * @constructor 12104 * @param {string} appBase application base URL 12105 * @param {string} appBaseNoFile application base URL stripped of any filename 12106 * @param {string} basePrefix url path prefix 12107 */ 12108function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 12109 this.$$html5 = true; 12110 basePrefix = basePrefix || ''; 12111 parseAbsoluteUrl(appBase, this); 12112 12113 12114 /** 12115 * Parse given html5 (regular) url string into properties 12116 * @param {string} url HTML5 url 12117 * @private 12118 */ 12119 this.$$parse = function(url) { 12120 var pathUrl = beginsWith(appBaseNoFile, url); 12121 if (!isString(pathUrl)) { 12122 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 12123 appBaseNoFile); 12124 } 12125 12126 parseAppUrl(pathUrl, this); 12127 12128 if (!this.$$path) { 12129 this.$$path = '/'; 12130 } 12131 12132 this.$$compose(); 12133 }; 12134 12135 /** 12136 * Compose url and update `absUrl` property 12137 * @private 12138 */ 12139 this.$$compose = function() {
vendor: 4,105 bytes, lines 12140-12262
12140 var search = toKeyValue(this.$$search), 12141 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 12142 12143 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 12144 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 12145 }; 12146 12147 this.$$parseLinkUrl = function(url, relHref) { 12148 if (relHref && relHref[0] === '#') { 12149 // special case for links to hash fragments: 12150 // keep the old url and only replace the hash fragment 12151 this.hash(relHref.slice(1)); 12152 return true; 12153 } 12154 var appUrl, prevAppUrl; 12155 var rewrittenUrl; 12156 12157 if (isDefined(appUrl = beginsWith(appBase, url))) { 12158 prevAppUrl = appUrl; 12159 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 12160 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 12161 } else { 12162 rewrittenUrl = appBase + prevAppUrl; 12163 } 12164 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 12165 rewrittenUrl = appBaseNoFile + appUrl; 12166 } else if (appBaseNoFile == url + '/') { 12167 rewrittenUrl = appBaseNoFile; 12168 } 12169 if (rewrittenUrl) { 12170 this.$$parse(rewrittenUrl); 12171 } 12172 return !!rewrittenUrl; 12173 }; 12174} 12175 12176 12177/** 12178 * LocationHashbangUrl represents url 12179 * This object is exposed as $location service when developer doesn't opt into html5 mode. 12180 * It also serves as the base class for html5 mode fallback on legacy browsers. 12181 * 12182 * @constructor 12183 * @param {string} appBase application base URL 12184 * @param {string} appBaseNoFile application base URL stripped of any filename 12185 * @param {string} hashPrefix hashbang prefix 12186 */ 12187function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 12188 12189 parseAbsoluteUrl(appBase, this); 12190 12191 12192 /** 12193 * Parse given hashbang url into properties 12194 * @param {string} url Hashbang url 12195 * @private 12196 */ 12197 this.$$parse = function(url) { 12198 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 12199 var withoutHashUrl; 12200 12201 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 12202 12203 // The rest of the url starts with a hash so we have 12204 // got either a hashbang path or a plain hash fragment 12205 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 12206 if (isUndefined(withoutHashUrl)) { 12207 // There was no hashbang prefix so we just have a hash fragment 12208 withoutHashUrl = withoutBaseUrl; 12209 } 12210 12211 } else { 12212 // There was no hashbang path nor hash fragment: 12213 // If we are in HTML5 mode we use what is left as the path; 12214 // Otherwise we ignore what is left 12215 if (this.$$html5) { 12216 withoutHashUrl = withoutBaseUrl; 12217 } else { 12218 withoutHashUrl = ''; 12219 if (isUndefined(withoutBaseUrl)) { 12220 appBase = url; 12221 this.replace(); 12222 } 12223 } 12224 } 12225 12226 parseAppUrl(withoutHashUrl, this); 12227 12228 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 12229 12230 this.$$compose(); 12231 12232 /* 12233 * In Windows, on an anchor node on documents loaded from 12234 * the filesystem, the browser will return a pathname 12235 * prefixed with the drive name ('/C:/path') when a 12236 * pathname without a drive is set: 12237 * * a.setAttribute('href', '/foo') 12238 * * a.pathname === '/C:/foo' //true 12239 * 12240 * Inside of Angular, we're always using pathnames that 12241 * do not include drive names for routing. 12242 */ 12243 function removeWindowsDriveName(path, url, base) { 12244 /* 12245 Matches paths for file protocol on windows, 12246 such as /C:/foo/bar, and captures only /foo/bar. 12247 */ 12248 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 12249 12250 var firstPathSegmentMatch; 12251 12252 //Get the relative path from the input URL. 12253 if (url.indexOf(base) === 0) { 12254 url = url.replace(base, ''); 12255 } 12256 12257 // The input URL intentionally contains a first path segment that ends with a colon. 12258 if (windowsFilePathExp.exec(url)) { 12259 return path; 12260 } 12261 12262 firstPathSegmentMatch = windowsFilePathExp.exec(path);
12263 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 12264 } 12265 }; 12266 12267 /** 12268 * Compose hashbang url and update `absUrl` property 12269 * @private 12270 */ 12271 this.$$compose = function() { 12272 var search = toKeyValue(this.$$search), 12273 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 12274 12275 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 12276 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 12277 }; 12278 12279 this.$$parseLinkUrl = function(url, relHref) { 12280 if (stripHash(appBase) == stripHash(url)) { 12281 this.$$parse(url); 12282 return true; 12283 } 12284 return false; 12285 }; 12286} 12287 12288 12289/** 12290 * LocationHashbangUrl represents url 12291 * This object is exposed as $location service when html5 history api is enabled but the browser 12292 * does not support it. 12293 * 12294 * @constructor 12295 * @param {string} appBase application base URL 12296 * @param {string} appBaseNoFile application base URL stripped of any filename 12297 * @param {string} hashPrefix hashbang prefix 12298 */ 12299function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 12300 this.$$html5 = true; 12301 LocationHashbangUrl.apply(this, arguments); 12302 12303 this.$$parseLinkUrl = function(url, relHref) { 12304 if (relHref && relHref[0] === '#') { 12305 // special case for links to hash fragments: 12306 // keep the old url and only replace the hash fragment 12307 this.hash(relHref.slice(1)); 12308 return true; 12309 } 12310 12311 var rewrittenUrl; 12312 var appUrl; 12313 12314 if (appBase == stripHash(url)) { 12315 rewrittenUrl = url; 12316 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 12317 rewrittenUrl = appBase + hashPrefix + appUrl; 12318 } else if (appBaseNoFile === url + '/') { 12319 rewrittenUrl = appBaseNoFile; 12320 } 12321 if (rewrittenUrl) { 12322 this.$$parse(rewrittenUrl); 12323 } 12324 return !!rewrittenUrl; 12325 }; 12326 12327 this.$$compose = function() { 12328 var search = toKeyValue(this.$$search), 12329 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 12330 12331 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 12332 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 12333 this.$$absUrl = appBase + hashPrefix + this.$$url; 12334 }; 12335 12336} 12337 12338 12339var locationPrototype = { 12340 12341 /** 12342 * Are we in html5 mode? 12343 * @private 12344 */ 12345 $$html5: false, 12346 12347 /** 12348 * Has any change been replacing? 12349 * @private 12350 */ 12351 $$replace: false, 12352 12353 /** 12354 * @ngdoc method 12355 * @name $location#absUrl 12356 * 12357 * @description 12358 * This method is getter only. 12359 * 12360 * Return full url representation with all segments encoded according to rules specified in 12361 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 12362 * 12363 * 12364 * ```js 12365 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12366 * var absUrl = $location.absUrl(); 12367 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 12368 * ``` 12369 * 12370 * @return {string} full url 12371 */ 12372 absUrl: locationGetter('$$absUrl'), 12373 12374 /** 12375 * @ngdoc method 12376 * @name $location#url 12377 * 12378 * @description 12379 * This method is getter / setter. 12380 * 12381 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 12382 * 12383 * Change path, search and hash, when called with parameter and return `$location`. 12384 * 12385 * 12386 * ```js 12387 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12388 * var url = $location.url(); 12389 * // => "/some/path?foo=bar&baz=xoxo" 12390 * ``` 12391 * 12392 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 12393 * @return {string} url 12394 */ 12395 url: function(url) { 12396 if (isUndefined(url)) { 12397 return this.$$url; 12398 } 12399 12400 var match = PATH_MATCH.exec(url); 12401 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 12402 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 12403 this.hash(match[5] || ''); 12404 12405 return this; 12406 }, 12407 12408 /** 12409 * @ngdoc method 12410 * @name $location#protocol 12411 * 12412 * @description 12413 * This method is getter only. 12414 * 12415 * Return protocol of current url. 12416 * 12417 * 12418 * ```js
12419 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12420 * var protocol = $location.protocol(); 12421 * // => "http" 12422 * ``` 12423 * 12424 * @return {string} protocol of current url 12425 */ 12426 protocol: locationGetter('$$protocol'), 12427 12428 /** 12429 * @ngdoc method 12430 * @name $location#host 12431 * 12432 * @description 12433 * This method is getter only. 12434 * 12435 * Return host of current url. 12436 * 12437 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 12438 * 12439 * 12440 * ```js 12441 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12442 * var host = $location.host(); 12443 * // => "example.com" 12444 * 12445 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 12446 * host = $location.host(); 12447 * // => "example.com" 12448 * host = location.host; 12449 * // => "example.com:8080" 12450 * ``` 12451 * 12452 * @return {string} host of current url. 12453 */ 12454 host: locationGetter('$$host'), 12455 12456 /** 12457 * @ngdoc method 12458 * @name $location#port 12459 * 12460 * @description 12461 * This method is getter only. 12462 * 12463 * Return port of current url. 12464 * 12465 * 12466 * ```js 12467 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12468 * var port = $location.port(); 12469 * // => 80 12470 * ``` 12471 * 12472 * @return {Number} port 12473 */ 12474 port: locationGetter('$$port'), 12475 12476 /** 12477 * @ngdoc method 12478 * @name $location#path 12479 * 12480 * @description 12481 * This method is getter / setter. 12482 * 12483 * Return path of current url when called without any parameter. 12484 * 12485 * Change path when called with parameter and return `$location`. 12486 * 12487 * Note: Path should always begin with forward slash (/), this method will add the forward slash 12488 * if it is missing. 12489 * 12490 * 12491 * ```js 12492 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12493 * var path = $location.path(); 12494 * // => "/some/path" 12495 * ``` 12496 * 12497 * @param {(string|number)=} path New path 12498 * @return {string} path 12499 */ 12500 path: locationGetterSetter('$$path', function(path) { 12501 path = path !== null ? path.toString() : ''; 12502 return path.charAt(0) == '/' ? path : '/' + path; 12503 }), 12504 12505 /** 12506 * @ngdoc method 12507 * @name $location#search 12508 * 12509 * @description 12510 * This method is getter / setter. 12511 * 12512 * Return search part (as object) of current url when called without any parameter. 12513 * 12514 * Change search part when called with parameter and return `$location`. 12515 * 12516 * 12517 * ```js 12518 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12519 * var searchObject = $location.search(); 12520 * // => {foo: 'bar', baz: 'xoxo'} 12521 * 12522 * // set foo to 'yipee' 12523 * $location.search('foo', 'yipee'); 12524 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 12525 * ``` 12526 * 12527 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 12528 * hash object. 12529 * 12530 * When called with a single argument the method acts as a setter, setting the `search` component 12531 * of `$location` to the specified value. 12532 * 12533 * If the argument is a hash object containing an array of values, these values will be encoded 12534 * as duplicate search parameters in the url. 12535 * 12536 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 12537 * will override only a single search property. 12538 * 12539 * If `paramValue` is an array, it will override the property of the `search` component of 12540 * `$location` specified via the first argument. 12541 * 12542 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 12543 * 12544 * If `paramValue` is `true`, the property specified via the first argument will be added with no 12545 * value nor trailing equal sign. 12546 * 12547 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 12548 * one or more arguments returns `$location` object itself. 12549 */ 12550 search: function(search, paramValue) { 12551 switch (arguments.length) { 12552 case 0: 12553 return this.$$search; 12554 case 1: 12555 if (isString(search) || isNumber(search)) { 12556 search = search.toString(); 12557 this.$$search = parseKeyValue(search); 12558 } else if (isObject(search)) { 12559 search = copy(search, {}); 12560 // remove object undefined or null properties
vendor: 9,382 bytes, lines 12561-12842
12561 forEach(search, function(value, key) { 12562 if (value == null) delete search[key]; 12563 }); 12564 12565 this.$$search = search; 12566 } else { 12567 throw $locationMinErr('isrcharg', 12568 'The first argument of the `$location#search()` call must be a string or an object.'); 12569 } 12570 break; 12571 default: 12572 if (isUndefined(paramValue) || paramValue === null) { 12573 delete this.$$search[search]; 12574 } else { 12575 this.$$search[search] = paramValue; 12576 } 12577 } 12578 12579 this.$$compose(); 12580 return this; 12581 }, 12582 12583 /** 12584 * @ngdoc method 12585 * @name $location#hash 12586 * 12587 * @description 12588 * This method is getter / setter. 12589 * 12590 * Returns the hash fragment when called without any parameters. 12591 * 12592 * Changes the hash fragment when called with a parameter and returns `$location`. 12593 * 12594 * 12595 * ```js 12596 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 12597 * var hash = $location.hash(); 12598 * // => "hashValue" 12599 * ``` 12600 * 12601 * @param {(string|number)=} hash New hash fragment 12602 * @return {string} hash 12603 */ 12604 hash: locationGetterSetter('$$hash', function(hash) { 12605 return hash !== null ? hash.toString() : ''; 12606 }), 12607 12608 /** 12609 * @ngdoc method 12610 * @name $location#replace 12611 * 12612 * @description 12613 * If called, all changes to $location during the current `$digest` will replace the current history 12614 * record, instead of adding a new one. 12615 */ 12616 replace: function() { 12617 this.$$replace = true; 12618 return this; 12619 } 12620}; 12621 12622forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 12623 Location.prototype = Object.create(locationPrototype); 12624 12625 /** 12626 * @ngdoc method 12627 * @name $location#state 12628 * 12629 * @description 12630 * This method is getter / setter. 12631 * 12632 * Return the history state object when called without any parameter. 12633 * 12634 * Change the history state object when called with one parameter and return `$location`. 12635 * The state object is later passed to `pushState` or `replaceState`. 12636 * 12637 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 12638 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 12639 * older browsers (like IE9 or Android < 4.0), don't use this method. 12640 * 12641 * @param {object=} state State object for pushState or replaceState 12642 * @return {object} state 12643 */ 12644 Location.prototype.state = function(state) { 12645 if (!arguments.length) { 12646 return this.$$state; 12647 } 12648 12649 if (Location !== LocationHtml5Url || !this.$$html5) { 12650 throw $locationMinErr('nostate', 'History API state support is available only ' + 12651 'in HTML5 mode and only in browsers supporting HTML5 History API'); 12652 } 12653 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 12654 // but we're changing the $$state reference to $browser.state() during the $digest 12655 // so the modification window is narrow. 12656 this.$$state = isUndefined(state) ? null : state; 12657 12658 return this; 12659 }; 12660}); 12661 12662 12663function locationGetter(property) { 12664 return function() { 12665 return this[property]; 12666 }; 12667} 12668 12669 12670function locationGetterSetter(property, preprocess) { 12671 return function(value) { 12672 if (isUndefined(value)) { 12673 return this[property]; 12674 } 12675 12676 this[property] = preprocess(value); 12677 this.$$compose(); 12678 12679 return this; 12680 }; 12681} 12682 12683 12684/** 12685 * @ngdoc service 12686 * @name $location 12687 * 12688 * @requires $rootElement 12689 * 12690 * @description 12691 * The $location service parses the URL in the browser address bar (based on the 12692 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 12693 * available to your application. Changes to the URL in the address bar are reflected into 12694 * $location service and changes to $location are reflected into the browser address bar. 12695 * 12696 * **The $location service:** 12697 * 12698 * - Exposes the current URL in the browser address bar, so you can 12699 * - Watch and observe the URL. 12700 * - Change the URL. 12701 * - Synchronizes the URL with the browser when the user 12702 * - Changes the address bar. 12703 * - Clicks the back or forward button (or clicks a History link). 12704 * - Clicks on a link. 12705 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 12706 * 12707 * For more information see {@link guide/$location Developer Guide: Using $location} 12708 */ 12709 12710/** 12711 * @ngdoc provider 12712 * @name $locationProvider 12713 * @description 12714 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 12715 */ 12716function $LocationProvider() { 12717 var hashPrefix = '', 12718 html5Mode = { 12719 enabled: false, 12720 requireBase: true, 12721 rewriteLinks: true 12722 }; 12723 12724 /** 12725 * @ngdoc method 12726 * @name $locationProvider#hashPrefix 12727 * @description 12728 * @param {string=} prefix Prefix for hash part (containing path and search) 12729 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12730 */ 12731 this.hashPrefix = function(prefix) { 12732 if (isDefined(prefix)) { 12733 hashPrefix = prefix; 12734 return this; 12735 } else { 12736 return hashPrefix; 12737 } 12738 }; 12739 12740 /** 12741 * @ngdoc method 12742 * @name $locationProvider#html5Mode 12743 * @description 12744 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 12745 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 12746 * properties: 12747 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 12748 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 12749 * support `pushState`. 12750 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 12751 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 12752 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 12753 * See the {@link guide/$location $location guide for more information} 12754 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 12755 * enables/disables url rewriting for relative links. 12756 * 12757 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 12758 */ 12759 this.html5Mode = function(mode) { 12760 if (isBoolean(mode)) { 12761 html5Mode.enabled = mode; 12762 return this; 12763 } else if (isObject(mode)) { 12764 12765 if (isBoolean(mode.enabled)) { 12766 html5Mode.enabled = mode.enabled; 12767 } 12768 12769 if (isBoolean(mode.requireBase)) { 12770 html5Mode.requireBase = mode.requireBase; 12771 } 12772 12773 if (isBoolean(mode.rewriteLinks)) { 12774 html5Mode.rewriteLinks = mode.rewriteLinks; 12775 } 12776 12777 return this; 12778 } else { 12779 return html5Mode; 12780 } 12781 }; 12782 12783 /** 12784 * @ngdoc event 12785 * @name $location#$locationChangeStart 12786 * @eventType broadcast on root scope 12787 * @description 12788 * Broadcasted before a URL will change. 12789 * 12790 * This change can be prevented by calling 12791 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 12792 * details about event object. Upon successful change 12793 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 12794 * 12795 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12796 * the browser supports the HTML5 History API. 12797 * 12798 * @param {Object} angularEvent Synthetic event object. 12799 * @param {string} newUrl New URL 12800 * @param {string=} oldUrl URL that was before it was changed. 12801 * @param {string=} newState New history state object 12802 * @param {string=} oldState History state object that was before it was changed. 12803 */ 12804 12805 /** 12806 * @ngdoc event 12807 * @name $location#$locationChangeSuccess 12808 * @eventType broadcast on root scope 12809 * @description 12810 * Broadcasted after a URL was changed. 12811 * 12812 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12813 * the browser supports the HTML5 History API. 12814 * 12815 * @param {Object} angularEvent Synthetic event object. 12816 * @param {string} newUrl New URL 12817 * @param {string=} oldUrl URL that was before it was changed. 12818 * @param {string=} newState New history state object 12819 * @param {string=} oldState History state object that was before it was changed. 12820 */ 12821 12822 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 12823 function($rootScope, $browser, $sniffer, $rootElement, $window) { 12824 var $location, 12825 LocationMode, 12826 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 12827 initialUrl = $browser.url(), 12828 appBase; 12829 12830 if (html5Mode.enabled) { 12831 if (!baseHref && html5Mode.requireBase) { 12832 throw $locationMinErr('nobase', 12833 "$location in HTML5 mode requires a <base> tag to be present!"); 12834 } 12835 appBase = serverBase(initialUrl) + (baseHref || '/'); 12836 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 12837 } else { 12838 appBase = stripHash(initialUrl); 12839 LocationMode = LocationHashbangUrl; 12840 } 12841 var appBaseNoFile = stripFile(appBase); 12842
12843 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 12844 $location.$$parseLinkUrl(initialUrl, initialUrl); 12845 12846 $location.$$state = $browser.state(); 12847 12848 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 12849 12850 function setBrowserUrlWithFallback(url, replace, state) { 12851 var oldUrl = $location.url(); 12852 var oldState = $location.$$state; 12853 try { 12854 $browser.url(url, replace, state); 12855 12856 // Make sure $location.state() returns referentially identical (not just deeply equal) 12857 // state object; this makes possible quick checking if the state changed in the digest 12858 // loop. Checking deep equality would be too expensive. 12859 $location.$$state = $browser.state(); 12860 } catch (e) { 12861 // Restore old values if pushState fails 12862 $location.url(oldUrl); 12863 $location.$$state = oldState; 12864 12865 throw e; 12866 } 12867 } 12868 12869 $rootElement.on('click', function(event) { 12870 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 12871 // currently we open nice url link and redirect then 12872 12873 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 12874 12875 var elm = jqLite(event.target); 12876 12877 // traverse the DOM up to find first A tag 12878 while (nodeName_(elm[0]) !== 'a') { 12879 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 12880 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 12881 } 12882 12883 var absHref = elm.prop('href'); 12884 // get the actual href attribute - see 12885 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 12886 var relHref = elm.attr('href') || elm.attr('xlink:href'); 12887 12888 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 12889 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 12890 // an animation. 12891 absHref = urlResolve(absHref.animVal).href; 12892 } 12893 12894 // Ignore when url is started with javascript: or mailto: 12895 if (IGNORE_URI_REGEXP.test(absHref)) return; 12896 12897 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 12898 if ($location.$$parseLinkUrl(absHref, relHref)) { 12899 // We do a preventDefault for all urls that are part of the angular application, 12900 // in html5mode and also without, so that we are able to abort navigation without 12901 // getting double entries in the location history. 12902 event.preventDefault(); 12903 // update location manually 12904 if ($location.absUrl() != $browser.url()) { 12905 $rootScope.$apply(); 12906 // hack to work around FF6 bug 684208 when scenario runner clicks on links 12907 $window.angular['ff-684208-preventDefault'] = true; 12908 } 12909 } 12910 } 12911 }); 12912 12913 12914 // rewrite hashbang url <> html5 url 12915 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 12916 $browser.url($location.absUrl(), true); 12917 } 12918 12919 var initializing = true; 12920 12921 // update $location when $browser url changes 12922 $browser.onUrlChange(function(newUrl, newState) { 12923 12924 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 12925 // If we are navigating outside of the app then force a reload 12926 $window.location.href = newUrl; 12927 return; 12928 } 12929 12930 $rootScope.$evalAsync(function() { 12931 var oldUrl = $location.absUrl(); 12932 var oldState = $location.$$state; 12933 var defaultPrevented; 12934 newUrl = trimEmptyHash(newUrl); 12935 $location.$$parse(newUrl); 12936 $location.$$state = newState; 12937 12938 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12939 newState, oldState).defaultPrevented; 12940 12941 // if the location was changed by a `$locationChangeStart` handler then stop 12942 // processing this location change 12943 if ($location.absUrl() !== newUrl) return; 12944 12945 if (defaultPrevented) { 12946 $location.$$parse(oldUrl); 12947 $location.$$state = oldState; 12948 setBrowserUrlWithFallback(oldUrl, false, oldState); 12949 } else { 12950 initializing = false; 12951 afterLocationChange(oldUrl, oldState); 12952 } 12953 }); 12954 if (!$rootScope.$$phase) $rootScope.$digest(); 12955 });
vendor: 4,218 bytes, lines 12956-13093
12956 12957 // update browser 12958 $rootScope.$watch(function $locationWatch() { 12959 var oldUrl = trimEmptyHash($browser.url()); 12960 var newUrl = trimEmptyHash($location.absUrl()); 12961 var oldState = $browser.state(); 12962 var currentReplace = $location.$$replace; 12963 var urlOrStateChanged = oldUrl !== newUrl || 12964 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 12965 12966 if (initializing || urlOrStateChanged) { 12967 initializing = false; 12968 12969 $rootScope.$evalAsync(function() { 12970 var newUrl = $location.absUrl(); 12971 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12972 $location.$$state, oldState).defaultPrevented; 12973 12974 // if the location was changed by a `$locationChangeStart` handler then stop 12975 // processing this location change 12976 if ($location.absUrl() !== newUrl) return; 12977 12978 if (defaultPrevented) { 12979 $location.$$parse(oldUrl); 12980 $location.$$state = oldState; 12981 } else { 12982 if (urlOrStateChanged) { 12983 setBrowserUrlWithFallback(newUrl, currentReplace, 12984 oldState === $location.$$state ? null : $location.$$state); 12985 } 12986 afterLocationChange(oldUrl, oldState); 12987 } 12988 }); 12989 } 12990 12991 $location.$$replace = false; 12992 12993 // we don't need to return anything because $evalAsync will make the digest loop dirty when 12994 // there is a change 12995 }); 12996 12997 return $location; 12998 12999 function afterLocationChange(oldUrl, oldState) { 13000 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 13001 $location.$$state, oldState); 13002 } 13003}]; 13004} 13005 13006/** 13007 * @ngdoc service 13008 * @name $log 13009 * @requires $window 13010 * 13011 * @description 13012 * Simple service for logging. Default implementation safely writes the message 13013 * into the browser's console (if present). 13014 * 13015 * The main purpose of this service is to simplify debugging and troubleshooting. 13016 * 13017 * The default is to log `debug` messages. You can use 13018 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 13019 * 13020 * @example 13021 <example module="logExample"> 13022 <file name="script.js"> 13023 angular.module('logExample', []) 13024 .controller('LogController', ['$scope', '$log', function($scope, $log) { 13025 $scope.$log = $log; 13026 $scope.message = 'Hello World!'; 13027 }]); 13028 </file> 13029 <file name="index.html"> 13030 <div ng-controller="LogController"> 13031 <p>Reload this page with open console, enter text and hit the log button...</p> 13032 <label>Message: 13033 <input type="text" ng-model="message" /></label> 13034 <button ng-click="$log.log(message)">log</button> 13035 <button ng-click="$log.warn(message)">warn</button> 13036 <button ng-click="$log.info(message)">info</button> 13037 <button ng-click="$log.error(message)">error</button> 13038 <button ng-click="$log.debug(message)">debug</button> 13039 </div> 13040 </file> 13041 </example> 13042 */ 13043 13044/** 13045 * @ngdoc provider 13046 * @name $logProvider 13047 * @description 13048 * Use the `$logProvider` to configure how the application logs messages 13049 */ 13050function $LogProvider() { 13051 var debug = true, 13052 self = this; 13053 13054 /** 13055 * @ngdoc method 13056 * @name $logProvider#debugEnabled 13057 * @description 13058 * @param {boolean=} flag enable or disable debug level messages 13059 * @returns {*} current value if used as getter or itself (chaining) if used as setter 13060 */ 13061 this.debugEnabled = function(flag) { 13062 if (isDefined(flag)) { 13063 debug = flag; 13064 return this; 13065 } else { 13066 return debug; 13067 } 13068 }; 13069 13070 this.$get = ['$window', function($window) { 13071 return { 13072 /** 13073 * @ngdoc method 13074 * @name $log#log 13075 * 13076 * @description 13077 * Write a log message 13078 */ 13079 log: consoleLog('log'), 13080 13081 /** 13082 * @ngdoc method 13083 * @name $log#info 13084 * 13085 * @description 13086 * Write an information message 13087 */ 13088 info: consoleLog('info'), 13089 13090 /** 13091 * @ngdoc method 13092 * @name $log#warn 13093 *
13094 * @description 13095 * Write a warning message 13096 */ 13097 warn: consoleLog('warn'), 13098 13099 /** 13100 * @ngdoc method 13101 * @name $log#error 13102 * 13103 * @description 13104 * Write an error message 13105 */ 13106 error: consoleLog('error'), 13107 13108 /** 13109 * @ngdoc method 13110 * @name $log#debug 13111 * 13112 * @description 13113 * Write a debug message 13114 */ 13115 debug: (function() { 13116 var fn = consoleLog('debug'); 13117 13118 return function() { 13119 if (debug) { 13120 fn.apply(self, arguments); 13121 } 13122 }; 13123 }()) 13124 }; 13125 13126 function formatError(arg) { 13127 if (arg instanceof Error) { 13128 if (arg.stack) { 13129 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 13130 ? 'Error: ' + arg.message + '\n' + arg.stack 13131 : arg.stack; 13132 } else if (arg.sourceURL) { 13133 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 13134 } 13135 } 13136 return arg; 13137 } 13138 13139 function consoleLog(type) { 13140 var console = $window.console || {}, 13141 logFn = console[type] || console.log || noop, 13142 hasApply = false; 13143 13144 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 13145 // The reason behind this is that console.log has type "object" in IE8... 13146 try { 13147 hasApply = !!logFn.apply; 13148 } catch (e) {} 13149 13150 if (hasApply) { 13151 return function() { 13152 var args = []; 13153 forEach(arguments, function(arg) { 13154 args.push(formatError(arg)); 13155 }); 13156 return logFn.apply(console, args); 13157 }; 13158 } 13159 13160 // we are IE which either doesn't have window.console => this is noop and we do nothing, 13161 // or we are IE where console.log doesn't have apply so we log at least first 2 args 13162 return function(arg1, arg2) { 13163 logFn(arg1, arg2 == null ? '' : arg2); 13164 }; 13165 } 13166 }]; 13167} 13168 13169/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 13170 * Any commits to this file should be reviewed with security in mind. * 13171 * Changes to this file can potentially create security vulnerabilities. * 13172 * An approval from 2 Core members with history of modifying * 13173 * this file is required. * 13174 * * 13175 * Does the change somehow allow for arbitrary javascript to be executed? * 13176 * Or allows for someone to change the prototype of built-in objects? * 13177 * Or gives undesired access to variables likes document or window? * 13178 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 13179 13180var $parseMinErr = minErr('$parse'); 13181 13182// Sandboxing Angular Expressions 13183// ------------------------------ 13184// Angular expressions are generally considered safe because these expressions only have direct 13185// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 13186// obtaining a reference to native JS functions such as the Function constructor. 13187// 13188// As an example, consider the following Angular expression: 13189// 13190// {}.toString.constructor('alert("evil JS code")') 13191// 13192// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 13193// against the expression language, but not to prevent exploits that were enabled by exposing 13194// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 13195// practice and therefore we are not even trying to protect against interaction with an object 13196// explicitly exposed in this way. 13197// 13198// In general, it is not possible to access a Window object from an angular expression unless a 13199// window or some DOM object that has a reference to window is published onto a Scope. 13200// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 13201// native objects. 13202// 13203// See https://docs.angularjs.org/guide/security 13204 13205 13206function ensureSafeMemberName(name, fullExpression) { 13207 if (name === "__defineGetter__" || name === "__defineSetter__" 13208 || name === "__lookupGetter__" || name === "__lookupSetter__" 13209 || name === "__proto__") { 13210 throw $parseMinErr('isecfld', 13211 'Attempting to access a disallowed field in Angular expressions! ' 13212 + 'Expression: {0}', fullExpression); 13213 } 13214 return name; 13215} 13216 13217function getStringValue(name) { 13218 // Property names must be strings. This means that non-string objects cannot be used 13219 // as keys in an object. Any non-string object, including a number, is typecasted 13220 // into a string via the toString method. 13221 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 13222 // 13223 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 13224 // to a string. It's not always possible. If `name` is an object and its `toString` method is 13225 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 13226 // 13227 // TypeError: Cannot convert object to primitive value 13228 // 13229 // For performance reasons, we don't catch this error here and allow it to propagate up the call 13230 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 13231 // such a 'broken' object as a key. 13232 return name + ''; 13233} 13234 13235function ensureSafeObject(obj, fullExpression) { 13236 // nifty check if obj is Function that is fast and works across iframes and other contexts 13237 if (obj) { 13238 if (obj.constructor === obj) { 13239 throw $parseMinErr('isecfn', 13240 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 13241 fullExpression); 13242 } else if (// isWindow(obj) 13243 obj.window === obj) { 13244 throw $parseMinErr('isecwindow', 13245 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 13246 fullExpression); 13247 } else if (// isElement(obj) 13248 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 13249 throw $parseMinErr('isecdom', 13250 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 13251 fullExpression); 13252 } else if (// block Object so that we can't get hold of dangerous Object.* methods 13253 obj === Object) { 13254 throw $parseMinErr('isecobj', 13255 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 13256 fullExpression); 13257 } 13258 } 13259 return obj;
13260} 13261 13262var CALL = Function.prototype.call; 13263var APPLY = Function.prototype.apply; 13264var BIND = Function.prototype.bind; 13265 13266function ensureSafeFunction(obj, fullExpression) { 13267 if (obj) { 13268 if (obj.constructor === obj) { 13269 throw $parseMinErr('isecfn', 13270 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 13271 fullExpression); 13272 } else if (obj === CALL || obj === APPLY || obj === BIND) { 13273 throw $parseMinErr('isecff', 13274 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 13275 fullExpression); 13276 } 13277 } 13278} 13279 13280function ensureSafeAssignContext(obj, fullExpression) { 13281 if (obj) { 13282 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 13283 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 13284 throw $parseMinErr('isecaf', 13285 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 13286 } 13287 } 13288} 13289 13290var OPERATORS = createMap(); 13291forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 13292var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 13293 13294 13295///////////////////////////////////////// 13296 13297 13298/** 13299 * @constructor 13300 */ 13301var Lexer = function(options) { 13302 this.options = options; 13303}; 13304 13305Lexer.prototype = { 13306 constructor: Lexer, 13307 13308 lex: function(text) { 13309 this.text = text; 13310 this.index = 0; 13311 this.tokens = []; 13312 13313 while (this.index < this.text.length) { 13314 var ch = this.text.charAt(this.index); 13315 if (ch === '"' || ch === "'") { 13316 this.readString(ch); 13317 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 13318 this.readNumber(); 13319 } else if (this.isIdent(ch)) { 13320 this.readIdent(); 13321 } else if (this.is(ch, '(){}[].,;:?')) { 13322 this.tokens.push({index: this.index, text: ch}); 13323 this.index++; 13324 } else if (this.isWhitespace(ch)) { 13325 this.index++; 13326 } else { 13327 var ch2 = ch + this.peek(); 13328 var ch3 = ch2 + this.peek(2); 13329 var op1 = OPERATORS[ch];
vendor: 47,995 bytes, lines 13330-14788
13330 var op2 = OPERATORS[ch2]; 13331 var op3 = OPERATORS[ch3]; 13332 if (op1 || op2 || op3) { 13333 var token = op3 ? ch3 : (op2 ? ch2 : ch); 13334 this.tokens.push({index: this.index, text: token, operator: true}); 13335 this.index += token.length; 13336 } else { 13337 this.throwError('Unexpected next character ', this.index, this.index + 1); 13338 } 13339 } 13340 } 13341 return this.tokens; 13342 }, 13343 13344 is: function(ch, chars) { 13345 return chars.indexOf(ch) !== -1; 13346 }, 13347 13348 peek: function(i) { 13349 var num = i || 1; 13350 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 13351 }, 13352 13353 isNumber: function(ch) { 13354 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 13355 }, 13356 13357 isWhitespace: function(ch) { 13358 // IE treats non-breaking space as \u00A0 13359 return (ch === ' ' || ch === '\r' || ch === '\t' || 13360 ch === '\n' || ch === '\v' || ch === '\u00A0'); 13361 }, 13362 13363 isIdent: function(ch) { 13364 return ('a' <= ch && ch <= 'z' || 13365 'A' <= ch && ch <= 'Z' || 13366 '_' === ch || ch === '$'); 13367 }, 13368 13369 isExpOperator: function(ch) { 13370 return (ch === '-' || ch === '+' || this.isNumber(ch)); 13371 }, 13372 13373 throwError: function(error, start, end) { 13374 end = end || this.index; 13375 var colStr = (isDefined(start) 13376 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 13377 : ' ' + end); 13378 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 13379 error, colStr, this.text); 13380 }, 13381 13382 readNumber: function() { 13383 var number = ''; 13384 var start = this.index; 13385 while (this.index < this.text.length) { 13386 var ch = lowercase(this.text.charAt(this.index)); 13387 if (ch == '.' || this.isNumber(ch)) { 13388 number += ch; 13389 } else { 13390 var peekCh = this.peek(); 13391 if (ch == 'e' && this.isExpOperator(peekCh)) { 13392 number += ch; 13393 } else if (this.isExpOperator(ch) && 13394 peekCh && this.isNumber(peekCh) && 13395 number.charAt(number.length - 1) == 'e') { 13396 number += ch; 13397 } else if (this.isExpOperator(ch) && 13398 (!peekCh || !this.isNumber(peekCh)) && 13399 number.charAt(number.length - 1) == 'e') { 13400 this.throwError('Invalid exponent'); 13401 } else { 13402 break; 13403 } 13404 } 13405 this.index++; 13406 } 13407 this.tokens.push({ 13408 index: start, 13409 text: number, 13410 constant: true, 13411 value: Number(number) 13412 }); 13413 }, 13414 13415 readIdent: function() { 13416 var start = this.index; 13417 while (this.index < this.text.length) { 13418 var ch = this.text.charAt(this.index); 13419 if (!(this.isIdent(ch) || this.isNumber(ch))) { 13420 break; 13421 } 13422 this.index++; 13423 } 13424 this.tokens.push({ 13425 index: start, 13426 text: this.text.slice(start, this.index), 13427 identifier: true 13428 }); 13429 }, 13430 13431 readString: function(quote) { 13432 var start = this.index; 13433 this.index++; 13434 var string = ''; 13435 var rawString = quote; 13436 var escape = false; 13437 while (this.index < this.text.length) { 13438 var ch = this.text.charAt(this.index); 13439 rawString += ch; 13440 if (escape) { 13441 if (ch === 'u') { 13442 var hex = this.text.substring(this.index + 1, this.index + 5); 13443 if (!hex.match(/[\da-f]{4}/i)) { 13444 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 13445 } 13446 this.index += 4; 13447 string += String.fromCharCode(parseInt(hex, 16)); 13448 } else { 13449 var rep = ESCAPE[ch]; 13450 string = string + (rep || ch); 13451 } 13452 escape = false; 13453 } else if (ch === '\\') { 13454 escape = true; 13455 } else if (ch === quote) { 13456 this.index++; 13457 this.tokens.push({ 13458 index: start, 13459 text: rawString, 13460 constant: true, 13461 value: string 13462 }); 13463 return; 13464 } else { 13465 string += ch; 13466 } 13467 this.index++; 13468 } 13469 this.throwError('Unterminated quote', start); 13470 } 13471}; 13472 13473var AST = function(lexer, options) { 13474 this.lexer = lexer; 13475 this.options = options; 13476}; 13477 13478AST.Program = 'Program'; 13479AST.ExpressionStatement = 'ExpressionStatement'; 13480AST.AssignmentExpression = 'AssignmentExpression'; 13481AST.ConditionalExpression = 'ConditionalExpression'; 13482AST.LogicalExpression = 'LogicalExpression'; 13483AST.BinaryExpression = 'BinaryExpression'; 13484AST.UnaryExpression = 'UnaryExpression'; 13485AST.CallExpression = 'CallExpression'; 13486AST.MemberExpression = 'MemberExpression'; 13487AST.Identifier = 'Identifier'; 13488AST.Literal = 'Literal'; 13489AST.ArrayExpression = 'ArrayExpression'; 13490AST.Property = 'Property'; 13491AST.ObjectExpression = 'ObjectExpression'; 13492AST.ThisExpression = 'ThisExpression'; 13493AST.LocalsExpression = 'LocalsExpression'; 13494 13495// Internal use only 13496AST.NGValueParameter = 'NGValueParameter'; 13497 13498AST.prototype = { 13499 ast: function(text) { 13500 this.text = text; 13501 this.tokens = this.lexer.lex(text); 13502 13503 var value = this.program(); 13504 13505 if (this.tokens.length !== 0) { 13506 this.throwError('is an unexpected token', this.tokens[0]); 13507 } 13508 13509 return value; 13510 }, 13511 13512 program: function() { 13513 var body = []; 13514 while (true) { 13515 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 13516 body.push(this.expressionStatement()); 13517 if (!this.expect(';')) { 13518 return { type: AST.Program, body: body}; 13519 } 13520 } 13521 }, 13522 13523 expressionStatement: function() { 13524 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 13525 }, 13526 13527 filterChain: function() { 13528 var left = this.expression(); 13529 var token; 13530 while ((token = this.expect('|'))) { 13531 left = this.filter(left); 13532 } 13533 return left; 13534 }, 13535 13536 expression: function() { 13537 return this.assignment(); 13538 }, 13539 13540 assignment: function() { 13541 var result = this.ternary(); 13542 if (this.expect('=')) { 13543 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 13544 } 13545 return result; 13546 }, 13547 13548 ternary: function() { 13549 var test = this.logicalOR(); 13550 var alternate; 13551 var consequent; 13552 if (this.expect('?')) { 13553 alternate = this.expression(); 13554 if (this.consume(':')) { 13555 consequent = this.expression(); 13556 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 13557 } 13558 } 13559 return test; 13560 }, 13561 13562 logicalOR: function() { 13563 var left = this.logicalAND(); 13564 while (this.expect('||')) { 13565 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 13566 } 13567 return left; 13568 }, 13569 13570 logicalAND: function() { 13571 var left = this.equality(); 13572 while (this.expect('&&')) { 13573 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 13574 } 13575 return left; 13576 }, 13577 13578 equality: function() { 13579 var left = this.relational(); 13580 var token; 13581 while ((token = this.expect('==','!=','===','!=='))) { 13582 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 13583 } 13584 return left; 13585 }, 13586 13587 relational: function() { 13588 var left = this.additive(); 13589 var token; 13590 while ((token = this.expect('<', '>', '<=', '>='))) { 13591 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 13592 } 13593 return left; 13594 }, 13595 13596 additive: function() { 13597 var left = this.multiplicative(); 13598 var token; 13599 while ((token = this.expect('+','-'))) { 13600 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 13601 } 13602 return left; 13603 }, 13604 13605 multiplicative: function() { 13606 var left = this.unary(); 13607 var token; 13608 while ((token = this.expect('*','/','%'))) { 13609 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 13610 } 13611 return left; 13612 }, 13613 13614 unary: function() { 13615 var token; 13616 if ((token = this.expect('+', '-', '!'))) { 13617 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 13618 } else { 13619 return this.primary(); 13620 } 13621 }, 13622 13623 primary: function() { 13624 var primary; 13625 if (this.expect('(')) { 13626 primary = this.filterChain(); 13627 this.consume(')'); 13628 } else if (this.expect('[')) { 13629 primary = this.arrayDeclaration(); 13630 } else if (this.expect('{')) { 13631 primary = this.object(); 13632 } else if (this.constants.hasOwnProperty(this.peek().text)) { 13633 primary = copy(this.constants[this.consume().text]); 13634 } else if (this.peek().identifier) { 13635 primary = this.identifier(); 13636 } else if (this.peek().constant) { 13637 primary = this.constant(); 13638 } else { 13639 this.throwError('not a primary expression', this.peek()); 13640 } 13641 13642 var next; 13643 while ((next = this.expect('(', '[', '.'))) { 13644 if (next.text === '(') { 13645 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 13646 this.consume(')'); 13647 } else if (next.text === '[') { 13648 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 13649 this.consume(']'); 13650 } else if (next.text === '.') { 13651 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 13652 } else { 13653 this.throwError('IMPOSSIBLE'); 13654 } 13655 } 13656 return primary; 13657 }, 13658 13659 filter: function(baseExpression) { 13660 var args = [baseExpression]; 13661 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 13662 13663 while (this.expect(':')) { 13664 args.push(this.expression()); 13665 } 13666 13667 return result; 13668 }, 13669 13670 parseArguments: function() { 13671 var args = []; 13672 if (this.peekToken().text !== ')') { 13673 do { 13674 args.push(this.expression()); 13675 } while (this.expect(',')); 13676 } 13677 return args; 13678 }, 13679 13680 identifier: function() { 13681 var token = this.consume(); 13682 if (!token.identifier) { 13683 this.throwError('is not a valid identifier', token); 13684 } 13685 return { type: AST.Identifier, name: token.text }; 13686 }, 13687 13688 constant: function() { 13689 // TODO check that it is a constant 13690 return { type: AST.Literal, value: this.consume().value }; 13691 }, 13692 13693 arrayDeclaration: function() { 13694 var elements = []; 13695 if (this.peekToken().text !== ']') { 13696 do { 13697 if (this.peek(']')) { 13698 // Support trailing commas per ES5.1. 13699 break; 13700 } 13701 elements.push(this.expression()); 13702 } while (this.expect(',')); 13703 } 13704 this.consume(']'); 13705 13706 return { type: AST.ArrayExpression, elements: elements }; 13707 }, 13708 13709 object: function() { 13710 var properties = [], property; 13711 if (this.peekToken().text !== '}') { 13712 do { 13713 if (this.peek('}')) { 13714 // Support trailing commas per ES5.1. 13715 break; 13716 } 13717 property = {type: AST.Property, kind: 'init'}; 13718 if (this.peek().constant) { 13719 property.key = this.constant(); 13720 } else if (this.peek().identifier) { 13721 property.key = this.identifier(); 13722 } else { 13723 this.throwError("invalid key", this.peek()); 13724 } 13725 this.consume(':'); 13726 property.value = this.expression(); 13727 properties.push(property); 13728 } while (this.expect(',')); 13729 } 13730 this.consume('}'); 13731 13732 return {type: AST.ObjectExpression, properties: properties }; 13733 }, 13734 13735 throwError: function(msg, token) { 13736 throw $parseMinErr('syntax', 13737 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 13738 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 13739 }, 13740 13741 consume: function(e1) { 13742 if (this.tokens.length === 0) { 13743 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13744 } 13745 13746 var token = this.expect(e1); 13747 if (!token) { 13748 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 13749 } 13750 return token; 13751 }, 13752 13753 peekToken: function() { 13754 if (this.tokens.length === 0) { 13755 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13756 } 13757 return this.tokens[0]; 13758 }, 13759 13760 peek: function(e1, e2, e3, e4) { 13761 return this.peekAhead(0, e1, e2, e3, e4); 13762 }, 13763 13764 peekAhead: function(i, e1, e2, e3, e4) { 13765 if (this.tokens.length > i) { 13766 var token = this.tokens[i]; 13767 var t = token.text; 13768 if (t === e1 || t === e2 || t === e3 || t === e4 || 13769 (!e1 && !e2 && !e3 && !e4)) { 13770 return token; 13771 } 13772 } 13773 return false; 13774 }, 13775 13776 expect: function(e1, e2, e3, e4) { 13777 var token = this.peek(e1, e2, e3, e4); 13778 if (token) { 13779 this.tokens.shift(); 13780 return token; 13781 } 13782 return false; 13783 }, 13784 13785 13786 /* `undefined` is not a constant, it is an identifier, 13787 * but using it as an identifier is not supported 13788 */ 13789 constants: { 13790 'true': { type: AST.Literal, value: true }, 13791 'false': { type: AST.Literal, value: false }, 13792 'null': { type: AST.Literal, value: null }, 13793 'undefined': {type: AST.Literal, value: undefined }, 13794 'this': {type: AST.ThisExpression }, 13795 '$locals': {type: AST.LocalsExpression } 13796 } 13797}; 13798 13799function ifDefined(v, d) { 13800 return typeof v !== 'undefined' ? v : d; 13801} 13802 13803function plusFn(l, r) { 13804 if (typeof l === 'undefined') return r; 13805 if (typeof r === 'undefined') return l; 13806 return l + r; 13807} 13808 13809function isStateless($filter, filterName) { 13810 var fn = $filter(filterName); 13811 return !fn.$stateful; 13812} 13813 13814function findConstantAndWatchExpressions(ast, $filter) { 13815 var allConstants; 13816 var argsToWatch; 13817 switch (ast.type) { 13818 case AST.Program: 13819 allConstants = true; 13820 forEach(ast.body, function(expr) { 13821 findConstantAndWatchExpressions(expr.expression, $filter); 13822 allConstants = allConstants && expr.expression.constant; 13823 }); 13824 ast.constant = allConstants; 13825 break; 13826 case AST.Literal: 13827 ast.constant = true; 13828 ast.toWatch = []; 13829 break; 13830 case AST.UnaryExpression: 13831 findConstantAndWatchExpressions(ast.argument, $filter); 13832 ast.constant = ast.argument.constant; 13833 ast.toWatch = ast.argument.toWatch; 13834 break; 13835 case AST.BinaryExpression: 13836 findConstantAndWatchExpressions(ast.left, $filter); 13837 findConstantAndWatchExpressions(ast.right, $filter); 13838 ast.constant = ast.left.constant && ast.right.constant; 13839 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 13840 break; 13841 case AST.LogicalExpression: 13842 findConstantAndWatchExpressions(ast.left, $filter); 13843 findConstantAndWatchExpressions(ast.right, $filter); 13844 ast.constant = ast.left.constant && ast.right.constant; 13845 ast.toWatch = ast.constant ? [] : [ast]; 13846 break; 13847 case AST.ConditionalExpression: 13848 findConstantAndWatchExpressions(ast.test, $filter); 13849 findConstantAndWatchExpressions(ast.alternate, $filter); 13850 findConstantAndWatchExpressions(ast.consequent, $filter); 13851 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 13852 ast.toWatch = ast.constant ? [] : [ast]; 13853 break; 13854 case AST.Identifier: 13855 ast.constant = false; 13856 ast.toWatch = [ast]; 13857 break; 13858 case AST.MemberExpression: 13859 findConstantAndWatchExpressions(ast.object, $filter); 13860 if (ast.computed) { 13861 findConstantAndWatchExpressions(ast.property, $filter); 13862 } 13863 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 13864 ast.toWatch = [ast]; 13865 break; 13866 case AST.CallExpression: 13867 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 13868 argsToWatch = []; 13869 forEach(ast.arguments, function(expr) { 13870 findConstantAndWatchExpressions(expr, $filter); 13871 allConstants = allConstants && expr.constant; 13872 if (!expr.constant) { 13873 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13874 } 13875 }); 13876 ast.constant = allConstants; 13877 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 13878 break; 13879 case AST.AssignmentExpression: 13880 findConstantAndWatchExpressions(ast.left, $filter); 13881 findConstantAndWatchExpressions(ast.right, $filter); 13882 ast.constant = ast.left.constant && ast.right.constant; 13883 ast.toWatch = [ast]; 13884 break; 13885 case AST.ArrayExpression: 13886 allConstants = true; 13887 argsToWatch = []; 13888 forEach(ast.elements, function(expr) { 13889 findConstantAndWatchExpressions(expr, $filter); 13890 allConstants = allConstants && expr.constant; 13891 if (!expr.constant) { 13892 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13893 } 13894 }); 13895 ast.constant = allConstants; 13896 ast.toWatch = argsToWatch; 13897 break; 13898 case AST.ObjectExpression: 13899 allConstants = true; 13900 argsToWatch = []; 13901 forEach(ast.properties, function(property) { 13902 findConstantAndWatchExpressions(property.value, $filter); 13903 allConstants = allConstants && property.value.constant; 13904 if (!property.value.constant) { 13905 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 13906 } 13907 }); 13908 ast.constant = allConstants; 13909 ast.toWatch = argsToWatch; 13910 break; 13911 case AST.ThisExpression: 13912 ast.constant = false; 13913 ast.toWatch = []; 13914 break; 13915 case AST.LocalsExpression: 13916 ast.constant = false; 13917 ast.toWatch = []; 13918 break; 13919 } 13920} 13921 13922function getInputs(body) { 13923 if (body.length != 1) return; 13924 var lastExpression = body[0].expression; 13925 var candidate = lastExpression.toWatch; 13926 if (candidate.length !== 1) return candidate; 13927 return candidate[0] !== lastExpression ? candidate : undefined; 13928} 13929 13930function isAssignable(ast) { 13931 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 13932} 13933 13934function assignableAST(ast) { 13935 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 13936 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 13937 } 13938} 13939 13940function isLiteral(ast) { 13941 return ast.body.length === 0 || 13942 ast.body.length === 1 && ( 13943 ast.body[0].expression.type === AST.Literal || 13944 ast.body[0].expression.type === AST.ArrayExpression || 13945 ast.body[0].expression.type === AST.ObjectExpression); 13946} 13947 13948function isConstant(ast) { 13949 return ast.constant; 13950} 13951 13952function ASTCompiler(astBuilder, $filter) { 13953 this.astBuilder = astBuilder; 13954 this.$filter = $filter; 13955} 13956 13957ASTCompiler.prototype = { 13958 compile: function(expression, expensiveChecks) { 13959 var self = this; 13960 var ast = this.astBuilder.ast(expression); 13961 this.state = { 13962 nextId: 0, 13963 filters: {}, 13964 expensiveChecks: expensiveChecks, 13965 fn: {vars: [], body: [], own: {}}, 13966 assign: {vars: [], body: [], own: {}}, 13967 inputs: [] 13968 }; 13969 findConstantAndWatchExpressions(ast, self.$filter); 13970 var extra = ''; 13971 var assignable; 13972 this.stage = 'assign'; 13973 if ((assignable = assignableAST(ast))) { 13974 this.state.computing = 'assign'; 13975 var result = this.nextId(); 13976 this.recurse(assignable, result); 13977 this.return_(result); 13978 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 13979 } 13980 var toWatch = getInputs(ast.body); 13981 self.stage = 'inputs'; 13982 forEach(toWatch, function(watch, key) { 13983 var fnKey = 'fn' + key; 13984 self.state[fnKey] = {vars: [], body: [], own: {}}; 13985 self.state.computing = fnKey; 13986 var intoId = self.nextId(); 13987 self.recurse(watch, intoId); 13988 self.return_(intoId); 13989 self.state.inputs.push(fnKey); 13990 watch.watchId = key; 13991 }); 13992 this.state.computing = 'fn'; 13993 this.stage = 'main'; 13994 this.recurse(ast); 13995 var fnString = 13996 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 13997 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 13998 '"' + this.USE + ' ' + this.STRICT + '";\n' + 13999 this.filterPrefix() + 14000 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 14001 extra + 14002 this.watchFns() + 14003 'return fn;'; 14004 14005 /* jshint -W054 */ 14006 var fn = (new Function('$filter', 14007 'ensureSafeMemberName', 14008 'ensureSafeObject', 14009 'ensureSafeFunction', 14010 'getStringValue', 14011 'ensureSafeAssignContext', 14012 'ifDefined', 14013 'plus', 14014 'text', 14015 fnString))( 14016 this.$filter, 14017 ensureSafeMemberName, 14018 ensureSafeObject, 14019 ensureSafeFunction, 14020 getStringValue, 14021 ensureSafeAssignContext, 14022 ifDefined, 14023 plusFn, 14024 expression); 14025 /* jshint +W054 */ 14026 this.state = this.stage = undefined; 14027 fn.literal = isLiteral(ast); 14028 fn.constant = isConstant(ast); 14029 return fn; 14030 }, 14031 14032 USE: 'use', 14033 14034 STRICT: 'strict', 14035 14036 watchFns: function() { 14037 var result = []; 14038 var fns = this.state.inputs; 14039 var self = this; 14040 forEach(fns, function(name) { 14041 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 14042 }); 14043 if (fns.length) { 14044 result.push('fn.inputs=[' + fns.join(',') + '];'); 14045 } 14046 return result.join(''); 14047 }, 14048 14049 generateFunction: function(name, params) { 14050 return 'function(' + params + '){' + 14051 this.varsPrefix(name) + 14052 this.body(name) + 14053 '};'; 14054 }, 14055 14056 filterPrefix: function() { 14057 var parts = []; 14058 var self = this; 14059 forEach(this.state.filters, function(id, filter) { 14060 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 14061 }); 14062 if (parts.length) return 'var ' + parts.join(',') + ';'; 14063 return ''; 14064 }, 14065 14066 varsPrefix: function(section) { 14067 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 14068 }, 14069 14070 body: function(section) { 14071 return this.state[section].body.join(''); 14072 }, 14073 14074 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 14075 var left, right, self = this, args, expression; 14076 recursionFn = recursionFn || noop; 14077 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 14078 intoId = intoId || this.nextId(); 14079 this.if_('i', 14080 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 14081 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 14082 ); 14083 return; 14084 } 14085 switch (ast.type) { 14086 case AST.Program: 14087 forEach(ast.body, function(expression, pos) { 14088 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 14089 if (pos !== ast.body.length - 1) { 14090 self.current().body.push(right, ';'); 14091 } else { 14092 self.return_(right); 14093 } 14094 }); 14095 break; 14096 case AST.Literal: 14097 expression = this.escape(ast.value); 14098 this.assign(intoId, expression); 14099 recursionFn(expression); 14100 break; 14101 case AST.UnaryExpression: 14102 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 14103 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 14104 this.assign(intoId, expression); 14105 recursionFn(expression); 14106 break; 14107 case AST.BinaryExpression: 14108 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 14109 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 14110 if (ast.operator === '+') { 14111 expression = this.plus(left, right); 14112 } else if (ast.operator === '-') { 14113 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 14114 } else { 14115 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 14116 } 14117 this.assign(intoId, expression); 14118 recursionFn(expression); 14119 break; 14120 case AST.LogicalExpression: 14121 intoId = intoId || this.nextId(); 14122 self.recurse(ast.left, intoId); 14123 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 14124 recursionFn(intoId); 14125 break; 14126 case AST.ConditionalExpression: 14127 intoId = intoId || this.nextId(); 14128 self.recurse(ast.test, intoId); 14129 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 14130 recursionFn(intoId); 14131 break; 14132 case AST.Identifier: 14133 intoId = intoId || this.nextId(); 14134 if (nameId) { 14135 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 14136 nameId.computed = false; 14137 nameId.name = ast.name; 14138 } 14139 ensureSafeMemberName(ast.name); 14140 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 14141 function() { 14142 self.if_(self.stage === 'inputs' || 's', function() { 14143 if (create && create !== 1) { 14144 self.if_( 14145 self.not(self.nonComputedMember('s', ast.name)), 14146 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 14147 } 14148 self.assign(intoId, self.nonComputedMember('s', ast.name)); 14149 }); 14150 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 14151 ); 14152 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 14153 self.addEnsureSafeObject(intoId); 14154 } 14155 recursionFn(intoId); 14156 break; 14157 case AST.MemberExpression: 14158 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 14159 intoId = intoId || this.nextId(); 14160 self.recurse(ast.object, left, undefined, function() { 14161 self.if_(self.notNull(left), function() { 14162 if (create && create !== 1) { 14163 self.addEnsureSafeAssignContext(left); 14164 } 14165 if (ast.computed) { 14166 right = self.nextId(); 14167 self.recurse(ast.property, right); 14168 self.getStringValue(right); 14169 self.addEnsureSafeMemberName(right); 14170 if (create && create !== 1) { 14171 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 14172 } 14173 expression = self.ensureSafeObject(self.computedMember(left, right)); 14174 self.assign(intoId, expression); 14175 if (nameId) { 14176 nameId.computed = true; 14177 nameId.name = right; 14178 } 14179 } else { 14180 ensureSafeMemberName(ast.property.name); 14181 if (create && create !== 1) { 14182 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 14183 } 14184 expression = self.nonComputedMember(left, ast.property.name); 14185 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 14186 expression = self.ensureSafeObject(expression); 14187 } 14188 self.assign(intoId, expression); 14189 if (nameId) { 14190 nameId.computed = false; 14191 nameId.name = ast.property.name; 14192 } 14193 } 14194 }, function() { 14195 self.assign(intoId, 'undefined'); 14196 }); 14197 recursionFn(intoId); 14198 }, !!create); 14199 break; 14200 case AST.CallExpression: 14201 intoId = intoId || this.nextId(); 14202 if (ast.filter) { 14203 right = self.filter(ast.callee.name); 14204 args = []; 14205 forEach(ast.arguments, function(expr) { 14206 var argument = self.nextId(); 14207 self.recurse(expr, argument); 14208 args.push(argument); 14209 }); 14210 expression = right + '(' + args.join(',') + ')'; 14211 self.assign(intoId, expression); 14212 recursionFn(intoId); 14213 } else { 14214 right = self.nextId(); 14215 left = {}; 14216 args = []; 14217 self.recurse(ast.callee, right, left, function() { 14218 self.if_(self.notNull(right), function() { 14219 self.addEnsureSafeFunction(right); 14220 forEach(ast.arguments, function(expr) { 14221 self.recurse(expr, self.nextId(), undefined, function(argument) { 14222 args.push(self.ensureSafeObject(argument)); 14223 }); 14224 }); 14225 if (left.name) { 14226 if (!self.state.expensiveChecks) { 14227 self.addEnsureSafeObject(left.context); 14228 } 14229 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 14230 } else { 14231 expression = right + '(' + args.join(',') + ')'; 14232 } 14233 expression = self.ensureSafeObject(expression); 14234 self.assign(intoId, expression); 14235 }, function() { 14236 self.assign(intoId, 'undefined'); 14237 }); 14238 recursionFn(intoId); 14239 }); 14240 } 14241 break; 14242 case AST.AssignmentExpression: 14243 right = this.nextId(); 14244 left = {}; 14245 if (!isAssignable(ast.left)) { 14246 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 14247 } 14248 this.recurse(ast.left, undefined, left, function() { 14249 self.if_(self.notNull(left.context), function() { 14250 self.recurse(ast.right, right); 14251 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 14252 self.addEnsureSafeAssignContext(left.context); 14253 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 14254 self.assign(intoId, expression); 14255 recursionFn(intoId || expression); 14256 }); 14257 }, 1); 14258 break; 14259 case AST.ArrayExpression: 14260 args = []; 14261 forEach(ast.elements, function(expr) { 14262 self.recurse(expr, self.nextId(), undefined, function(argument) { 14263 args.push(argument); 14264 }); 14265 }); 14266 expression = '[' + args.join(',') + ']'; 14267 this.assign(intoId, expression); 14268 recursionFn(expression); 14269 break; 14270 case AST.ObjectExpression: 14271 args = []; 14272 forEach(ast.properties, function(property) { 14273 self.recurse(property.value, self.nextId(), undefined, function(expr) { 14274 args.push(self.escape( 14275 property.key.type === AST.Identifier ? property.key.name : 14276 ('' + property.key.value)) + 14277 ':' + expr); 14278 }); 14279 }); 14280 expression = '{' + args.join(',') + '}'; 14281 this.assign(intoId, expression); 14282 recursionFn(expression); 14283 break; 14284 case AST.ThisExpression: 14285 this.assign(intoId, 's'); 14286 recursionFn('s'); 14287 break; 14288 case AST.LocalsExpression: 14289 this.assign(intoId, 'l'); 14290 recursionFn('l'); 14291 break; 14292 case AST.NGValueParameter: 14293 this.assign(intoId, 'v'); 14294 recursionFn('v'); 14295 break; 14296 } 14297 }, 14298 14299 getHasOwnProperty: function(element, property) { 14300 var key = element + '.' + property; 14301 var own = this.current().own; 14302 if (!own.hasOwnProperty(key)) { 14303 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 14304 } 14305 return own[key]; 14306 }, 14307 14308 assign: function(id, value) { 14309 if (!id) return; 14310 this.current().body.push(id, '=', value, ';'); 14311 return id; 14312 }, 14313 14314 filter: function(filterName) { 14315 if (!this.state.filters.hasOwnProperty(filterName)) { 14316 this.state.filters[filterName] = this.nextId(true); 14317 } 14318 return this.state.filters[filterName]; 14319 }, 14320 14321 ifDefined: function(id, defaultValue) { 14322 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 14323 }, 14324 14325 plus: function(left, right) { 14326 return 'plus(' + left + ',' + right + ')'; 14327 }, 14328 14329 return_: function(id) { 14330 this.current().body.push('return ', id, ';'); 14331 }, 14332 14333 if_: function(test, alternate, consequent) { 14334 if (test === true) { 14335 alternate(); 14336 } else { 14337 var body = this.current().body; 14338 body.push('if(', test, '){'); 14339 alternate(); 14340 body.push('}'); 14341 if (consequent) { 14342 body.push('else{'); 14343 consequent(); 14344 body.push('}'); 14345 } 14346 } 14347 }, 14348 14349 not: function(expression) { 14350 return '!(' + expression + ')'; 14351 }, 14352 14353 notNull: function(expression) { 14354 return expression + '!=null'; 14355 }, 14356 14357 nonComputedMember: function(left, right) { 14358 return left + '.' + right; 14359 }, 14360 14361 computedMember: function(left, right) { 14362 return left + '[' + right + ']'; 14363 }, 14364 14365 member: function(left, right, computed) { 14366 if (computed) return this.computedMember(left, right); 14367 return this.nonComputedMember(left, right); 14368 }, 14369 14370 addEnsureSafeObject: function(item) { 14371 this.current().body.push(this.ensureSafeObject(item), ';'); 14372 }, 14373 14374 addEnsureSafeMemberName: function(item) { 14375 this.current().body.push(this.ensureSafeMemberName(item), ';'); 14376 }, 14377 14378 addEnsureSafeFunction: function(item) { 14379 this.current().body.push(this.ensureSafeFunction(item), ';'); 14380 }, 14381 14382 addEnsureSafeAssignContext: function(item) { 14383 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 14384 }, 14385 14386 ensureSafeObject: function(item) { 14387 return 'ensureSafeObject(' + item + ',text)'; 14388 }, 14389 14390 ensureSafeMemberName: function(item) { 14391 return 'ensureSafeMemberName(' + item + ',text)'; 14392 }, 14393 14394 ensureSafeFunction: function(item) { 14395 return 'ensureSafeFunction(' + item + ',text)'; 14396 }, 14397 14398 getStringValue: function(item) { 14399 this.assign(item, 'getStringValue(' + item + ')'); 14400 }, 14401 14402 ensureSafeAssignContext: function(item) { 14403 return 'ensureSafeAssignContext(' + item + ',text)'; 14404 }, 14405 14406 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 14407 var self = this; 14408 return function() { 14409 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 14410 }; 14411 }, 14412 14413 lazyAssign: function(id, value) { 14414 var self = this; 14415 return function() { 14416 self.assign(id, value); 14417 }; 14418 }, 14419 14420 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 14421 14422 stringEscapeFn: function(c) { 14423 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 14424 }, 14425 14426 escape: function(value) { 14427 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 14428 if (isNumber(value)) return value.toString(); 14429 if (value === true) return 'true'; 14430 if (value === false) return 'false'; 14431 if (value === null) return 'null'; 14432 if (typeof value === 'undefined') return 'undefined'; 14433 14434 throw $parseMinErr('esc', 'IMPOSSIBLE'); 14435 }, 14436 14437 nextId: function(skip, init) { 14438 var id = 'v' + (this.state.nextId++); 14439 if (!skip) { 14440 this.current().vars.push(id + (init ? '=' + init : '')); 14441 } 14442 return id; 14443 }, 14444 14445 current: function() { 14446 return this.state[this.state.computing]; 14447 } 14448}; 14449 14450 14451function ASTInterpreter(astBuilder, $filter) { 14452 this.astBuilder = astBuilder; 14453 this.$filter = $filter; 14454} 14455 14456ASTInterpreter.prototype = { 14457 compile: function(expression, expensiveChecks) { 14458 var self = this; 14459 var ast = this.astBuilder.ast(expression); 14460 this.expression = expression; 14461 this.expensiveChecks = expensiveChecks; 14462 findConstantAndWatchExpressions(ast, self.$filter); 14463 var assignable; 14464 var assign; 14465 if ((assignable = assignableAST(ast))) { 14466 assign = this.recurse(assignable); 14467 } 14468 var toWatch = getInputs(ast.body); 14469 var inputs; 14470 if (toWatch) { 14471 inputs = []; 14472 forEach(toWatch, function(watch, key) { 14473 var input = self.recurse(watch); 14474 watch.input = input; 14475 inputs.push(input); 14476 watch.watchId = key; 14477 }); 14478 } 14479 var expressions = []; 14480 forEach(ast.body, function(expression) { 14481 expressions.push(self.recurse(expression.expression)); 14482 }); 14483 var fn = ast.body.length === 0 ? function() {} : 14484 ast.body.length === 1 ? expressions[0] : 14485 function(scope, locals) { 14486 var lastValue; 14487 forEach(expressions, function(exp) { 14488 lastValue = exp(scope, locals); 14489 }); 14490 return lastValue; 14491 }; 14492 if (assign) { 14493 fn.assign = function(scope, value, locals) { 14494 return assign(scope, locals, value); 14495 }; 14496 } 14497 if (inputs) { 14498 fn.inputs = inputs; 14499 } 14500 fn.literal = isLiteral(ast); 14501 fn.constant = isConstant(ast); 14502 return fn; 14503 }, 14504 14505 recurse: function(ast, context, create) { 14506 var left, right, self = this, args, expression; 14507 if (ast.input) { 14508 return this.inputs(ast.input, ast.watchId); 14509 } 14510 switch (ast.type) { 14511 case AST.Literal: 14512 return this.value(ast.value, context); 14513 case AST.UnaryExpression: 14514 right = this.recurse(ast.argument); 14515 return this['unary' + ast.operator](right, context); 14516 case AST.BinaryExpression: 14517 left = this.recurse(ast.left); 14518 right = this.recurse(ast.right); 14519 return this['binary' + ast.operator](left, right, context); 14520 case AST.LogicalExpression: 14521 left = this.recurse(ast.left); 14522 right = this.recurse(ast.right); 14523 return this['binary' + ast.operator](left, right, context); 14524 case AST.ConditionalExpression: 14525 return this['ternary?:']( 14526 this.recurse(ast.test), 14527 this.recurse(ast.alternate), 14528 this.recurse(ast.consequent), 14529 context 14530 ); 14531 case AST.Identifier: 14532 ensureSafeMemberName(ast.name, self.expression); 14533 return self.identifier(ast.name, 14534 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 14535 context, create, self.expression); 14536 case AST.MemberExpression: 14537 left = this.recurse(ast.object, false, !!create); 14538 if (!ast.computed) { 14539 ensureSafeMemberName(ast.property.name, self.expression); 14540 right = ast.property.name; 14541 } 14542 if (ast.computed) right = this.recurse(ast.property); 14543 return ast.computed ? 14544 this.computedMember(left, right, context, create, self.expression) : 14545 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 14546 case AST.CallExpression: 14547 args = []; 14548 forEach(ast.arguments, function(expr) { 14549 args.push(self.recurse(expr)); 14550 }); 14551 if (ast.filter) right = this.$filter(ast.callee.name); 14552 if (!ast.filter) right = this.recurse(ast.callee, true); 14553 return ast.filter ? 14554 function(scope, locals, assign, inputs) { 14555 var values = []; 14556 for (var i = 0; i < args.length; ++i) { 14557 values.push(args[i](scope, locals, assign, inputs)); 14558 } 14559 var value = right.apply(undefined, values, inputs); 14560 return context ? {context: undefined, name: undefined, value: value} : value; 14561 } : 14562 function(scope, locals, assign, inputs) { 14563 var rhs = right(scope, locals, assign, inputs); 14564 var value; 14565 if (rhs.value != null) { 14566 ensureSafeObject(rhs.context, self.expression); 14567 ensureSafeFunction(rhs.value, self.expression); 14568 var values = []; 14569 for (var i = 0; i < args.length; ++i) { 14570 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 14571 } 14572 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 14573 } 14574 return context ? {value: value} : value; 14575 }; 14576 case AST.AssignmentExpression: 14577 left = this.recurse(ast.left, true, 1); 14578 right = this.recurse(ast.right); 14579 return function(scope, locals, assign, inputs) { 14580 var lhs = left(scope, locals, assign, inputs); 14581 var rhs = right(scope, locals, assign, inputs); 14582 ensureSafeObject(lhs.value, self.expression); 14583 ensureSafeAssignContext(lhs.context); 14584 lhs.context[lhs.name] = rhs; 14585 return context ? {value: rhs} : rhs; 14586 }; 14587 case AST.ArrayExpression: 14588 args = []; 14589 forEach(ast.elements, function(expr) { 14590 args.push(self.recurse(expr)); 14591 }); 14592 return function(scope, locals, assign, inputs) { 14593 var value = []; 14594 for (var i = 0; i < args.length; ++i) { 14595 value.push(args[i](scope, locals, assign, inputs)); 14596 } 14597 return context ? {value: value} : value; 14598 }; 14599 case AST.ObjectExpression: 14600 args = []; 14601 forEach(ast.properties, function(property) { 14602 args.push({key: property.key.type === AST.Identifier ? 14603 property.key.name : 14604 ('' + property.key.value), 14605 value: self.recurse(property.value) 14606 }); 14607 }); 14608 return function(scope, locals, assign, inputs) { 14609 var value = {}; 14610 for (var i = 0; i < args.length; ++i) { 14611 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 14612 } 14613 return context ? {value: value} : value; 14614 }; 14615 case AST.ThisExpression: 14616 return function(scope) { 14617 return context ? {value: scope} : scope; 14618 }; 14619 case AST.LocalsExpression: 14620 return function(scope, locals) { 14621 return context ? {value: locals} : locals; 14622 }; 14623 case AST.NGValueParameter: 14624 return function(scope, locals, assign, inputs) { 14625 return context ? {value: assign} : assign; 14626 }; 14627 } 14628 }, 14629 14630 'unary+': function(argument, context) { 14631 return function(scope, locals, assign, inputs) { 14632 var arg = argument(scope, locals, assign, inputs); 14633 if (isDefined(arg)) { 14634 arg = +arg; 14635 } else { 14636 arg = 0; 14637 } 14638 return context ? {value: arg} : arg; 14639 }; 14640 }, 14641 'unary-': function(argument, context) { 14642 return function(scope, locals, assign, inputs) { 14643 var arg = argument(scope, locals, assign, inputs); 14644 if (isDefined(arg)) { 14645 arg = -arg; 14646 } else { 14647 arg = 0; 14648 } 14649 return context ? {value: arg} : arg; 14650 }; 14651 }, 14652 'unary!': function(argument, context) { 14653 return function(scope, locals, assign, inputs) { 14654 var arg = !argument(scope, locals, assign, inputs); 14655 return context ? {value: arg} : arg; 14656 }; 14657 }, 14658 'binary+': function(left, right, context) { 14659 return function(scope, locals, assign, inputs) { 14660 var lhs = left(scope, locals, assign, inputs); 14661 var rhs = right(scope, locals, assign, inputs); 14662 var arg = plusFn(lhs, rhs); 14663 return context ? {value: arg} : arg; 14664 }; 14665 }, 14666 'binary-': function(left, right, context) { 14667 return function(scope, locals, assign, inputs) { 14668 var lhs = left(scope, locals, assign, inputs); 14669 var rhs = right(scope, locals, assign, inputs); 14670 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 14671 return context ? {value: arg} : arg; 14672 }; 14673 }, 14674 'binary*': function(left, right, context) { 14675 return function(scope, locals, assign, inputs) { 14676 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 14677 return context ? {value: arg} : arg; 14678 }; 14679 }, 14680 'binary/': function(left, right, context) { 14681 return function(scope, locals, assign, inputs) { 14682 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 14683 return context ? {value: arg} : arg; 14684 }; 14685 }, 14686 'binary%': function(left, right, context) { 14687 return function(scope, locals, assign, inputs) { 14688 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 14689 return context ? {value: arg} : arg; 14690 }; 14691 }, 14692 'binary===': function(left, right, context) { 14693 return function(scope, locals, assign, inputs) { 14694 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 14695 return context ? {value: arg} : arg; 14696 }; 14697 }, 14698 'binary!==': function(left, right, context) { 14699 return function(scope, locals, assign, inputs) { 14700 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 14701 return context ? {value: arg} : arg; 14702 }; 14703 }, 14704 'binary==': function(left, right, context) { 14705 return function(scope, locals, assign, inputs) { 14706 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 14707 return context ? {value: arg} : arg; 14708 }; 14709 }, 14710 'binary!=': function(left, right, context) { 14711 return function(scope, locals, assign, inputs) { 14712 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 14713 return context ? {value: arg} : arg; 14714 }; 14715 }, 14716 'binary<': function(left, right, context) { 14717 return function(scope, locals, assign, inputs) { 14718 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 14719 return context ? {value: arg} : arg; 14720 }; 14721 }, 14722 'binary>': function(left, right, context) { 14723 return function(scope, locals, assign, inputs) { 14724 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 14725 return context ? {value: arg} : arg; 14726 }; 14727 }, 14728 'binary<=': function(left, right, context) { 14729 return function(scope, locals, assign, inputs) { 14730 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 14731 return context ? {value: arg} : arg; 14732 }; 14733 }, 14734 'binary>=': function(left, right, context) { 14735 return function(scope, locals, assign, inputs) { 14736 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 14737 return context ? {value: arg} : arg; 14738 }; 14739 }, 14740 'binary&&': function(left, right, context) { 14741 return function(scope, locals, assign, inputs) { 14742 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 14743 return context ? {value: arg} : arg; 14744 }; 14745 }, 14746 'binary||': function(left, right, context) { 14747 return function(scope, locals, assign, inputs) { 14748 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 14749 return context ? {value: arg} : arg; 14750 }; 14751 }, 14752 'ternary?:': function(test, alternate, consequent, context) { 14753 return function(scope, locals, assign, inputs) { 14754 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 14755 return context ? {value: arg} : arg; 14756 }; 14757 }, 14758 value: function(value, context) { 14759 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 14760 }, 14761 identifier: function(name, expensiveChecks, context, create, expression) { 14762 return function(scope, locals, assign, inputs) { 14763 var base = locals && (name in locals) ? locals : scope; 14764 if (create && create !== 1 && base && !(base[name])) { 14765 base[name] = {}; 14766 } 14767 var value = base ? base[name] : undefined; 14768 if (expensiveChecks) { 14769 ensureSafeObject(value, expression); 14770 } 14771 if (context) { 14772 return {context: base, name: name, value: value}; 14773 } else { 14774 return value; 14775 } 14776 }; 14777 }, 14778 computedMember: function(left, right, context, create, expression) { 14779 return function(scope, locals, assign, inputs) { 14780 var lhs = left(scope, locals, assign, inputs); 14781 var rhs; 14782 var value; 14783 if (lhs != null) { 14784 rhs = right(scope, locals, assign, inputs); 14785 rhs = getStringValue(rhs); 14786 ensureSafeMemberName(rhs, expression); 14787 if (create && create !== 1) { 14788 ensureSafeAssignContext(lhs);
14789 if (lhs && !(lhs[rhs])) { 14790 lhs[rhs] = {}; 14791 } 14792 } 14793 value = lhs[rhs]; 14794 ensureSafeObject(value, expression); 14795 } 14796 if (context) { 14797 return {context: lhs, name: rhs, value: value}; 14798 } else { 14799 return value; 14800 } 14801 }; 14802 }, 14803 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 14804 return function(scope, locals, assign, inputs) { 14805 var lhs = left(scope, locals, assign, inputs); 14806 if (create && create !== 1) { 14807 ensureSafeAssignContext(lhs); 14808 if (lhs && !(lhs[right])) { 14809 lhs[right] = {}; 14810 } 14811 } 14812 var value = lhs != null ? lhs[right] : undefined; 14813 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 14814 ensureSafeObject(value, expression); 14815 } 14816 if (context) { 14817 return {context: lhs, name: right, value: value}; 14818 } else { 14819 return value; 14820 } 14821 }; 14822 }, 14823 inputs: function(input, watchId) { 14824 return function(scope, value, locals, inputs) { 14825 if (inputs) return inputs[watchId]; 14826 return input(scope, value, locals); 14827 }; 14828 } 14829}; 14830 14831/** 14832 * @constructor 14833 */ 14834var Parser = function(lexer, $filter, options) { 14835 this.lexer = lexer; 14836 this.$filter = $filter; 14837 this.options = options; 14838 this.ast = new AST(this.lexer); 14839 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 14840 new ASTCompiler(this.ast, $filter); 14841}; 14842 14843Parser.prototype = { 14844 constructor: Parser, 14845 14846 parse: function(text) { 14847 return this.astCompiler.compile(text, this.options.expensiveChecks); 14848 } 14849}; 14850 14851function isPossiblyDangerousMemberName(name) { 14852 return name == 'constructor'; 14853} 14854 14855var objectValueOf = Object.prototype.valueOf; 14856 14857function getValueOf(value) { 14858 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 14859} 14860 14861/////////////////////////////////// 14862 14863/** 14864 * @ngdoc service 14865 * @name $parse 14866 * @kind function 14867 * 14868 * @description 14869 * 14870 * Converts Angular {@link guide/expression expression} into a function. 14871 * 14872 * ```js 14873 * var getter = $parse('user.name'); 14874 * var setter = getter.assign; 14875 * var context = {user:{name:'angular'}}; 14876 * var locals = {user:{name:'local'}}; 14877 * 14878 * expect(getter(context)).toEqual('angular'); 14879 * setter(context, 'newValue'); 14880 * expect(context.user.name).toEqual('newValue'); 14881 * expect(getter(context, locals)).toEqual('local'); 14882 * ``` 14883 * 14884 * 14885 * @param {string} expression String expression to compile. 14886 * @returns {function(context, locals)} a function which represents the compiled expression: 14887 * 14888 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14889 * are evaluated against (typically a scope object). 14890 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14891 * `context`. 14892 * 14893 * The returned function also has the following properties: 14894 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 14895 * literal. 14896 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 14897 * constant literals. 14898 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 14899 * set to a function to change its value on the given context. 14900 * 14901 */ 14902 14903 14904/** 14905 * @ngdoc provider 14906 * @name $parseProvider 14907 * 14908 * @description 14909 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 14910 * service. 14911 */ 14912function $ParseProvider() { 14913 var cacheDefault = createMap(); 14914 var cacheExpensive = createMap(); 14915 14916 this.$get = ['$filter', function($filter) { 14917 var noUnsafeEval = csp().noUnsafeEval; 14918 var $parseOptions = { 14919 csp: noUnsafeEval, 14920 expensiveChecks: false 14921 }, 14922 $parseOptionsExpensive = { 14923 csp: noUnsafeEval, 14924 expensiveChecks: true 14925 }; 14926 14927 return function $parse(exp, interceptorFn, expensiveChecks) { 14928 var parsedExpression, oneTime, cacheKey; 14929 14930 switch (typeof exp) { 14931 case 'string': 14932 exp = exp.trim(); 14933 cacheKey = exp; 14934 14935 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 14936 parsedExpression = cache[cacheKey]; 14937 14938 if (!parsedExpression) { 14939 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 14940 oneTime = true; 14941 exp = exp.substring(2); 14942 } 14943 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 14944 var lexer = new Lexer(parseOptions); 14945 var parser = new Parser(lexer, $filter, parseOptions); 14946 parsedExpression = parser.parse(exp); 14947 if (parsedExpression.constant) { 14948 parsedExpression.$$watchDelegate = constantWatchDelegate; 14949 } else if (oneTime) { 14950 parsedExpression.$$watchDelegate = parsedExpression.literal ? 14951 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 14952 } else if (parsedExpression.inputs) { 14953 parsedExpression.$$watchDelegate = inputsWatchDelegate; 14954 } 14955 cache[cacheKey] = parsedExpression; 14956 } 14957 return addInterceptor(parsedExpression, interceptorFn); 14958 14959 case 'function': 14960 return addInterceptor(exp, interceptorFn); 14961 14962 default: 14963 return addInterceptor(noop, interceptorFn); 14964 } 14965 }; 14966 14967 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 14968 14969 if (newValue == null || oldValueOfValue == null) { // null/undefined 14970 return newValue === oldValueOfValue; 14971 } 14972 14973 if (typeof newValue === 'object') { 14974 14975 // attempt to convert the value to a primitive type 14976 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 14977 // be cheaply dirty-checked 14978 newValue = getValueOf(newValue); 14979 14980 if (typeof newValue === 'object') { 14981 // objects/arrays are not supported - deep-watching them would be too expensive 14982 return false; 14983 } 14984 14985 // fall-through to the primitive equality check 14986 } 14987 14988 //Primitive or NaN 14989 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 14990 } 14991 14992 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 14993 var inputExpressions = parsedExpression.inputs; 14994 var lastResult; 14995 14996 if (inputExpressions.length === 1) { 14997 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 14998 inputExpressions = inputExpressions[0]; 14999 return scope.$watch(function expressionInputWatch(scope) { 15000 var newInputValue = inputExpressions(scope); 15001 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 15002 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 15003 oldInputValueOf = newInputValue && getValueOf(newInputValue); 15004 } 15005 return lastResult; 15006 }, listener, objectEquality, prettyPrintExpression); 15007 } 15008 15009 var oldInputValueOfValues = []; 15010 var oldInputValues = []; 15011 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 15012 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 15013 oldInputValues[i] = null; 15014 } 15015 15016 return scope.$watch(function expressionInputsWatch(scope) { 15017 var changed = false; 15018 15019 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 15020 var newInputValue = inputExpressions[i](scope); 15021 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 15022 oldInputValues[i] = newInputValue; 15023 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 15024 } 15025 } 15026 15027 if (changed) { 15028 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 15029 } 15030 15031 return lastResult; 15032 }, listener, objectEquality, prettyPrintExpression); 15033 } 15034 15035 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
15036 var unwatch, lastValue; 15037 return unwatch = scope.$watch(function oneTimeWatch(scope) { 15038 return parsedExpression(scope); 15039 }, function oneTimeListener(value, old, scope) { 15040 lastValue = value; 15041 if (isFunction(listener)) { 15042 listener.apply(this, arguments); 15043 } 15044 if (isDefined(value)) { 15045 scope.$$postDigest(function() { 15046 if (isDefined(lastValue)) { 15047 unwatch(); 15048 } 15049 }); 15050 } 15051 }, objectEquality); 15052 } 15053 15054 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 15055 var unwatch, lastValue; 15056 return unwatch = scope.$watch(function oneTimeWatch(scope) { 15057 return parsedExpression(scope); 15058 }, function oneTimeListener(value, old, scope) { 15059 lastValue = value; 15060 if (isFunction(listener)) { 15061 listener.call(this, value, old, scope); 15062 } 15063 if (isAllDefined(value)) { 15064 scope.$$postDigest(function() { 15065 if (isAllDefined(lastValue)) unwatch(); 15066 }); 15067 } 15068 }, objectEquality); 15069 15070 function isAllDefined(value) { 15071 var allDefined = true; 15072 forEach(value, function(val) { 15073 if (!isDefined(val)) allDefined = false; 15074 }); 15075 return allDefined; 15076 } 15077 } 15078 15079 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 15080 var unwatch; 15081 return unwatch = scope.$watch(function constantWatch(scope) { 15082 unwatch(); 15083 return parsedExpression(scope); 15084 }, listener, objectEquality); 15085 } 15086 15087 function addInterceptor(parsedExpression, interceptorFn) { 15088 if (!interceptorFn) return parsedExpression; 15089 var watchDelegate = parsedExpression.$$watchDelegate; 15090 var useInputs = false; 15091 15092 var regularWatch = 15093 watchDelegate !== oneTimeLiteralWatchDelegate && 15094 watchDelegate !== oneTimeWatchDelegate; 15095 15096 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 15097 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 15098 return interceptorFn(value, scope, locals); 15099 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 15100 var value = parsedExpression(scope, locals, assign, inputs); 15101 var result = interceptorFn(value, scope, locals); 15102 // we only return the interceptor's result if the 15103 // initial value is defined (for bind-once) 15104 return isDefined(value) ? result : value; 15105 }; 15106 15107 // Propagate $$watchDelegates other then inputsWatchDelegate 15108 if (parsedExpression.$$watchDelegate && 15109 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 15110 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 15111 } else if (!interceptorFn.$stateful) { 15112 // If there is an interceptor, but no watchDelegate then treat the interceptor like 15113 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 15114 fn.$$watchDelegate = inputsWatchDelegate; 15115 useInputs = !parsedExpression.inputs; 15116 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 15117 } 15118 15119 return fn; 15120 } 15121 }]; 15122} 15123 15124/** 15125 * @ngdoc service 15126 * @name $q 15127 * @requires $rootScope 15128 * 15129 * @description 15130 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 15131 * when they are done processing. 15132 * 15133 * This is an implementation of promises/deferred objects inspired by 15134 * [Kris Kowal's Q](https://github.com/kriskowal/q). 15135 * 15136 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 15137 * implementations, and the other which resembles ES6 promises to some degree. 15138 * 15139 * # $q constructor 15140 * 15141 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 15142 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 15143 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 15144 * 15145 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 15146 * available yet.
15147 * 15148 * It can be used like so: 15149 * 15150 * ```js 15151 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 15152 * // are available in the current lexical scope (they could have been injected or passed in). 15153 * 15154 * function asyncGreet(name) { 15155 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 15156 * return $q(function(resolve, reject) { 15157 * setTimeout(function() { 15158 * if (okToGreet(name)) { 15159 * resolve('Hello, ' + name + '!'); 15160 * } else { 15161 * reject('Greeting ' + name + ' is not allowed.'); 15162 * } 15163 * }, 1000); 15164 * }); 15165 * } 15166 * 15167 * var promise = asyncGreet('Robin Hood'); 15168 * promise.then(function(greeting) { 15169 * alert('Success: ' + greeting); 15170 * }, function(reason) { 15171 * alert('Failed: ' + reason); 15172 * }); 15173 * ``` 15174 * 15175 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 15176 * 15177 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 15178 * 15179 * However, the more traditional CommonJS-style usage is still available, and documented below. 15180 * 15181 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 15182 * interface for interacting with an object that represents the result of an action that is 15183 * performed asynchronously, and may or may not be finished at any given point in time. 15184 * 15185 * From the perspective of dealing with error handling, deferred and promise APIs are to 15186 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 15187 * 15188 * ```js 15189 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 15190 * // are available in the current lexical scope (they could have been injected or passed in). 15191 * 15192 * function asyncGreet(name) { 15193 * var deferred = $q.defer(); 15194 * 15195 * setTimeout(function() { 15196 * deferred.notify('About to greet ' + name + '.'); 15197 * 15198 * if (okToGreet(name)) { 15199 * deferred.resolve('Hello, ' + name + '!'); 15200 * } else { 15201 * deferred.reject('Greeting ' + name + ' is not allowed.'); 15202 * } 15203 * }, 1000); 15204 * 15205 * return deferred.promise; 15206 * } 15207 * 15208 * var promise = asyncGreet('Robin Hood'); 15209 * promise.then(function(greeting) { 15210 * alert('Success: ' + greeting); 15211 * }, function(reason) { 15212 * alert('Failed: ' + reason); 15213 * }, function(update) { 15214 * alert('Got notification: ' + update); 15215 * }); 15216 * ``` 15217 * 15218 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 15219 * comes in the way of guarantees that promise and deferred APIs make, see 15220 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 15221 * 15222 * Additionally the promise api allows for composition that is very hard to do with the 15223 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 15224 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 15225 * section on serial or parallel joining of promises. 15226 * 15227 * # The Deferred API 15228 * 15229 * A new instance of deferred is constructed by calling `$q.defer()`. 15230 * 15231 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 15232 * that can be used for signaling the successful or unsuccessful completion, as well as the status 15233 * of the task. 15234 * 15235 * **Methods** 15236 * 15237 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 15238 * constructed via `$q.reject`, the promise will be rejected instead. 15239 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 15240 * resolving it with a rejection constructed via `$q.reject`. 15241 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 15242 * multiple times before the promise is either resolved or rejected. 15243 * 15244 * **Properties** 15245 * 15246 * - promise â `{Promise}` â promise object associated with this deferred. 15247 * 15248 * 15249 * # The Promise API 15250 * 15251 * A new promise instance is created when a deferred instance is created and can be retrieved by 15252 * calling `deferred.promise`. 15253 * 15254 * The purpose of the promise object is to allow for interested parties to get access to the result 15255 * of the deferred task when it completes. 15256 * 15257 * **Methods** 15258 * 15259 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 15260 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 15261 * as soon as the result is available. The callbacks are called with a single argument: the result 15262 * or rejection reason. Additionally, the notify callback may be called zero or more times to 15263 * provide a progress indication, before the promise is resolved or rejected. 15264 * 15265 * This method *returns a new promise* which is resolved or rejected via the return value of the 15266 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
15267 * with the value which is resolved in that promise using 15268 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 15269 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 15270 * resolved or rejected from the notifyCallback method. 15271 * 15272 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 15273 * 15274 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 15275 * but to do so without modifying the final value. This is useful to release resources or do some 15276 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 15277 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 15278 * more information. 15279 * 15280 * # Chaining promises 15281 * 15282 * Because calling the `then` method of a promise returns a new derived promise, it is easily 15283 * possible to create a chain of promises: 15284 * 15285 * ```js 15286 * promiseB = promiseA.then(function(result) { 15287 * return result + 1; 15288 * }); 15289 * 15290 * // promiseB will be resolved immediately after promiseA is resolved and its value 15291 * // will be the result of promiseA incremented by 1 15292 * ``` 15293 * 15294 * It is possible to create chains of any length and since a promise can be resolved with another 15295 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 15296 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 15297 * $http's response interceptors. 15298 * 15299 * 15300 * # Differences between Kris Kowal's Q and $q 15301 * 15302 * There are two main differences: 15303 * 15304 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 15305 * mechanism in angular, which means faster propagation of resolution or rejection into your 15306 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 15307 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
15308 * all the important functionality needed for common async tasks. 15309 * 15310 * # Testing 15311 * 15312 * ```js 15313 * it('should simulate promise', inject(function($q, $rootScope) { 15314 * var deferred = $q.defer(); 15315 * var promise = deferred.promise; 15316 * var resolvedValue; 15317 * 15318 * promise.then(function(value) { resolvedValue = value; }); 15319 * expect(resolvedValue).toBeUndefined(); 15320 * 15321 * // Simulate resolving of promise 15322 * deferred.resolve(123); 15323 * // Note that the 'then' function does not get called synchronously. 15324 * // This is because we want the promise API to always be async, whether or not 15325 * // it got called synchronously or asynchronously. 15326 * expect(resolvedValue).toBeUndefined(); 15327 * 15328 * // Propagate promise resolution to 'then' functions using $apply(). 15329 * $rootScope.$apply(); 15330 * expect(resolvedValue).toEqual(123); 15331 * })); 15332 * ``` 15333 * 15334 * @param {function(function, function)} resolver Function which is responsible for resolving or 15335 * rejecting the newly created promise. The first parameter is a function which resolves the 15336 * promise, the second parameter is a function which rejects the promise. 15337 * 15338 * @returns {Promise} The newly created promise. 15339 */ 15340function $QProvider() { 15341 15342 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 15343 return qFactory(function(callback) { 15344 $rootScope.$evalAsync(callback); 15345 }, $exceptionHandler); 15346 }]; 15347} 15348 15349function $$QProvider() { 15350 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 15351 return qFactory(function(callback) { 15352 $browser.defer(callback); 15353 }, $exceptionHandler); 15354 }]; 15355} 15356 15357/** 15358 * Constructs a promise manager. 15359 * 15360 * @param {function(function)} nextTick Function for executing functions in the next turn. 15361 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 15362 * debugging purposes. 15363 * @returns {object} Promise manager. 15364 */ 15365function qFactory(nextTick, exceptionHandler) { 15366 var $qMinErr = minErr('$q', TypeError); 15367 15368 /** 15369 * @ngdoc method 15370 * @name ng.$q#defer 15371 * @kind function 15372 * 15373 * @description 15374 * Creates a `Deferred` object which represents a task which will finish in the future. 15375 * 15376 * @returns {Deferred} Returns a new instance of deferred. 15377 */ 15378 var defer = function() { 15379 var d = new Deferred(); 15380 //Necessary to support unbound execution :/ 15381 d.resolve = simpleBind(d, d.resolve); 15382 d.reject = simpleBind(d, d.reject); 15383 d.notify = simpleBind(d, d.notify); 15384 return d; 15385 }; 15386 15387 function Promise() { 15388 this.$$state = { status: 0 }; 15389 } 15390 15391 extend(Promise.prototype, { 15392 then: function(onFulfilled, onRejected, progressBack) { 15393 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 15394 return this; 15395 } 15396 var result = new Deferred(); 15397 15398 this.$$state.pending = this.$$state.pending || []; 15399 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 15400 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 15401 15402 return result.promise; 15403 }, 15404 15405 "catch": function(callback) { 15406 return this.then(null, callback); 15407 }, 15408 15409 "finally": function(callback, progressBack) { 15410 return this.then(function(value) { 15411 return handleCallback(value, true, callback); 15412 }, function(error) { 15413 return handleCallback(error, false, callback); 15414 }, progressBack); 15415 } 15416 }); 15417 15418 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 15419 function simpleBind(context, fn) { 15420 return function(value) { 15421 fn.call(context, value); 15422 }; 15423 } 15424 15425 function processQueue(state) { 15426 var fn, deferred, pending; 15427 15428 pending = state.pending; 15429 state.processScheduled = false; 15430 state.pending = undefined; 15431 for (var i = 0, ii = pending.length; i < ii; ++i) { 15432 deferred = pending[i][0]; 15433 fn = pending[i][state.status];
15434 try { 15435 if (isFunction(fn)) { 15436 deferred.resolve(fn(state.value)); 15437 } else if (state.status === 1) { 15438 deferred.resolve(state.value); 15439 } else { 15440 deferred.reject(state.value); 15441 } 15442 } catch (e) { 15443 deferred.reject(e); 15444 exceptionHandler(e); 15445 } 15446 } 15447 } 15448 15449 function scheduleProcessQueue(state) { 15450 if (state.processScheduled || !state.pending) return; 15451 state.processScheduled = true; 15452 nextTick(function() { processQueue(state); }); 15453 } 15454 15455 function Deferred() { 15456 this.promise = new Promise(); 15457 } 15458 15459 extend(Deferred.prototype, { 15460 resolve: function(val) { 15461 if (this.promise.$$state.status) return; 15462 if (val === this.promise) { 15463 this.$$reject($qMinErr( 15464 'qcycle', 15465 "Expected promise to be resolved with value other than itself '{0}'", 15466 val)); 15467 } else { 15468 this.$$resolve(val); 15469 } 15470 15471 }, 15472 15473 $$resolve: function(val) { 15474 var then; 15475 var that = this; 15476 var done = false; 15477 try { 15478 if ((isObject(val) || isFunction(val))) then = val && val.then; 15479 if (isFunction(then)) { 15480 this.promise.$$state.status = -1; 15481 then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); 15482 } else { 15483 this.promise.$$state.value = val; 15484 this.promise.$$state.status = 1; 15485 scheduleProcessQueue(this.promise.$$state); 15486 } 15487 } catch (e) { 15488 rejectPromise(e); 15489 exceptionHandler(e); 15490 } 15491 15492 function resolvePromise(val) { 15493 if (done) return; 15494 done = true; 15495 that.$$resolve(val); 15496 } 15497 function rejectPromise(val) { 15498 if (done) return; 15499 done = true; 15500 that.$$reject(val); 15501 } 15502 }, 15503 15504 reject: function(reason) { 15505 if (this.promise.$$state.status) return; 15506 this.$$reject(reason); 15507 }, 15508 15509 $$reject: function(reason) { 15510 this.promise.$$state.value = reason; 15511 this.promise.$$state.status = 2; 15512 scheduleProcessQueue(this.promise.$$state); 15513 }, 15514 15515 notify: function(progress) { 15516 var callbacks = this.promise.$$state.pending; 15517 15518 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 15519 nextTick(function() { 15520 var callback, result; 15521 for (var i = 0, ii = callbacks.length; i < ii; i++) { 15522 result = callbacks[i][0]; 15523 callback = callbacks[i][3]; 15524 try { 15525 result.notify(isFunction(callback) ? callback(progress) : progress); 15526 } catch (e) { 15527 exceptionHandler(e); 15528 } 15529 } 15530 }); 15531 } 15532 } 15533 }); 15534 15535 /** 15536 * @ngdoc method 15537 * @name $q#reject 15538 * @kind function 15539 * 15540 * @description 15541 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 15542 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 15543 * a promise chain, you don't need to worry about it. 15544 * 15545 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 15546 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 15547 * a promise error callback and you want to forward the error to the promise derived from the 15548 * current promise, you have to "rethrow" the error by returning a rejection constructed via 15549 * `reject`. 15550 * 15551 * ```js 15552 * promiseB = promiseA.then(function(result) { 15553 * // success: do something and resolve promiseB 15554 * // with the old or a new result 15555 * return result; 15556 * }, function(reason) { 15557 * // error: handle the error if possible and 15558 * // resolve promiseB with newPromiseOrValue, 15559 * // otherwise forward the rejection to promiseB 15560 * if (canHandle(reason)) { 15561 * // handle the error and recover 15562 * return newPromiseOrValue; 15563 * } 15564 * return $q.reject(reason); 15565 * }); 15566 * ``` 15567 * 15568 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 15569 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 15570 */ 15571 var reject = function(reason) { 15572 var result = new Deferred(); 15573 result.reject(reason); 15574 return result.promise; 15575 }; 15576 15577 var makePromise = function makePromise(value, resolved) { 15578 var result = new Deferred(); 15579 if (resolved) { 15580 result.resolve(value); 15581 } else { 15582 result.reject(value); 15583 } 15584 return result.promise; 15585 }; 15586 15587 var handleCallback = function handleCallback(value, isResolved, callback) { 15588 var callbackOutput = null; 15589 try { 15590 if (isFunction(callback)) callbackOutput = callback(); 15591 } catch (e) { 15592 return makePromise(e, false); 15593 }
15594 if (isPromiseLike(callbackOutput)) { 15595 return callbackOutput.then(function() { 15596 return makePromise(value, isResolved); 15597 }, function(error) { 15598 return makePromise(error, false); 15599 }); 15600 } else { 15601 return makePromise(value, isResolved); 15602 } 15603 }; 15604 15605 /** 15606 * @ngdoc method 15607 * @name $q#when 15608 * @kind function 15609 * 15610 * @description 15611 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 15612 * This is useful when you are dealing with an object that might or might not be a promise, or if 15613 * the promise comes from a source that can't be trusted. 15614 * 15615 * @param {*} value Value or a promise 15616 * @param {Function=} successCallback 15617 * @param {Function=} errorCallback 15618 * @param {Function=} progressCallback 15619 * @returns {Promise} Returns a promise of the passed value or promise 15620 */ 15621 15622 15623 var when = function(value, callback, errback, progressBack) { 15624 var result = new Deferred(); 15625 result.resolve(value); 15626 return result.promise.then(callback, errback, progressBack); 15627 }; 15628 15629 /** 15630 * @ngdoc method 15631 * @name $q#resolve 15632 * @kind function 15633 * 15634 * @description 15635 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 15636 * 15637 * @param {*} value Value or a promise 15638 * @param {Function=} successCallback 15639 * @param {Function=} errorCallback 15640 * @param {Function=} progressCallback 15641 * @returns {Promise} Returns a promise of the passed value or promise 15642 */ 15643 var resolve = when; 15644 15645 /** 15646 * @ngdoc method 15647 * @name $q#all 15648 * @kind function 15649 * 15650 * @description 15651 * Combines multiple promises into a single promise that is resolved when all of the input 15652 * promises are resolved. 15653 * 15654 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 15655 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 15656 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 15657 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 15658 * with the same rejection value. 15659 */ 15660 15661 function all(promises) { 15662 var deferred = new Deferred(), 15663 counter = 0, 15664 results = isArray(promises) ? [] : {}; 15665 15666 forEach(promises, function(promise, key) { 15667 counter++; 15668 when(promise).then(function(value) { 15669 if (results.hasOwnProperty(key)) return; 15670 results[key] = value; 15671 if (!(--counter)) deferred.resolve(results); 15672 }, function(reason) { 15673 if (results.hasOwnProperty(key)) return; 15674 deferred.reject(reason); 15675 }); 15676 }); 15677 15678 if (counter === 0) { 15679 deferred.resolve(results); 15680 } 15681 15682 return deferred.promise; 15683 } 15684 15685 var $Q = function Q(resolver) { 15686 if (!isFunction(resolver)) { 15687 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 15688 } 15689 15690 var deferred = new Deferred(); 15691 15692 function resolveFn(value) { 15693 deferred.resolve(value); 15694 } 15695 15696 function rejectFn(reason) { 15697 deferred.reject(reason); 15698 } 15699 15700 resolver(resolveFn, rejectFn); 15701 15702 return deferred.promise; 15703 }; 15704 15705 // Let's make the instanceof operator work for promises, so that 15706 // `new $q(fn) instanceof $q` would evaluate to true. 15707 $Q.prototype = Promise.prototype; 15708 15709 $Q.defer = defer; 15710 $Q.reject = reject; 15711 $Q.when = when; 15712 $Q.resolve = resolve; 15713 $Q.all = all; 15714 15715 return $Q; 15716} 15717 15718function $$RAFProvider() { //rAF 15719 this.$get = ['$window', '$timeout', function($window, $timeout) { 15720 var requestAnimationFrame = $window.requestAnimationFrame || 15721 $window.webkitRequestAnimationFrame; 15722 15723 var cancelAnimationFrame = $window.cancelAnimationFrame || 15724 $window.webkitCancelAnimationFrame || 15725 $window.webkitCancelRequestAnimationFrame; 15726 15727 var rafSupported = !!requestAnimationFrame; 15728 var raf = rafSupported 15729 ? function(fn) { 15730 var id = requestAnimationFrame(fn); 15731 return function() { 15732 cancelAnimationFrame(id); 15733 }; 15734 } 15735 : function(fn) { 15736 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 15737 return function() { 15738 $timeout.cancel(timer); 15739 }; 15740 }; 15741 15742 raf.supported = rafSupported; 15743 15744 return raf; 15745 }]; 15746} 15747 15748/** 15749 * DESIGN NOTES 15750 * 15751 * The design decisions behind the scope are heavily favored for speed and memory consumption. 15752 * 15753 * The typical use of scope is to watch the expressions, which most of the time return the same 15754 * value as last time so we optimize the operation. 15755 * 15756 * Closures construction is expensive in terms of speed as well as memory: 15757 * - No closures, instead use prototypical inheritance for API 15758 * - Internal state needs to be stored on scope directly, which means that private state is 15759 * exposed as $$____ properties 15760 * 15761 * Loop operations are optimized by using while(count--) { ... } 15762 * - This means that in order to keep the same order of execution as addition we have to add 15763 * items to the array at the beginning (unshift) instead of at the end (push) 15764 * 15765 * Child scopes are created and removed often 15766 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 15767 * 15768 * There are fewer watches than observers. This is why you don't want the observer to be implemented 15769 * in the same way as watch. Watch requires return of the initialization function which is expensive 15770 * to construct. 15771 */ 15772 15773 15774/** 15775 * @ngdoc provider 15776 * @name $rootScopeProvider 15777 * @description 15778 * 15779 * Provider for the $rootScope service. 15780 */ 15781 15782/** 15783 * @ngdoc method 15784 * @name $rootScopeProvider#digestTtl 15785 * @description 15786 * 15787 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 15788 * assuming that the model is unstable. 15789 * 15790 * The current default is 10 iterations. 15791 * 15792 * In complex applications it's possible that the dependencies between `$watch`s will result in 15793 * several digest iterations. However if an application needs more than the default 10 digest 15794 * iterations for its model to stabilize then you should investigate what is causing the model to 15795 * continuously change during the digest. 15796 * 15797 * Increasing the TTL could have performance implications, so you should not change it without 15798 * proper justification. 15799 * 15800 * @param {number} limit The number of digest iterations. 15801 */ 15802 15803 15804/** 15805 * @ngdoc service 15806 * @name $rootScope 15807 * @description 15808 * 15809 * Every application has a single root {@link ng.$rootScope.Scope scope}. 15810 * All other scopes are descendant scopes of the root scope. Scopes provide separation 15811 * between the model and the view, via a mechanism for watching the model for changes. 15812 * They also provide event emission/broadcast and subscription facility. See the 15813 * {@link guide/scope developer guide on scopes}. 15814 */ 15815function $RootScopeProvider() { 15816 var TTL = 10; 15817 var $rootScopeMinErr = minErr('$rootScope'); 15818 var lastDirtyWatch = null; 15819 var applyAsyncId = null; 15820 15821 this.digestTtl = function(value) { 15822 if (arguments.length) { 15823 TTL = value; 15824 } 15825 return TTL; 15826 }; 15827 15828 function createChildScopeClass(parent) { 15829 function ChildScope() {
15830 this.$$watchers = this.$$nextSibling = 15831 this.$$childHead = this.$$childTail = null; 15832 this.$$listeners = {}; 15833 this.$$listenerCount = {}; 15834 this.$$watchersCount = 0; 15835 this.$id = nextUid(); 15836 this.$$ChildScope = null; 15837 } 15838 ChildScope.prototype = parent; 15839 return ChildScope; 15840 } 15841 15842 this.$get = ['$exceptionHandler', '$parse', '$browser', 15843 function($exceptionHandler, $parse, $browser) { 15844 15845 function destroyChildScope($event) { 15846 $event.currentScope.$$destroyed = true; 15847 } 15848 15849 function cleanUpScope($scope) { 15850 15851 if (msie === 9) { 15852 // There is a memory leak in IE9 if all child scopes are not disconnected 15853 // completely when a scope is destroyed. So this code will recurse up through 15854 // all this scopes children 15855 // 15856 // See issue https://github.com/angular/angular.js/issues/10706 15857 $scope.$$childHead && cleanUpScope($scope.$$childHead); 15858 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling); 15859 } 15860 15861 // The code below works around IE9 and V8's memory leaks 15862 // 15863 // See: 15864 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 15865 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 15866 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 15867 15868 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 15869 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 15870 } 15871 15872 /** 15873 * @ngdoc type 15874 * @name $rootScope.Scope 15875 * 15876 * @description 15877 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 15878 * {@link auto.$injector $injector}. Child scopes are created using the 15879 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 15880 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 15881 * an in-depth introduction and usage examples. 15882 * 15883 * 15884 * # Inheritance 15885 * A scope can inherit from a parent scope, as in this example: 15886 * ```js 15887 var parent = $rootScope; 15888 var child = parent.$new(); 15889 15890 parent.salutation = "Hello"; 15891 expect(child.salutation).toEqual('Hello'); 15892 15893 child.salutation = "Welcome"; 15894 expect(child.salutation).toEqual('Welcome'); 15895 expect(parent.salutation).toEqual('Hello'); 15896 * ``` 15897 * 15898 * When interacting with `Scope` in tests, additional helper methods are available on the 15899 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 15900 * details. 15901 * 15902 * 15903 * @param {Object.<string, function()>=} providers Map of service factory which need to be 15904 * provided for the current scope. Defaults to {@link ng}. 15905 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 15906 * append/override services provided by `providers`. This is handy 15907 * when unit-testing and having the need to override a default 15908 * service. 15909 * @returns {Object} Newly created scope. 15910 * 15911 */ 15912 function Scope() { 15913 this.$id = nextUid(); 15914 this.$$phase = this.$parent = this.$$watchers = 15915 this.$$nextSibling = this.$$prevSibling = 15916 this.$$childHead = this.$$childTail = null; 15917 this.$root = this; 15918 this.$$destroyed = false; 15919 this.$$listeners = {}; 15920 this.$$listenerCount = {}; 15921 this.$$watchersCount = 0; 15922 this.$$isolateBindings = null; 15923 } 15924 15925 /** 15926 * @ngdoc property 15927 * @name $rootScope.Scope#$id 15928 * 15929 * @description 15930 * Unique scope ID (monotonically increasing) useful for debugging. 15931 */ 15932 15933 /** 15934 * @ngdoc property 15935 * @name $rootScope.Scope#$parent 15936 * 15937 * @description 15938 * Reference to the parent scope. 15939 */ 15940 15941 /** 15942 * @ngdoc property 15943 * @name $rootScope.Scope#$root 15944 *
vendor: 5,748 bytes, lines 15945-16051
15945 * @description 15946 * Reference to the root scope. 15947 */ 15948 15949 Scope.prototype = { 15950 constructor: Scope, 15951 /** 15952 * @ngdoc method 15953 * @name $rootScope.Scope#$new 15954 * @kind function 15955 * 15956 * @description 15957 * Creates a new child {@link ng.$rootScope.Scope scope}. 15958 * 15959 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 15960 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 15961 * 15962 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 15963 * desired for the scope and its child scopes to be permanently detached from the parent and 15964 * thus stop participating in model change detection and listener notification by invoking. 15965 * 15966 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 15967 * parent scope. The scope is isolated, as it can not see parent scope properties. 15968 * When creating widgets, it is useful for the widget to not accidentally read parent 15969 * state. 15970 * 15971 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 15972 * of the newly created scope. Defaults to `this` scope if not provided. 15973 * This is used when creating a transclude scope to correctly place it 15974 * in the scope hierarchy while maintaining the correct prototypical 15975 * inheritance. 15976 * 15977 * @returns {Object} The newly created child scope. 15978 * 15979 */ 15980 $new: function(isolate, parent) { 15981 var child; 15982 15983 parent = parent || this; 15984 15985 if (isolate) { 15986 child = new Scope(); 15987 child.$root = this.$root; 15988 } else { 15989 // Only create a child scope class if somebody asks for one, 15990 // but cache it to allow the VM to optimize lookups. 15991 if (!this.$$ChildScope) { 15992 this.$$ChildScope = createChildScopeClass(this); 15993 } 15994 child = new this.$$ChildScope(); 15995 } 15996 child.$parent = parent; 15997 child.$$prevSibling = parent.$$childTail; 15998 if (parent.$$childHead) { 15999 parent.$$childTail.$$nextSibling = child; 16000 parent.$$childTail = child; 16001 } else { 16002 parent.$$childHead = parent.$$childTail = child; 16003 } 16004 16005 // When the new scope is not isolated or we inherit from `this`, and 16006 // the parent scope is destroyed, the property `$$destroyed` is inherited 16007 // prototypically. In all other cases, this property needs to be set 16008 // when the parent scope is destroyed. 16009 // The listener needs to be added after the parent is set 16010 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 16011 16012 return child; 16013 }, 16014 16015 /** 16016 * @ngdoc method 16017 * @name $rootScope.Scope#$watch 16018 * @kind function 16019 * 16020 * @description 16021 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 16022 * 16023 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 16024 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 16025 * its value when executed multiple times with the same input because it may be executed multiple 16026 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 16027 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 16028 * - The `listener` is called only when the value from the current `watchExpression` and the 16029 * previous call to `watchExpression` are not equal (with the exception of the initial run, 16030 * see below). Inequality is determined according to reference inequality, 16031 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 16032 * via the `!==` Javascript operator, unless `objectEquality == true` 16033 * (see next point) 16034 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 16035 * according to the {@link angular.equals} function. To save the value of the object for 16036 * later comparison, the {@link angular.copy} function is used. This therefore means that 16037 * watching complex objects will have adverse memory and performance implications. 16038 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 16039 * This is achieved by rerunning the watchers until no changes are detected. The rerun 16040 * iteration limit is 10 to prevent an infinite loop deadlock. 16041 * 16042 * 16043 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 16044 * you can register a `watchExpression` function with no `listener`. (Be prepared for 16045 * multiple calls to your `watchExpression` because it will execute multiple times in a 16046 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 16047 * 16048 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 16049 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 16050 * watcher. In rare cases, this is undesirable because the listener is called when the result 16051 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
16052 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 16053 * listener was called due to initialization. 16054 * 16055 * 16056 * 16057 * # Example 16058 * ```js 16059 // let's assume that scope was dependency injected as the $rootScope 16060 var scope = $rootScope; 16061 scope.name = 'misko'; 16062 scope.counter = 0; 16063 16064 expect(scope.counter).toEqual(0); 16065 scope.$watch('name', function(newValue, oldValue) { 16066 scope.counter = scope.counter + 1; 16067 }); 16068 expect(scope.counter).toEqual(0); 16069 16070 scope.$digest(); 16071 // the listener is always called during the first $digest loop after it was registered 16072 expect(scope.counter).toEqual(1); 16073 16074 scope.$digest(); 16075 // but now it will not be called unless the value changes 16076 expect(scope.counter).toEqual(1); 16077 16078 scope.name = 'adam'; 16079 scope.$digest(); 16080 expect(scope.counter).toEqual(2); 16081 16082 16083 16084 // Using a function as a watchExpression 16085 var food; 16086 scope.foodCounter = 0; 16087 expect(scope.foodCounter).toEqual(0); 16088 scope.$watch( 16089 // This function returns the value being watched. It is called for each turn of the $digest loop 16090 function() { return food; }, 16091 // This is the change listener, called when the value returned from the above function changes 16092 function(newValue, oldValue) { 16093 if ( newValue !== oldValue ) { 16094 // Only increment the counter if the value changed 16095 scope.foodCounter = scope.foodCounter + 1; 16096 } 16097 } 16098 ); 16099 // No digest has been run so the counter will be zero 16100 expect(scope.foodCounter).toEqual(0); 16101 16102 // Run the digest but since food has not changed count will still be zero 16103 scope.$digest(); 16104 expect(scope.foodCounter).toEqual(0); 16105 16106 // Update food and run digest. Now the counter will increment 16107 food = 'cheeseburger'; 16108 scope.$digest(); 16109 expect(scope.foodCounter).toEqual(1); 16110 16111 * ``` 16112 * 16113 * 16114 * 16115 * @param {(function()|string)} watchExpression Expression that is evaluated on each 16116 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 16117 * a call to the `listener`. 16118 * 16119 * - `string`: Evaluated as {@link guide/expression expression} 16120 * - `function(scope)`: called with current `scope` as a parameter. 16121 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 16122 * of `watchExpression` changes. 16123 * 16124 * - `newVal` contains the current value of the `watchExpression` 16125 * - `oldVal` contains the previous value of the `watchExpression` 16126 * - `scope` refers to the current scope 16127 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 16128 * comparing for reference equality. 16129 * @returns {function()} Returns a deregistration function for this listener. 16130 */ 16131 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 16132 var get = $parse(watchExp); 16133 16134 if (get.$$watchDelegate) { 16135 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 16136 } 16137 var scope = this, 16138 array = scope.$$watchers, 16139 watcher = { 16140 fn: listener, 16141 last: initWatchVal, 16142 get: get, 16143 exp: prettyPrintExpression || watchExp, 16144 eq: !!objectEquality 16145 }; 16146 16147 lastDirtyWatch = null; 16148 16149 if (!isFunction(listener)) { 16150 watcher.fn = noop; 16151 } 16152 16153 if (!array) { 16154 array = scope.$$watchers = []; 16155 } 16156 // we use unshift since we use a while loop in $digest for speed. 16157 // the while loop reads in reverse order. 16158 array.unshift(watcher); 16159 incrementWatchersCount(this, 1); 16160 16161 return function deregisterWatch() { 16162 if (arrayRemove(array, watcher) >= 0) { 16163 incrementWatchersCount(scope, -1); 16164 } 16165 lastDirtyWatch = null; 16166 }; 16167 }, 16168 16169 /** 16170 * @ngdoc method 16171 * @name $rootScope.Scope#$watchGroup 16172 * @kind function 16173 *
16174 * @description 16175 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 16176 * If any one expression in the collection changes the `listener` is executed. 16177 * 16178 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 16179 * call to $digest() to see if any items changes. 16180 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 16181 * 16182 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 16183 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 16184 * 16185 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 16186 * expression in `watchExpressions` changes 16187 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 16188 * those of `watchExpression` 16189 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 16190 * those of `watchExpression` 16191 * The `scope` refers to the current scope. 16192 * @returns {function()} Returns a de-registration function for all listeners. 16193 */ 16194 $watchGroup: function(watchExpressions, listener) { 16195 var oldValues = new Array(watchExpressions.length); 16196 var newValues = new Array(watchExpressions.length); 16197 var deregisterFns = []; 16198 var self = this; 16199 var changeReactionScheduled = false; 16200 var firstRun = true; 16201 16202 if (!watchExpressions.length) { 16203 // No expressions means we call the listener ASAP 16204 var shouldCall = true; 16205 self.$evalAsync(function() { 16206 if (shouldCall) listener(newValues, newValues, self); 16207 }); 16208 return function deregisterWatchGroup() { 16209 shouldCall = false; 16210 }; 16211 } 16212 16213 if (watchExpressions.length === 1) { 16214 // Special case size of one 16215 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 16216 newValues[0] = value; 16217 oldValues[0] = oldValue; 16218 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 16219 }); 16220 } 16221 16222 forEach(watchExpressions, function(expr, i) { 16223 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 16224 newValues[i] = value; 16225 oldValues[i] = oldValue; 16226 if (!changeReactionScheduled) { 16227 changeReactionScheduled = true; 16228 self.$evalAsync(watchGroupAction); 16229 } 16230 }); 16231 deregisterFns.push(unwatchFn); 16232 }); 16233 16234 function watchGroupAction() { 16235 changeReactionScheduled = false; 16236 16237 if (firstRun) { 16238 firstRun = false; 16239 listener(newValues, newValues, self); 16240 } else { 16241 listener(newValues, oldValues, self); 16242 } 16243 } 16244 16245 return function deregisterWatchGroup() { 16246 while (deregisterFns.length) { 16247 deregisterFns.shift()(); 16248 } 16249 }; 16250 }, 16251 16252 16253 /** 16254 * @ngdoc method 16255 * @name $rootScope.Scope#$watchCollection 16256 * @kind function 16257 * 16258 * @description 16259 * Shallow watches the properties of an object and fires whenever any of the properties change 16260 * (for arrays, this implies watching the array items; for object maps, this implies watching 16261 * the properties). If a change is detected, the `listener` callback is fired. 16262 * 16263 * - The `obj` collection is observed via standard $watch operation and is examined on every 16264 * call to $digest() to see if any items have been added, removed, or moved. 16265 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 16266 * adding, removing, and moving items belonging to an object or array. 16267 * 16268 * 16269 * # Example 16270 * ```js 16271 $scope.names = ['igor', 'matias', 'misko', 'james']; 16272 $scope.dataCount = 4; 16273 16274 $scope.$watchCollection('names', function(newNames, oldNames) { 16275 $scope.dataCount = newNames.length; 16276 }); 16277 16278 expect($scope.dataCount).toEqual(4); 16279 $scope.$digest(); 16280 16281 //still at 4 ... no changes 16282 expect($scope.dataCount).toEqual(4); 16283 16284 $scope.names.pop(); 16285 $scope.$digest(); 16286 16287 //now there's been a change 16288 expect($scope.dataCount).toEqual(3); 16289 * ``` 16290 * 16291 * 16292 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 16293 * expression value should evaluate to an object or an array which is observed on each 16294 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 16295 * collection will trigger a call to the `listener`. 16296 * 16297 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 16298 * when a change is detected. 16299 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 16300 * - The `oldCollection` object is a copy of the former collection data. 16301 * Due to performance considerations, the`oldCollection` value is computed only if the 16302 * `listener` function declares two or more arguments. 16303 * - The `scope` argument refers to the current scope. 16304 * 16305 * @returns {function()} Returns a de-registration function for this listener. When the 16306 * de-registration function is executed, the internal watch operation is terminated. 16307 */ 16308 $watchCollection: function(obj, listener) { 16309 $watchCollectionInterceptor.$stateful = true; 16310 16311 var self = this; 16312 // the current value, updated on each dirty-check run 16313 var newValue; 16314 // a shallow copy of the newValue from the last dirty-check run, 16315 // updated to match newValue during dirty-check run 16316 var oldValue; 16317 // a shallow copy of the newValue from when the last change happened 16318 var veryOldValue; 16319 // only track veryOldValue if the listener is asking for it 16320 var trackVeryOldValue = (listener.length > 1); 16321 var changeDetected = 0; 16322 var changeDetector = $parse(obj, $watchCollectionInterceptor); 16323 var internalArray = []; 16324 var internalObject = {}; 16325 var initRun = true; 16326 var oldLength = 0; 16327 16328 function $watchCollectionInterceptor(_value) { 16329 newValue = _value; 16330 var newLength, key, bothNaN, newItem, oldItem; 16331 16332 // If the new value is undefined, then return undefined as the watch may be a one-time watch 16333 if (isUndefined(newValue)) return; 16334 16335 if (!isObject(newValue)) { // if primitive 16336 if (oldValue !== newValue) { 16337 oldValue = newValue; 16338 changeDetected++; 16339 } 16340 } else if (isArrayLike(newValue)) { 16341 if (oldValue !== internalArray) { 16342 // we are transitioning from something which was not an array into array. 16343 oldValue = internalArray; 16344 oldLength = oldValue.length = 0; 16345 changeDetected++; 16346 } 16347 16348 newLength = newValue.length; 16349 16350 if (oldLength !== newLength) { 16351 // if lengths do not match we need to trigger change notification 16352 changeDetected++; 16353 oldValue.length = oldLength = newLength; 16354 } 16355 // copy the items to oldValue and look for changes. 16356 for (var i = 0; i < newLength; i++) { 16357 oldItem = oldValue[i]; 16358 newItem = newValue[i]; 16359 16360 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
vendor: 5,713 bytes, lines 16361-16512
16361 if (!bothNaN && (oldItem !== newItem)) { 16362 changeDetected++; 16363 oldValue[i] = newItem; 16364 } 16365 } 16366 } else { 16367 if (oldValue !== internalObject) { 16368 // we are transitioning from something which was not an object into object. 16369 oldValue = internalObject = {}; 16370 oldLength = 0; 16371 changeDetected++; 16372 } 16373 // copy the items to oldValue and look for changes. 16374 newLength = 0; 16375 for (key in newValue) { 16376 if (hasOwnProperty.call(newValue, key)) { 16377 newLength++; 16378 newItem = newValue[key]; 16379 oldItem = oldValue[key]; 16380 16381 if (key in oldValue) { 16382 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 16383 if (!bothNaN && (oldItem !== newItem)) { 16384 changeDetected++; 16385 oldValue[key] = newItem; 16386 } 16387 } else { 16388 oldLength++; 16389 oldValue[key] = newItem; 16390 changeDetected++; 16391 } 16392 } 16393 } 16394 if (oldLength > newLength) { 16395 // we used to have more keys, need to find them and destroy them. 16396 changeDetected++; 16397 for (key in oldValue) { 16398 if (!hasOwnProperty.call(newValue, key)) { 16399 oldLength--; 16400 delete oldValue[key]; 16401 } 16402 } 16403 } 16404 } 16405 return changeDetected; 16406 } 16407 16408 function $watchCollectionAction() { 16409 if (initRun) { 16410 initRun = false; 16411 listener(newValue, newValue, self); 16412 } else { 16413 listener(newValue, veryOldValue, self); 16414 } 16415 16416 // make a copy for the next time a collection is changed 16417 if (trackVeryOldValue) { 16418 if (!isObject(newValue)) { 16419 //primitive 16420 veryOldValue = newValue; 16421 } else if (isArrayLike(newValue)) { 16422 veryOldValue = new Array(newValue.length); 16423 for (var i = 0; i < newValue.length; i++) { 16424 veryOldValue[i] = newValue[i]; 16425 } 16426 } else { // if object 16427 veryOldValue = {}; 16428 for (var key in newValue) { 16429 if (hasOwnProperty.call(newValue, key)) { 16430 veryOldValue[key] = newValue[key]; 16431 } 16432 } 16433 } 16434 } 16435 } 16436 16437 return this.$watch(changeDetector, $watchCollectionAction); 16438 }, 16439 16440 /** 16441 * @ngdoc method 16442 * @name $rootScope.Scope#$digest 16443 * @kind function 16444 * 16445 * @description 16446 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 16447 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 16448 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 16449 * until no more listeners are firing. This means that it is possible to get into an infinite 16450 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 16451 * iterations exceeds 10. 16452 * 16453 * Usually, you don't call `$digest()` directly in 16454 * {@link ng.directive:ngController controllers} or in 16455 * {@link ng.$compileProvider#directive directives}. 16456 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 16457 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 16458 * 16459 * If you want to be notified whenever `$digest()` is called, 16460 * you can register a `watchExpression` function with 16461 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 16462 * 16463 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 16464 * 16465 * # Example 16466 * ```js 16467 var scope = ...; 16468 scope.name = 'misko'; 16469 scope.counter = 0; 16470 16471 expect(scope.counter).toEqual(0); 16472 scope.$watch('name', function(newValue, oldValue) { 16473 scope.counter = scope.counter + 1; 16474 }); 16475 expect(scope.counter).toEqual(0); 16476 16477 scope.$digest(); 16478 // the listener is always called during the first $digest loop after it was registered 16479 expect(scope.counter).toEqual(1); 16480 16481 scope.$digest(); 16482 // but now it will not be called unless the value changes 16483 expect(scope.counter).toEqual(1); 16484 16485 scope.name = 'adam'; 16486 scope.$digest(); 16487 expect(scope.counter).toEqual(2); 16488 * ``` 16489 * 16490 */ 16491 $digest: function() { 16492 var watch, value, last, 16493 watchers, 16494 length, 16495 dirty, ttl = TTL, 16496 next, current, target = this, 16497 watchLog = [], 16498 logIdx, logMsg, asyncTask; 16499 16500 beginPhase('$digest'); 16501 // Check for changes to browser url that happened in sync before the call to $digest 16502 $browser.$$checkUrlChange(); 16503 16504 if (this === $rootScope && applyAsyncId !== null) { 16505 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 16506 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 16507 $browser.defer.cancel(applyAsyncId); 16508 flushApplyAsync(); 16509 } 16510 16511 lastDirtyWatch = null; 16512
16513 do { // "while dirty" loop 16514 dirty = false; 16515 current = target; 16516 16517 while (asyncQueue.length) { 16518 try { 16519 asyncTask = asyncQueue.shift(); 16520 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 16521 } catch (e) { 16522 $exceptionHandler(e); 16523 } 16524 lastDirtyWatch = null; 16525 } 16526 16527 traverseScopesLoop: 16528 do { // "traverse the scopes" loop 16529 if ((watchers = current.$$watchers)) { 16530 // process our watches 16531 length = watchers.length; 16532 while (length--) { 16533 try { 16534 watch = watchers[length]; 16535 // Most common watches are on primitives, in which case we can short 16536 // circuit it with === operator, only when === fails do we use .equals 16537 if (watch) { 16538 if ((value = watch.get(current)) !== (last = watch.last) && 16539 !(watch.eq 16540 ? equals(value, last) 16541 : (typeof value === 'number' && typeof last === 'number' 16542 && isNaN(value) && isNaN(last)))) { 16543 dirty = true; 16544 lastDirtyWatch = watch; 16545 watch.last = watch.eq ? copy(value, null) : value; 16546 watch.fn(value, ((last === initWatchVal) ? value : last), current); 16547 if (ttl < 5) { 16548 logIdx = 4 - ttl; 16549 if (!watchLog[logIdx]) watchLog[logIdx] = []; 16550 watchLog[logIdx].push({ 16551 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 16552 newVal: value, 16553 oldVal: last 16554 }); 16555 } 16556 } else if (watch === lastDirtyWatch) { 16557 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 16558 // have already been tested. 16559 dirty = false; 16560 break traverseScopesLoop; 16561 } 16562 } 16563 } catch (e) { 16564 $exceptionHandler(e); 16565 } 16566 } 16567 } 16568 16569 // Insanity Warning: scope depth-first traversal 16570 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16571 // this piece should be kept in sync with the traversal in $broadcast 16572 if (!(next = ((current.$$watchersCount && current.$$childHead) || 16573 (current !== target && current.$$nextSibling)))) { 16574 while (current !== target && !(next = current.$$nextSibling)) { 16575 current = current.$parent; 16576 } 16577 } 16578 } while ((current = next)); 16579 16580 // `break traverseScopesLoop;` takes us to here 16581 16582 if ((dirty || asyncQueue.length) && !(ttl--)) { 16583 clearPhase(); 16584 throw $rootScopeMinErr('infdig', 16585 '{0} $digest() iterations reached. Aborting!\n' + 16586 'Watchers fired in the last 5 iterations: {1}', 16587 TTL, watchLog); 16588 } 16589 16590 } while (dirty || asyncQueue.length); 16591 16592 clearPhase(); 16593 16594 while (postDigestQueue.length) { 16595 try { 16596 postDigestQueue.shift()(); 16597 } catch (e) { 16598 $exceptionHandler(e); 16599 } 16600 } 16601 }, 16602 16603 16604 /** 16605 * @ngdoc event 16606 * @name $rootScope.Scope#$destroy 16607 * @eventType broadcast on scope being destroyed 16608 * 16609 * @description 16610 * Broadcasted when a scope and its children are being destroyed. 16611 * 16612 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 16613 * clean up DOM bindings before an element is removed from the DOM. 16614 */ 16615 16616 /** 16617 * @ngdoc method 16618 * @name $rootScope.Scope#$destroy 16619 * @kind function 16620 *
16621 * @description 16622 * Removes the current scope (and all of its children) from the parent scope. Removal implies 16623 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 16624 * propagate to the current scope and its children. Removal also implies that the current 16625 * scope is eligible for garbage collection. 16626 * 16627 * The `$destroy()` is usually used by directives such as 16628 * {@link ng.directive:ngRepeat ngRepeat} for managing the 16629 * unrolling of the loop. 16630 * 16631 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 16632 * Application code can register a `$destroy` event handler that will give it a chance to 16633 * perform any necessary cleanup. 16634 * 16635 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 16636 * clean up DOM bindings before an element is removed from the DOM. 16637 */ 16638 $destroy: function() { 16639 // We can't destroy a scope that has been already destroyed. 16640 if (this.$$destroyed) return; 16641 var parent = this.$parent; 16642 16643 this.$broadcast('$destroy'); 16644 this.$$destroyed = true; 16645 16646 if (this === $rootScope) { 16647 //Remove handlers attached to window when $rootScope is removed 16648 $browser.$$applicationDestroyed(); 16649 } 16650 16651 incrementWatchersCount(this, -this.$$watchersCount); 16652 for (var eventName in this.$$listenerCount) { 16653 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 16654 } 16655 16656 // sever all the references to parent scopes (after this cleanup, the current scope should 16657 // not be retained by any of our references and should be eligible for garbage collection) 16658 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 16659 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 16660 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 16661 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 16662 16663 // Disable listeners, watchers and apply/digest methods 16664 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 16665 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 16666 this.$$listeners = {}; 16667 16668 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 16669 this.$$nextSibling = null; 16670 cleanUpScope(this); 16671 }, 16672 16673 /** 16674 * @ngdoc method 16675 * @name $rootScope.Scope#$eval 16676 * @kind function 16677 * 16678 * @description 16679 * Executes the `expression` on the current scope and returns the result. Any exceptions in 16680 * the expression are propagated (uncaught). This is useful when evaluating Angular 16681 * expressions. 16682 * 16683 * # Example 16684 * ```js 16685 var scope = ng.$rootScope.Scope(); 16686 scope.a = 1; 16687 scope.b = 2; 16688 16689 expect(scope.$eval('a+b')).toEqual(3); 16690 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 16691 * ``` 16692 * 16693 * @param {(string|function())=} expression An angular expression to be executed. 16694 * 16695 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16696 * - `function(scope)`: execute the function with the current `scope` parameter. 16697 * 16698 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16699 * @returns {*} The result of evaluating the expression. 16700 */ 16701 $eval: function(expr, locals) { 16702 return $parse(expr)(this, locals); 16703 }, 16704 16705 /** 16706 * @ngdoc method 16707 * @name $rootScope.Scope#$evalAsync 16708 * @kind function 16709 * 16710 * @description 16711 * Executes the expression on the current scope at a later point in time. 16712 * 16713 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 16714 * that: 16715 * 16716 * - it will execute after the function that scheduled the evaluation (preferably before DOM 16717 * rendering). 16718 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 16719 * `expression` execution. 16720 * 16721 * Any exceptions from the execution of the expression are forwarded to the 16722 * {@link ng.$exceptionHandler $exceptionHandler} service. 16723 * 16724 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 16725 * will be scheduled. However, it is encouraged to always call code that changes the model 16726 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 16727 * 16728 * @param {(string|function())=} expression An angular expression to be executed. 16729 * 16730 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16731 * - `function(scope)`: execute the function with the current `scope` parameter. 16732 * 16733 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16734 */ 16735 $evalAsync: function(expr, locals) { 16736 // if we are outside of an $digest loop and this is the first time we are scheduling async 16737 // task also schedule async auto-flush 16738 if (!$rootScope.$$phase && !asyncQueue.length) { 16739 $browser.defer(function() { 16740 if (asyncQueue.length) { 16741 $rootScope.$digest(); 16742 } 16743 }); 16744 } 16745 16746 asyncQueue.push({scope: this, expression: expr, locals: locals}); 16747 }, 16748 16749 $$postDigest: function(fn) { 16750 postDigestQueue.push(fn); 16751 }, 16752 16753 /** 16754 * @ngdoc method 16755 * @name $rootScope.Scope#$apply 16756 * @kind function 16757 *
16758 * @description 16759 * `$apply()` is used to execute an expression in angular from outside of the angular 16760 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 16761 * Because we are calling into the angular framework we need to perform proper scope life 16762 * cycle of {@link ng.$exceptionHandler exception handling}, 16763 * {@link ng.$rootScope.Scope#$digest executing watches}. 16764 * 16765 * ## Life cycle 16766 * 16767 * # Pseudo-Code of `$apply()` 16768 * ```js 16769 function $apply(expr) { 16770 try { 16771 return $eval(expr); 16772 } catch (e) { 16773 $exceptionHandler(e); 16774 } finally { 16775 $root.$digest(); 16776 } 16777 } 16778 * ``` 16779 * 16780 * 16781 * Scope's `$apply()` method transitions through the following stages: 16782 * 16783 * 1. The {@link guide/expression expression} is executed using the 16784 * {@link ng.$rootScope.Scope#$eval $eval()} method. 16785 * 2. Any exceptions from the execution of the expression are forwarded to the 16786 * {@link ng.$exceptionHandler $exceptionHandler} service. 16787 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 16788 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 16789 * 16790 * 16791 * @param {(string|function())=} exp An angular expression to be executed. 16792 * 16793 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16794 * - `function(scope)`: execute the function with current `scope` parameter. 16795 * 16796 * @returns {*} The result of evaluating the expression. 16797 */ 16798 $apply: function(expr) { 16799 try { 16800 beginPhase('$apply'); 16801 try { 16802 return this.$eval(expr); 16803 } finally { 16804 clearPhase(); 16805 } 16806 } catch (e) { 16807 $exceptionHandler(e); 16808 } finally { 16809 try { 16810 $rootScope.$digest(); 16811 } catch (e) { 16812 $exceptionHandler(e); 16813 throw e; 16814 } 16815 } 16816 }, 16817 16818 /** 16819 * @ngdoc method 16820 * @name $rootScope.Scope#$applyAsync 16821 * @kind function 16822 * 16823 * @description 16824 * Schedule the invocation of $apply to occur at a later time. The actual time difference 16825 * varies across browsers, but is typically around ~10 milliseconds. 16826 * 16827 * This can be used to queue up multiple expressions which need to be evaluated in the same 16828 * digest. 16829 * 16830 * @param {(string|function())=} exp An angular expression to be executed. 16831 * 16832 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16833 * - `function(scope)`: execute the function with current `scope` parameter. 16834 */ 16835 $applyAsync: function(expr) { 16836 var scope = this; 16837 expr && applyAsyncQueue.push($applyAsyncExpression); 16838 scheduleApplyAsync(); 16839 16840 function $applyAsyncExpression() { 16841 scope.$eval(expr); 16842 } 16843 }, 16844 16845 /** 16846 * @ngdoc method 16847 * @name $rootScope.Scope#$on 16848 * @kind function 16849 * 16850 * @description 16851 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 16852 * discussion of event life cycle. 16853 * 16854 * The event listener function format is: `function(event, args...)`. The `event` object 16855 * passed into the listener has the following attributes: 16856 * 16857 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 16858 * `$broadcast`-ed. 16859 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 16860 * event propagates through the scope hierarchy, this property is set to null. 16861 * - `name` - `{string}`: name of the event. 16862 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 16863 * further event propagation (available only for events that were `$emit`-ed). 16864 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 16865 * to true. 16866 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 16867 * 16868 * @param {string} name Event name to listen on. 16869 * @param {function(event, ...args)} listener Function to call when the event is emitted. 16870 * @returns {function()} Returns a deregistration function for this listener. 16871 */ 16872 $on: function(name, listener) { 16873 var namedListeners = this.$$listeners[name]; 16874 if (!namedListeners) { 16875 this.$$listeners[name] = namedListeners = []; 16876 } 16877 namedListeners.push(listener); 16878 16879 var current = this; 16880 do { 16881 if (!current.$$listenerCount[name]) { 16882 current.$$listenerCount[name] = 0; 16883 } 16884 current.$$listenerCount[name]++; 16885 } while ((current = current.$parent)); 16886 16887 var self = this; 16888 return function() { 16889 var indexOfListener = namedListeners.indexOf(listener); 16890 if (indexOfListener !== -1) { 16891 namedListeners[indexOfListener] = null; 16892 decrementListenerCount(self, 1, name); 16893 } 16894 }; 16895 }, 16896 16897 16898 /** 16899 * @ngdoc method 16900 * @name $rootScope.Scope#$emit 16901 * @kind function 16902 *
16903 * @description 16904 * Dispatches an event `name` upwards through the scope hierarchy notifying the 16905 * registered {@link ng.$rootScope.Scope#$on} listeners. 16906 * 16907 * The event life cycle starts at the scope on which `$emit` was called. All 16908 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16909 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 16910 * registered listeners along the way. The event will stop propagating if one of the listeners 16911 * cancels it. 16912 * 16913 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16914 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16915 * 16916 * @param {string} name Event name to emit. 16917 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16918 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 16919 */ 16920 $emit: function(name, args) { 16921 var empty = [], 16922 namedListeners, 16923 scope = this, 16924 stopPropagation = false, 16925 event = { 16926 name: name, 16927 targetScope: scope, 16928 stopPropagation: function() {stopPropagation = true;}, 16929 preventDefault: function() { 16930 event.defaultPrevented = true; 16931 }, 16932 defaultPrevented: false 16933 }, 16934 listenerArgs = concat([event], arguments, 1), 16935 i, length; 16936 16937 do { 16938 namedListeners = scope.$$listeners[name] || empty; 16939 event.currentScope = scope; 16940 for (i = 0, length = namedListeners.length; i < length; i++) { 16941 16942 // if listeners were deregistered, defragment the array 16943 if (!namedListeners[i]) { 16944 namedListeners.splice(i, 1); 16945 i--; 16946 length--; 16947 continue; 16948 } 16949 try { 16950 //allow all listeners attached to the current scope to run 16951 namedListeners[i].apply(null, listenerArgs); 16952 } catch (e) { 16953 $exceptionHandler(e); 16954 } 16955 } 16956 //if any listener on the current scope stops propagation, prevent bubbling 16957 if (stopPropagation) { 16958 event.currentScope = null; 16959 return event; 16960 } 16961 //traverse upwards 16962 scope = scope.$parent; 16963 } while (scope); 16964 16965 event.currentScope = null; 16966 16967 return event; 16968 }, 16969 16970 16971 /** 16972 * @ngdoc method 16973 * @name $rootScope.Scope#$broadcast 16974 * @kind function 16975 * 16976 * @description 16977 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 16978 * registered {@link ng.$rootScope.Scope#$on} listeners. 16979 * 16980 * The event life cycle starts at the scope on which `$broadcast` was called. All 16981 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16982 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 16983 * scope and calls all registered listeners along the way. The event cannot be canceled. 16984 * 16985 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16986 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16987 * 16988 * @param {string} name Event name to broadcast. 16989 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16990 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 16991 */ 16992 $broadcast: function(name, args) { 16993 var target = this, 16994 current = target, 16995 next = target, 16996 event = { 16997 name: name, 16998 targetScope: target, 16999 preventDefault: function() { 17000 event.defaultPrevented = true; 17001 }, 17002 defaultPrevented: false 17003 }; 17004 17005 if (!target.$$listenerCount[name]) return event; 17006 17007 var listenerArgs = concat([event], arguments, 1), 17008 listeners, i, length; 17009 17010 //down while you can, then up and next sibling or up and next sibling until back at root 17011 while ((current = next)) { 17012 event.currentScope = current; 17013 listeners = current.$$listeners[name] || []; 17014 for (i = 0, length = listeners.length; i < length; i++) { 17015 // if listeners were deregistered, defragment the array 17016 if (!listeners[i]) { 17017 listeners.splice(i, 1); 17018 i--; 17019 length--; 17020 continue; 17021 } 17022 17023 try { 17024 listeners[i].apply(null, listenerArgs); 17025 } catch (e) { 17026 $exceptionHandler(e); 17027 } 17028 } 17029 17030 // Insanity Warning: scope depth-first traversal 17031 // yes, this code is a bit crazy, but it works and we have tests to prove it! 17032 // this piece should be kept in sync with the traversal in $digest 17033 // (though it differs due to having the extra check for $$listenerCount) 17034 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 17035 (current !== target && current.$$nextSibling)))) { 17036 while (current !== target && !(next = current.$$nextSibling)) { 17037 current = current.$parent; 17038 } 17039 } 17040 } 17041 17042 event.currentScope = null; 17043 return event; 17044 } 17045 }; 17046 17047 var $rootScope = new Scope(); 17048 17049 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 17050 var asyncQueue = $rootScope.$$asyncQueue = []; 17051 var postDigestQueue = $rootScope.$$postDigestQueue = []; 17052 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 17053 17054 return $rootScope; 17055 17056 17057 function beginPhase(phase) { 17058 if ($rootScope.$$phase) { 17059 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 17060 } 17061 17062 $rootScope.$$phase = phase; 17063 } 17064 17065 function clearPhase() { 17066 $rootScope.$$phase = null; 17067 } 17068 17069 function incrementWatchersCount(current, count) { 17070 do { 17071 current.$$watchersCount += count; 17072 } while ((current = current.$parent)); 17073 } 17074 17075 function decrementListenerCount(current, count, name) { 17076 do { 17077 current.$$listenerCount[name] -= count; 17078 17079 if (current.$$listenerCount[name] === 0) { 17080 delete current.$$listenerCount[name]; 17081 } 17082 } while ((current = current.$parent)); 17083 } 17084 17085 /** 17086 * function used as an initial value for watchers. 17087 * because it's unique we can easily tell it apart from other values 17088 */ 17089 function initWatchVal() {} 17090 17091 function flushApplyAsync() {
vendor: 6,136 bytes, lines 17092-17252
17092 while (applyAsyncQueue.length) { 17093 try { 17094 applyAsyncQueue.shift()(); 17095 } catch (e) { 17096 $exceptionHandler(e); 17097 } 17098 } 17099 applyAsyncId = null; 17100 } 17101 17102 function scheduleApplyAsync() { 17103 if (applyAsyncId === null) { 17104 applyAsyncId = $browser.defer(function() { 17105 $rootScope.$apply(flushApplyAsync); 17106 }); 17107 } 17108 } 17109 }]; 17110} 17111 17112/** 17113 * @ngdoc service 17114 * @name $rootElement 17115 * 17116 * @description 17117 * The root element of Angular application. This is either the element where {@link 17118 * ng.directive:ngApp ngApp} was declared or the element passed into 17119 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 17120 * location where the application's {@link auto.$injector $injector} service gets 17121 * published, and can be retrieved using `$rootElement.injector()`. 17122 */ 17123 17124 17125// the implementation is in angular.bootstrap 17126 17127/** 17128 * @description 17129 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 17130 */ 17131function $$SanitizeUriProvider() { 17132 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 17133 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 17134 17135 /** 17136 * @description 17137 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 17138 * urls during a[href] sanitization. 17139 * 17140 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 17141 * 17142 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 17143 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 17144 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 17145 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 17146 * 17147 * @param {RegExp=} regexp New regexp to whitelist urls with. 17148 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 17149 * chaining otherwise. 17150 */ 17151 this.aHrefSanitizationWhitelist = function(regexp) { 17152 if (isDefined(regexp)) { 17153 aHrefSanitizationWhitelist = regexp; 17154 return this; 17155 } 17156 return aHrefSanitizationWhitelist; 17157 }; 17158 17159 17160 /** 17161 * @description 17162 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 17163 * urls during img[src] sanitization. 17164 * 17165 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 17166 * 17167 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 17168 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 17169 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 17170 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 17171 * 17172 * @param {RegExp=} regexp New regexp to whitelist urls with. 17173 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 17174 * chaining otherwise. 17175 */ 17176 this.imgSrcSanitizationWhitelist = function(regexp) { 17177 if (isDefined(regexp)) { 17178 imgSrcSanitizationWhitelist = regexp; 17179 return this; 17180 } 17181 return imgSrcSanitizationWhitelist; 17182 }; 17183 17184 this.$get = function() { 17185 return function sanitizeUri(uri, isImage) { 17186 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 17187 var normalizedVal; 17188 normalizedVal = urlResolve(uri).href; 17189 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 17190 return 'unsafe:' + normalizedVal; 17191 } 17192 return uri; 17193 }; 17194 }; 17195} 17196 17197/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 17198 * Any commits to this file should be reviewed with security in mind. * 17199 * Changes to this file can potentially create security vulnerabilities. * 17200 * An approval from 2 Core members with history of modifying * 17201 * this file is required. * 17202 * * 17203 * Does the change somehow allow for arbitrary javascript to be executed? * 17204 * Or allows for someone to change the prototype of built-in objects? * 17205 * Or gives undesired access to variables likes document or window? * 17206 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 17207 17208var $sceMinErr = minErr('$sce'); 17209 17210var SCE_CONTEXTS = { 17211 HTML: 'html', 17212 CSS: 'css', 17213 URL: 'url', 17214 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 17215 // url. (e.g. ng-include, script src, templateUrl) 17216 RESOURCE_URL: 'resourceUrl', 17217 JS: 'js' 17218}; 17219 17220// Helper functions follow. 17221 17222function adjustMatcher(matcher) { 17223 if (matcher === 'self') { 17224 return matcher; 17225 } else if (isString(matcher)) { 17226 // Strings match exactly except for 2 wildcards - '*' and '**'. 17227 // '*' matches any character except those from the set ':/.?&'. 17228 // '**' matches any character (like .* in a RegExp). 17229 // More than 2 *'s raises an error as it's ill defined. 17230 if (matcher.indexOf('***') > -1) { 17231 throw $sceMinErr('iwcard', 17232 'Illegal sequence *** in string matcher. String: {0}', matcher); 17233 } 17234 matcher = escapeForRegexp(matcher). 17235 replace('\\*\\*', '.*'). 17236 replace('\\*', '[^:/.?&;]*'); 17237 return new RegExp('^' + matcher + '$'); 17238 } else if (isRegExp(matcher)) { 17239 // The only other type of matcher allowed is a Regexp. 17240 // Match entire URL / disallow partial matches. 17241 // Flags are reset (i.e. no global, ignoreCase or multiline) 17242 return new RegExp('^' + matcher.source + '$'); 17243 } else { 17244 throw $sceMinErr('imatcher', 17245 'Matchers may only be "self", string patterns or RegExp objects'); 17246 } 17247} 17248 17249 17250function adjustMatchers(matchers) { 17251 var adjustedMatchers = []; 17252 if (isDefined(matchers)) {
17253 forEach(matchers, function(matcher) { 17254 adjustedMatchers.push(adjustMatcher(matcher)); 17255 }); 17256 } 17257 return adjustedMatchers; 17258} 17259 17260 17261/** 17262 * @ngdoc service 17263 * @name $sceDelegate 17264 * @kind function 17265 * 17266 * @description 17267 * 17268 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 17269 * Contextual Escaping (SCE)} services to AngularJS. 17270 * 17271 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 17272 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 17273 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 17274 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 17275 * work because `$sce` delegates to `$sceDelegate` for these operations. 17276 * 17277 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 17278 * 17279 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 17280 * can override it completely to change the behavior of `$sce`, the common case would 17281 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 17282 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 17283 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 17284 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 17285 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 17286 */ 17287 17288/** 17289 * @ngdoc provider 17290 * @name $sceDelegateProvider 17291 * @description 17292 * 17293 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 17294 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 17295 * that the URLs used for sourcing Angular templates are safe. Refer {@link 17296 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 17297 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 17298 * 17299 * For the general details about this service in Angular, read the main page for {@link ng.$sce 17300 * Strict Contextual Escaping (SCE)}. 17301 * 17302 * **Example**: Consider the following case. <a name="example"></a> 17303 * 17304 * - your app is hosted at url `http://myapp.example.com/` 17305 * - but some of your templates are hosted on other domains you control such as 17306 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 17307 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 17308 * 17309 * Here is what a secure configuration for this scenario might look like: 17310 * 17311 * ``` 17312 * angular.module('myApp', []).config(function($sceDelegateProvider) { 17313 * $sceDelegateProvider.resourceUrlWhitelist([ 17314 * // Allow same origin resource loads. 17315 * 'self', 17316 * // Allow loading from our assets domain. Notice the difference between * and **. 17317 * 'http://srv*.assets.example.com/**' 17318 * ]); 17319 * 17320 * // The blacklist overrides the whitelist so the open redirect here is blocked. 17321 * $sceDelegateProvider.resourceUrlBlacklist([ 17322 * 'http://myapp.example.com/clickThru**' 17323 * ]); 17324 * }); 17325 * ``` 17326 */ 17327 17328function $SceDelegateProvider() { 17329 this.SCE_CONTEXTS = SCE_CONTEXTS; 17330 17331 // Resource URLs can also be trusted by policy. 17332 var resourceUrlWhitelist = ['self'], 17333 resourceUrlBlacklist = []; 17334 17335 /** 17336 * @ngdoc method 17337 * @name $sceDelegateProvider#resourceUrlWhitelist 17338 * @kind function 17339 * 17340 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 17341 * provided. This must be an array or null. A snapshot of this array is used so further 17342 * changes to the array are ignored. 17343 * 17344 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 17345 * allowed in this array. 17346 * 17347 * Note: **an empty whitelist array will block all URLs**! 17348 * 17349 * @return {Array} the currently set whitelist array. 17350 * 17351 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 17352 * same origin resource requests. 17353 * 17354 * @description 17355 * Sets/Gets the whitelist of trusted resource URLs. 17356 */ 17357 this.resourceUrlWhitelist = function(value) { 17358 if (arguments.length) { 17359 resourceUrlWhitelist = adjustMatchers(value); 17360 } 17361 return resourceUrlWhitelist; 17362 }; 17363 17364 /** 17365 * @ngdoc method 17366 * @name $sceDelegateProvider#resourceUrlBlacklist 17367 * @kind function 17368 * 17369 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 17370 * provided. This must be an array or null. A snapshot of this array is used so further 17371 * changes to the array are ignored. 17372 * 17373 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 17374 * allowed in this array. 17375 *
17376 * The typical usage for the blacklist is to **block 17377 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 17378 * these would otherwise be trusted but actually return content from the redirected domain. 17379 * 17380 * Finally, **the blacklist overrides the whitelist** and has the final say. 17381 * 17382 * @return {Array} the currently set blacklist array. 17383 * 17384 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 17385 * is no blacklist.) 17386 * 17387 * @description 17388 * Sets/Gets the blacklist of trusted resource URLs. 17389 */ 17390 17391 this.resourceUrlBlacklist = function(value) { 17392 if (arguments.length) { 17393 resourceUrlBlacklist = adjustMatchers(value); 17394 } 17395 return resourceUrlBlacklist; 17396 }; 17397 17398 this.$get = ['$injector', function($injector) { 17399 17400 var htmlSanitizer = function htmlSanitizer(html) { 17401 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 17402 }; 17403 17404 if ($injector.has('$sanitize')) { 17405 htmlSanitizer = $injector.get('$sanitize'); 17406 } 17407 17408 17409 function matchUrl(matcher, parsedUrl) { 17410 if (matcher === 'self') { 17411 return urlIsSameOrigin(parsedUrl); 17412 } else { 17413 // definitely a regex. See adjustMatchers() 17414 return !!matcher.exec(parsedUrl.href); 17415 } 17416 } 17417 17418 function isResourceUrlAllowedByPolicy(url) { 17419 var parsedUrl = urlResolve(url.toString()); 17420 var i, n, allowed = false; 17421 // Ensure that at least one item from the whitelist allows this url. 17422 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 17423 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 17424 allowed = true; 17425 break; 17426 } 17427 } 17428 if (allowed) { 17429 // Ensure that no item from the blacklist blocked this url. 17430 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 17431 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 17432 allowed = false; 17433 break; 17434 } 17435 } 17436 } 17437 return allowed; 17438 } 17439 17440 function generateHolderType(Base) { 17441 var holderType = function TrustedValueHolderType(trustedValue) { 17442 this.$$unwrapTrustedValue = function() { 17443 return trustedValue; 17444 }; 17445 }; 17446 if (Base) { 17447 holderType.prototype = new Base(); 17448 } 17449 holderType.prototype.valueOf = function sceValueOf() { 17450 return this.$$unwrapTrustedValue(); 17451 }; 17452 holderType.prototype.toString = function sceToString() { 17453 return this.$$unwrapTrustedValue().toString(); 17454 }; 17455 return holderType; 17456 } 17457 17458 var trustedValueHolderBase = generateHolderType(), 17459 byType = {}; 17460 17461 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 17462 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 17463 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 17464 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 17465 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 17466 17467 /** 17468 * @ngdoc method 17469 * @name $sceDelegate#trustAs 17470 * 17471 * @description 17472 * Returns an object that is trusted by angular for use in specified strict 17473 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 17474 * attribute interpolation, any dom event binding attribute interpolation 17475 * such as for onclick, etc.) that uses the provided value. 17476 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 17477 * 17478 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17479 * resourceUrl, html, js and css. 17480 * @param {*} value The value that that should be considered trusted/safe. 17481 * @returns {*} A value that can be used to stand in for the provided `value` in places 17482 * where Angular expects a $sce.trustAs() return value. 17483 */ 17484 function trustAs(type, trustedValue) { 17485 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 17486 if (!Constructor) { 17487 throw $sceMinErr('icontext',
vendor: 5,338 bytes, lines 17488-17616
17488 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 17489 type, trustedValue); 17490 } 17491 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 17492 return trustedValue; 17493 } 17494 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 17495 // mutable objects, we ensure here that the value passed in is actually a string. 17496 if (typeof trustedValue !== 'string') { 17497 throw $sceMinErr('itype', 17498 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 17499 type); 17500 } 17501 return new Constructor(trustedValue); 17502 } 17503 17504 /** 17505 * @ngdoc method 17506 * @name $sceDelegate#valueOf 17507 * 17508 * @description 17509 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 17510 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 17511 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 17512 * 17513 * If the passed parameter is not a value that had been returned by {@link 17514 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 17515 * 17516 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 17517 * call or anything else. 17518 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 17519 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 17520 * `value` unchanged. 17521 */ 17522 function valueOf(maybeTrusted) { 17523 if (maybeTrusted instanceof trustedValueHolderBase) { 17524 return maybeTrusted.$$unwrapTrustedValue(); 17525 } else { 17526 return maybeTrusted; 17527 } 17528 } 17529 17530 /** 17531 * @ngdoc method 17532 * @name $sceDelegate#getTrusted 17533 * 17534 * @description 17535 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 17536 * returns the originally supplied value if the queried context type is a supertype of the 17537 * created type. If this condition isn't satisfied, throws an exception. 17538 * 17539 * <div class="alert alert-danger"> 17540 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 17541 * (XSS) vulnerability in your application. 17542 * </div> 17543 * 17544 * @param {string} type The kind of context in which this value is to be used. 17545 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 17546 * `$sceDelegate.trustAs`} call. 17547 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 17548 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 17549 */ 17550 function getTrusted(type, maybeTrusted) { 17551 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 17552 return maybeTrusted; 17553 } 17554 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 17555 if (constructor && maybeTrusted instanceof constructor) { 17556 return maybeTrusted.$$unwrapTrustedValue(); 17557 } 17558 // If we get here, then we may only take one of two actions. 17559 // 1. sanitize the value for the requested type, or 17560 // 2. throw an exception. 17561 if (type === SCE_CONTEXTS.RESOURCE_URL) { 17562 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 17563 return maybeTrusted; 17564 } else { 17565 throw $sceMinErr('insecurl', 17566 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 17567 maybeTrusted.toString()); 17568 } 17569 } else if (type === SCE_CONTEXTS.HTML) { 17570 return htmlSanitizer(maybeTrusted); 17571 } 17572 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 17573 } 17574 17575 return { trustAs: trustAs, 17576 getTrusted: getTrusted, 17577 valueOf: valueOf }; 17578 }]; 17579} 17580 17581 17582/** 17583 * @ngdoc provider 17584 * @name $sceProvider 17585 * @description 17586 * 17587 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 17588 * - enable/disable Strict Contextual Escaping (SCE) in a module 17589 * - override the default implementation with a custom delegate 17590 * 17591 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 17592 */ 17593 17594/* jshint maxlen: false*/ 17595 17596/** 17597 * @ngdoc service 17598 * @name $sce 17599 * @kind function 17600 * 17601 * @description 17602 * 17603 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 17604 * 17605 * # Strict Contextual Escaping 17606 * 17607 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 17608 * contexts to result in a value that is marked as safe to use for that context. One example of 17609 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 17610 * to these contexts as privileged or SCE contexts. 17611 * 17612 * As of version 1.2, Angular ships with SCE enabled by default. 17613 * 17614 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 17615 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 17616 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17617 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 17618 * to the top of your HTML document. 17619 * 17620 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 17621 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 17622 * 17623 * Here's an example of a binding in a privileged context: 17624 * 17625 * ``` 17626 * <input ng-model="userHtml" aria-label="User input"> 17627 * <div ng-bind-html="userHtml"></div> 17628 * ``` 17629 * 17630 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 17631 * disabled, this application allows the user to render arbitrary HTML into the DIV. 17632 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 17633 * bindings. (HTML is just one example of a context where rendering user controlled input creates 17634 * security vulnerabilities.) 17635 * 17636 * For the case of HTML, you might use a library, either on the client side, or on the server side, 17637 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 17638 * 17639 * How would you ensure that every place that used these types of bindings was bound to a value that 17640 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 17641 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 17642 * properties/fields and forgot to update the binding to the sanitized value? 17643 * 17644 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 17645 * determine that something explicitly says it's safe to use a value for binding in that
17646 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 17647 * for those values that you can easily tell are safe - because they were received from your server, 17648 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 17649 * allowing only the files in a specific directory to do this. Ensuring that the internal API 17650 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 17651 * 17652 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 17653 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 17654 * obtain values that will be accepted by SCE / privileged contexts. 17655 * 17656 * 17657 * ## How does it work? 17658 * 17659 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 17660 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 17661 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 17662 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 17663 * 17664 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 17665 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 17666 * simplified): 17667 * 17668 * ``` 17669 * var ngBindHtmlDirective = ['$sce', function($sce) { 17670 * return function(scope, element, attr) { 17671 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 17672 * element.html(value || ''); 17673 * }); 17674 * }; 17675 * }]; 17676 * ``` 17677 * 17678 * ## Impact on loading templates 17679 * 17680 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 17681 * `templateUrl`'s specified by {@link guide/directive directives}. 17682 * 17683 * By default, Angular only loads templates from the same domain and protocol as the application 17684 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 17685 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 17686 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 17687 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 17688 * 17689 * *Please note*: 17690 * The browser's 17691 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 17692 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 17693 * policy apply in addition to this and may further restrict whether the template is successfully 17694 * loaded. This means that without the right CORS policy, loading templates from a different domain 17695 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 17696 * browsers. 17697 * 17698 * ## This feels like too much overhead 17699 * 17700 * It's important to remember that SCE only applies to interpolation expressions. 17701 * 17702 * If your expressions are constant literals, they're automatically trusted and you don't need to 17703 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 17704 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 17705 * 17706 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 17707 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 17708 * 17709 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 17710 * templates in `ng-include` from your application's domain without having to even know about SCE. 17711 * It blocks loading templates from other domains or loading templates over http from an https 17712 * served document. You can change these by setting your own custom {@link 17713 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 17714 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 17715 * 17716 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 17717 * application that's secure and can be audited to verify that with much more ease than bolting 17718 * security onto an application later. 17719 * 17720 * <a name="contexts"></a> 17721 * ## What trusted context types are supported? 17722 * 17723 * | Context | Notes | 17724 * |---------------------|----------------| 17725 * | `$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. | 17726 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 17727 * | `$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. | 17728 * | `$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. | 17729 * | `$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. | 17730 * 17731 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 17732 * 17733 * Each element in these arrays must be one of the following: 17734 * 17735 * - **'self'** 17736 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 17737 * domain** as the application document using the **same protocol**. 17738 * - **String** (except the special value `'self'`) 17739 * - The string is matched against the full *normalized / absolute URL* of the resource 17740 * being tested (substring matches are not good enough.) 17741 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 17742 * match themselves. 17743 * - `*`: matches zero or more occurrences of any character other than one of the following 6 17744 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17745 * in a whitelist. 17746 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 17747 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 17748 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 17749 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 17750 * http://foo.example.com/templates/**). 17751 * - **RegExp** (*see caveat below*) 17752 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 17753 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 17754 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 17755 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 17756 * small number of cases. A `.` character in the regex used when matching the scheme or a 17757 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 17758 * is highly recommended to use the string patterns and only fall back to regular expressions 17759 * as a last resort. 17760 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 17761 * matched against the **entire** *normalized / absolute URL* of the resource being tested 17762 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 17763 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 17764 * - If you are generating your JavaScript from some other templating engine (not 17765 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 17766 * remember to escape your regular expression (and be aware that you might need more than 17767 * one level of escaping depending on your templating engine and the way you interpolated 17768 * the value.) Do make use of your platform's escaping mechanism as it might be good 17769 * enough before coding your own. E.g. Ruby has 17770 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 17771 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 17772 * Javascript lacks a similar built in function for escaping. Take a look at Google 17773 * Closure library's [goog.string.regExpEscape(s)]( 17774 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 17775 * 17776 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 17777 * 17778 * ## Show me an example using SCE. 17779 * 17780 * <example module="mySceApp" deps="angular-sanitize.js"> 17781 * <file name="index.html"> 17782 * <div ng-controller="AppController as myCtrl"> 17783 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 17784 * <b>User comments</b><br> 17785 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 17786 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 17787 * exploit. 17788 * <div class="well"> 17789 * <div ng-repeat="userComment in myCtrl.userComments"> 17790 * <b>{{userComment.name}}</b>: 17791 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 17792 * <br> 17793 * </div> 17794 * </div> 17795 * </div> 17796 * </file> 17797 * 17798 * <file name="script.js"> 17799 * angular.module('mySceApp', ['ngSanitize']) 17800 * .controller('AppController', ['$http', '$templateCache', '$sce', 17801 * function($http, $templateCache, $sce) { 17802 * var self = this; 17803 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 17804 * self.userComments = userComments; 17805 * }); 17806 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 17807 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17808 * 'sanitization."">Hover over this text.</span>'); 17809 * }]); 17810 * </file> 17811 * 17812 * <file name="test_data.json"> 17813 * [ 17814 * { "name": "Alice", 17815 * "htmlComment": 17816 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 17817 * }, 17818 * { "name": "Bob",
17819 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 17820 * } 17821 * ] 17822 * </file> 17823 * 17824 * <file name="protractor.js" type="protractor"> 17825 * describe('SCE doc demo', function() { 17826 * it('should sanitize untrusted values', function() { 17827 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 17828 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 17829 * }); 17830 * 17831 * it('should NOT sanitize explicitly trusted values', function() { 17832 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 17833 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17834 * 'sanitization."">Hover over this text.</span>'); 17835 * }); 17836 * }); 17837 * </file> 17838 * </example> 17839 * 17840 * 17841 * 17842 * ## Can I disable SCE completely? 17843 * 17844 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 17845 * for little coding overhead. It will be much harder to take an SCE disabled application and 17846 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 17847 * for cases where you have a lot of existing code that was written before SCE was introduced and 17848 * you're migrating them a module at a time. 17849 * 17850 * That said, here's how you can completely disable SCE: 17851 * 17852 * ``` 17853 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 17854 * // Completely disable SCE. For demonstration purposes only! 17855 * // Do not use in new projects. 17856 * $sceProvider.enabled(false); 17857 * }); 17858 * ``` 17859 * 17860 */ 17861/* jshint maxlen: 100 */ 17862 17863function $SceProvider() { 17864 var enabled = true; 17865 17866 /** 17867 * @ngdoc method 17868 * @name $sceProvider#enabled 17869 * @kind function 17870 * 17871 * @param {boolean=} value If provided, then enables/disables SCE. 17872 * @return {boolean} true if SCE is enabled, false otherwise. 17873 * 17874 * @description 17875 * Enables/disables SCE and returns the current value. 17876 */ 17877 this.enabled = function(value) { 17878 if (arguments.length) { 17879 enabled = !!value; 17880 } 17881 return enabled; 17882 }; 17883 17884 17885 /* Design notes on the default implementation for SCE. 17886 * 17887 * The API contract for the SCE delegate 17888 * ------------------------------------- 17889 * The SCE delegate object must provide the following 3 methods: 17890 * 17891 * - trustAs(contextEnum, value) 17892 * This method is used to tell the SCE service that the provided value is OK to use in the 17893 * contexts specified by contextEnum. It must return an object that will be accepted by 17894 * getTrusted() for a compatible contextEnum and return this value. 17895 * 17896 * - valueOf(value) 17897 * For values that were not produced by trustAs(), return them as is. For values that were 17898 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 17899 * trustAs is wrapping the given values into some type, this operation unwraps it when given 17900 * such a value. 17901 * 17902 * - getTrusted(contextEnum, value) 17903 * This function should return the a value that is safe to use in the context specified by 17904 * contextEnum or throw and exception otherwise. 17905 * 17906 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 17907 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 17908 * instance, an implementation could maintain a registry of all trusted objects by context. In 17909 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 17910 * return the same object passed in if it was found in the registry under a compatible context or 17911 * throw an exception otherwise. An implementation might only wrap values some of the time based 17912 * on some criteria. getTrusted() might return a value and not throw an exception for special 17913 * constants or objects even if not wrapped. All such implementations fulfill this contract. 17914 * 17915 * 17916 * A note on the inheritance model for SCE contexts 17917 * ------------------------------------------------ 17918 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 17919 * is purely an implementation details. 17920 * 17921 * The contract is simply this: 17922 * 17923 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 17924 * will also succeed. 17925 * 17926 * Inheritance happens to capture this in a natural way. In some future, we
17927 * may not use inheritance anymore. That is OK because no code outside of 17928 * sce.js and sceSpecs.js would need to be aware of this detail. 17929 */ 17930 17931 this.$get = ['$parse', '$sceDelegate', function( 17932 $parse, $sceDelegate) { 17933 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 17934 // the "expression(javascript expression)" syntax which is insecure. 17935 if (enabled && msie < 8) { 17936 throw $sceMinErr('iequirks', 17937 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 17938 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 17939 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 17940 } 17941 17942 var sce = shallowCopy(SCE_CONTEXTS); 17943 17944 /** 17945 * @ngdoc method 17946 * @name $sce#isEnabled 17947 * @kind function 17948 * 17949 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 17950 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 17951 * 17952 * @description 17953 * Returns a boolean indicating if SCE is enabled. 17954 */ 17955 sce.isEnabled = function() { 17956 return enabled; 17957 }; 17958 sce.trustAs = $sceDelegate.trustAs; 17959 sce.getTrusted = $sceDelegate.getTrusted; 17960 sce.valueOf = $sceDelegate.valueOf; 17961 17962 if (!enabled) { 17963 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 17964 sce.valueOf = identity; 17965 } 17966 17967 /** 17968 * @ngdoc method 17969 * @name $sce#parseAs 17970 * 17971 * @description 17972 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 17973 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 17974 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 17975 * *result*)} 17976 * 17977 * @param {string} type The kind of SCE context in which this result will be used. 17978 * @param {string} expression String expression to compile. 17979 * @returns {function(context, locals)} a function which represents the compiled expression: 17980 * 17981 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17982 * are evaluated against (typically a scope object). 17983 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17984 * `context`. 17985 */ 17986 sce.parseAs = function sceParseAs(type, expr) { 17987 var parsed = $parse(expr); 17988 if (parsed.literal && parsed.constant) { 17989 return parsed; 17990 } else { 17991 return $parse(expr, function(value) { 17992 return sce.getTrusted(type, value); 17993 }); 17994 } 17995 }; 17996 17997 /** 17998 * @ngdoc method 17999 * @name $sce#trustAs 18000 * 18001 * @description 18002 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 18003 * returns an object that is trusted by angular for use in specified strict contextual 18004 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 18005 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 18006 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 18007 * escaping. 18008 * 18009 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 18010 * resourceUrl, html, js and css. 18011 * @param {*} value The value that that should be considered trusted/safe. 18012 * @returns {*} A value that can be used to stand in for the provided `value` in places 18013 * where Angular expects a $sce.trustAs() return value. 18014 */ 18015 18016 /** 18017 * @ngdoc method 18018 * @name $sce#trustAsHtml 18019 * 18020 * @description 18021 * Shorthand method. `$sce.trustAsHtml(value)` â 18022 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 18023 * 18024 * @param {*} value The value to trustAs. 18025 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 18026 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 18027 * only accept expressions that are either literal constants or are the 18028 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 18029 */ 18030 18031 /** 18032 * @ngdoc method 18033 * @name $sce#trustAsUrl 18034 *
18035 * @description 18036 * Shorthand method. `$sce.trustAsUrl(value)` â 18037 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 18038 * 18039 * @param {*} value The value to trustAs. 18040 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 18041 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 18042 * only accept expressions that are either literal constants or are the 18043 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 18044 */ 18045 18046 /** 18047 * @ngdoc method 18048 * @name $sce#trustAsResourceUrl 18049 * 18050 * @description 18051 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 18052 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 18053 * 18054 * @param {*} value The value to trustAs. 18055 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 18056 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 18057 * only accept expressions that are either literal constants or are the return 18058 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 18059 */ 18060 18061 /** 18062 * @ngdoc method 18063 * @name $sce#trustAsJs 18064 * 18065 * @description 18066 * Shorthand method. `$sce.trustAsJs(value)` â 18067 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 18068 * 18069 * @param {*} value The value to trustAs. 18070 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 18071 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 18072 * only accept expressions that are either literal constants or are the 18073 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 18074 */ 18075 18076 /** 18077 * @ngdoc method 18078 * @name $sce#getTrusted 18079 * 18080 * @description 18081 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 18082 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 18083 * originally supplied value if the queried context type is a supertype of the created type. 18084 * If this condition isn't satisfied, throws an exception. 18085 * 18086 * @param {string} type The kind of context in which this value is to be used. 18087 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 18088 * call. 18089 * @returns {*} The value the was originally provided to 18090 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 18091 * Otherwise, throws an exception. 18092 */ 18093 18094 /** 18095 * @ngdoc method 18096 * @name $sce#getTrustedHtml 18097 * 18098 * @description 18099 * Shorthand method. `$sce.getTrustedHtml(value)` â 18100 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 18101 * 18102 * @param {*} value The value to pass to `$sce.getTrusted`. 18103 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 18104 */ 18105 18106 /** 18107 * @ngdoc method 18108 * @name $sce#getTrustedCss 18109 * 18110 * @description 18111 * Shorthand method. `$sce.getTrustedCss(value)` â 18112 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 18113 * 18114 * @param {*} value The value to pass to `$sce.getTrusted`. 18115 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 18116 */ 18117 18118 /** 18119 * @ngdoc method 18120 * @name $sce#getTrustedUrl 18121 * 18122 * @description 18123 * Shorthand method. `$sce.getTrustedUrl(value)` â 18124 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 18125 * 18126 * @param {*} value The value to pass to `$sce.getTrusted`. 18127 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 18128 */ 18129 18130 /** 18131 * @ngdoc method 18132 * @name $sce#getTrustedResourceUrl 18133 * 18134 * @description 18135 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 18136 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 18137 * 18138 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 18139 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 18140 */ 18141 18142 /** 18143 * @ngdoc method 18144 * @name $sce#getTrustedJs 18145 *
18146 * @description 18147 * Shorthand method. `$sce.getTrustedJs(value)` â 18148 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 18149 * 18150 * @param {*} value The value to pass to `$sce.getTrusted`. 18151 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 18152 */ 18153 18154 /** 18155 * @ngdoc method 18156 * @name $sce#parseAsHtml 18157 * 18158 * @description 18159 * Shorthand method. `$sce.parseAsHtml(expression string)` â 18160 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 18161 * 18162 * @param {string} expression String expression to compile. 18163 * @returns {function(context, locals)} a function which represents the compiled expression: 18164 * 18165 * * `context` â `{object}` â an object against which any expressions embedded in the strings 18166 * are evaluated against (typically a scope object). 18167 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 18168 * `context`. 18169 */ 18170 18171 /** 18172 * @ngdoc method 18173 * @name $sce#parseAsCss 18174 * 18175 * @description 18176 * Shorthand method. `$sce.parseAsCss(value)` â 18177 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 18178 * 18179 * @param {string} expression String expression to compile. 18180 * @returns {function(context, locals)} a function which represents the compiled expression: 18181 * 18182 * * `context` â `{object}` â an object against which any expressions embedded in the strings 18183 * are evaluated against (typically a scope object). 18184 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 18185 * `context`. 18186 */ 18187 18188 /** 18189 * @ngdoc method 18190 * @name $sce#parseAsUrl 18191 * 18192 * @description 18193 * Shorthand method. `$sce.parseAsUrl(value)` â 18194 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 18195 * 18196 * @param {string} expression String expression to compile. 18197 * @returns {function(context, locals)} a function which represents the compiled expression: 18198 * 18199 * * `context` â `{object}` â an object against which any expressions embedded in the strings 18200 * are evaluated against (typically a scope object). 18201 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 18202 * `context`. 18203 */ 18204 18205 /** 18206 * @ngdoc method 18207 * @name $sce#parseAsResourceUrl 18208 * 18209 * @description 18210 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 18211 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 18212 * 18213 * @param {string} expression String expression to compile. 18214 * @returns {function(context, locals)} a function which represents the compiled expression: 18215 * 18216 * * `context` â `{object}` â an object against which any expressions embedded in the strings 18217 * are evaluated against (typically a scope object). 18218 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 18219 * `context`. 18220 */ 18221 18222 /** 18223 * @ngdoc method 18224 * @name $sce#parseAsJs 18225 * 18226 * @description 18227 * Shorthand method. `$sce.parseAsJs(value)` â 18228 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 18229 * 18230 * @param {string} expression String expression to compile. 18231 * @returns {function(context, locals)} a function which represents the compiled expression: 18232 * 18233 * * `context` â `{object}` â an object against which any expressions embedded in the strings 18234 * are evaluated against (typically a scope object). 18235 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 18236 * `context`. 18237 */ 18238 18239 // Shorthand delegations. 18240 var parse = sce.parseAs, 18241 getTrusted = sce.getTrusted, 18242 trustAs = sce.trustAs; 18243 18244 forEach(SCE_CONTEXTS, function(enumValue, name) { 18245 var lName = lowercase(name); 18246 sce[camelCase("parse_as_" + lName)] = function(expr) { 18247 return parse(enumValue, expr); 18248 }; 18249 sce[camelCase("get_trusted_" + lName)] = function(value) { 18250 return getTrusted(enumValue, value); 18251 }; 18252 sce[camelCase("trust_as_" + lName)] = function(value) { 18253 return trustAs(enumValue, value); 18254 }; 18255 });
18256 18257 return sce; 18258 }]; 18259} 18260 18261/** 18262 * !!! This is an undocumented "private" service !!! 18263 * 18264 * @name $sniffer 18265 * @requires $window 18266 * @requires $document 18267 * 18268 * @property {boolean} history Does the browser support html5 history api ? 18269 * @property {boolean} transitions Does the browser support CSS transition events ? 18270 * @property {boolean} animations Does the browser support CSS animation events ? 18271 * 18272 * @description 18273 * This is very simple implementation of testing browser's features. 18274 */ 18275function $SnifferProvider() { 18276 this.$get = ['$window', '$document', function($window, $document) { 18277 var eventSupport = {}, 18278 android = 18279 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 18280 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 18281 document = $document[0] || {}, 18282 vendorPrefix, 18283 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 18284 bodyStyle = document.body && document.body.style, 18285 transitions = false, 18286 animations = false, 18287 match; 18288 18289 if (bodyStyle) { 18290 for (var prop in bodyStyle) { 18291 if (match = vendorRegex.exec(prop)) { 18292 vendorPrefix = match[0]; 18293 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 18294 break; 18295 } 18296 } 18297 18298 if (!vendorPrefix) { 18299 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 18300 } 18301 18302 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 18303 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 18304 18305 if (android && (!transitions || !animations)) { 18306 transitions = isString(bodyStyle.webkitTransition); 18307 animations = isString(bodyStyle.webkitAnimation); 18308 } 18309 } 18310 18311 18312 return { 18313 // Android has history.pushState, but it does not update location correctly 18314 // so let's not use the history API at all. 18315 // http://code.google.com/p/android/issues/detail?id=17471 18316 // https://github.com/angular/angular.js/issues/904 18317 18318 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 18319 // so let's not use the history API also 18320 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 18321 // jshint -W018 18322 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 18323 // jshint +W018 18324 hasEvent: function(event) { 18325 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 18326 // it. In particular the event is not fired when backspace or delete key are pressed or 18327 // when cut operation is performed. 18328 // IE10+ implements 'input' event but it erroneously fires under various situations, 18329 // e.g. when placeholder changes, or a form is focused. 18330 if (event === 'input' && msie <= 11) return false; 18331 18332 if (isUndefined(eventSupport[event])) { 18333 var divElm = document.createElement('div'); 18334 eventSupport[event] = 'on' + event in divElm; 18335 } 18336 18337 return eventSupport[event]; 18338 }, 18339 csp: csp(), 18340 vendorPrefix: vendorPrefix, 18341 transitions: transitions, 18342 animations: animations, 18343 android: android 18344 }; 18345 }]; 18346} 18347 18348var $compileMinErr = minErr('$compile'); 18349 18350/** 18351 * @ngdoc provider 18352 * @name $templateRequestProvider 18353 * @description 18354 * Used to configure the options passed to the {@link $http} service when making a template request. 18355 * 18356 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 18357 * requesting a template. 18358 */ 18359function $TemplateRequestProvider() { 18360 18361 var httpOptions; 18362 18363 /** 18364 * @ngdoc method 18365 * @name $templateRequestProvider#httpOptions 18366 * @description 18367 * The options to be passed to the {@link $http} service when making the request. 18368 * You can use this to override options such as the "Accept" header for template requests. 18369 * 18370 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 18371 * options if not overridden here. 18372 * 18373 * @param {string=} value new value for the {@link $http} options. 18374 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 18375 */ 18376 this.httpOptions = function(val) { 18377 if (val) { 18378 httpOptions = val; 18379 return this; 18380 } 18381 return httpOptions; 18382 }; 18383 18384 /** 18385 * @ngdoc service 18386 * @name $templateRequest 18387 * 18388 * @description 18389 * The `$templateRequest` service runs security checks then downloads the provided template using 18390 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 18391 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 18392 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 18393 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 18394 * when `tpl` is of type string and `$templateCache` has the matching entry. 18395 * 18396 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 18397 * can configure this via {@link $templateRequestProvider#httpOptions}. 18398 * 18399 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 18400 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 18401 * 18402 * @return {Promise} a promise for the HTTP response data of the given URL. 18403 * 18404 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 18405 */ 18406 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 18407 18408 function handleRequestFn(tpl, ignoreRequestError) { 18409 handleRequestFn.totalPendingRequests++; 18410 18411 // We consider the template cache holds only trusted templates, so 18412 // there's no need to go through whitelisting again for keys that already 18413 // are included in there. This also makes Angular accept any script 18414 // directive, no matter its name. However, we still need to unwrap trusted 18415 // types. 18416 if (!isString(tpl) || !$templateCache.get(tpl)) { 18417 tpl = $sce.getTrustedResourceUrl(tpl); 18418 } 18419 18420 var transformResponse = $http.defaults && $http.defaults.transformResponse; 18421 18422 if (isArray(transformResponse)) { 18423 transformResponse = transformResponse.filter(function(transformer) { 18424 return transformer !== defaultHttpResponseTransform; 18425 }); 18426 } else if (transformResponse === defaultHttpResponseTransform) { 18427 transformResponse = null; 18428 } 18429 18430 return $http.get(tpl, extend({ 18431 cache: $templateCache, 18432 transformResponse: transformResponse 18433 }, httpOptions)) 18434 ['finally'](function() { 18435 handleRequestFn.totalPendingRequests--; 18436 }) 18437 .then(function(response) { 18438 $templateCache.put(tpl, response.data); 18439 return response.data; 18440 }, handleError); 18441 18442 function handleError(resp) { 18443 if (!ignoreRequestError) { 18444 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 18445 tpl, resp.status, resp.statusText); 18446 } 18447 return $q.reject(resp); 18448 } 18449 } 18450 18451 handleRequestFn.totalPendingRequests = 0; 18452 18453 return handleRequestFn; 18454 }]; 18455} 18456
vendor: 5,580 bytes, lines 18457-18610
18457function $$TestabilityProvider() { 18458 this.$get = ['$rootScope', '$browser', '$location', 18459 function($rootScope, $browser, $location) { 18460 18461 /** 18462 * @name $testability 18463 * 18464 * @description 18465 * The private $$testability service provides a collection of methods for use when debugging 18466 * or by automated test and debugging tools. 18467 */ 18468 var testability = {}; 18469 18470 /** 18471 * @name $$testability#findBindings 18472 * 18473 * @description 18474 * Returns an array of elements that are bound (via ng-bind or {{}}) 18475 * to expressions matching the input. 18476 * 18477 * @param {Element} element The element root to search from. 18478 * @param {string} expression The binding expression to match. 18479 * @param {boolean} opt_exactMatch If true, only returns exact matches 18480 * for the expression. Filters and whitespace are ignored. 18481 */ 18482 testability.findBindings = function(element, expression, opt_exactMatch) { 18483 var bindings = element.getElementsByClassName('ng-binding'); 18484 var matches = []; 18485 forEach(bindings, function(binding) { 18486 var dataBinding = angular.element(binding).data('$binding'); 18487 if (dataBinding) { 18488 forEach(dataBinding, function(bindingName) { 18489 if (opt_exactMatch) { 18490 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 18491 if (matcher.test(bindingName)) { 18492 matches.push(binding); 18493 } 18494 } else { 18495 if (bindingName.indexOf(expression) != -1) { 18496 matches.push(binding); 18497 } 18498 } 18499 }); 18500 } 18501 }); 18502 return matches; 18503 }; 18504 18505 /** 18506 * @name $$testability#findModels 18507 * 18508 * @description 18509 * Returns an array of elements that are two-way found via ng-model to 18510 * expressions matching the input. 18511 * 18512 * @param {Element} element The element root to search from. 18513 * @param {string} expression The model expression to match. 18514 * @param {boolean} opt_exactMatch If true, only returns exact matches 18515 * for the expression. 18516 */ 18517 testability.findModels = function(element, expression, opt_exactMatch) { 18518 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 18519 for (var p = 0; p < prefixes.length; ++p) { 18520 var attributeEquals = opt_exactMatch ? '=' : '*='; 18521 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 18522 var elements = element.querySelectorAll(selector); 18523 if (elements.length) { 18524 return elements; 18525 } 18526 } 18527 }; 18528 18529 /** 18530 * @name $$testability#getLocation 18531 * 18532 * @description 18533 * Shortcut for getting the location in a browser agnostic way. Returns 18534 * the path, search, and hash. (e.g. /path?a=b#hash) 18535 */ 18536 testability.getLocation = function() { 18537 return $location.url(); 18538 }; 18539 18540 /** 18541 * @name $$testability#setLocation 18542 * 18543 * @description 18544 * Shortcut for navigating to a location without doing a full page reload. 18545 * 18546 * @param {string} url The location url (path, search and hash, 18547 * e.g. /path?a=b#hash) to go to. 18548 */ 18549 testability.setLocation = function(url) { 18550 if (url !== $location.url()) { 18551 $location.url(url); 18552 $rootScope.$digest(); 18553 } 18554 }; 18555 18556 /** 18557 * @name $$testability#whenStable 18558 * 18559 * @description 18560 * Calls the callback when $timeout and $http requests are completed. 18561 * 18562 * @param {function} callback 18563 */ 18564 testability.whenStable = function(callback) { 18565 $browser.notifyWhenNoOutstandingRequests(callback); 18566 }; 18567 18568 return testability; 18569 }]; 18570} 18571 18572function $TimeoutProvider() { 18573 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 18574 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 18575 18576 var deferreds = {}; 18577 18578 18579 /** 18580 * @ngdoc service 18581 * @name $timeout 18582 * 18583 * @description 18584 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 18585 * block and delegates any exceptions to 18586 * {@link ng.$exceptionHandler $exceptionHandler} service. 18587 * 18588 * The return value of calling `$timeout` is a promise, which will be resolved when 18589 * the delay has passed and the timeout function, if provided, is executed. 18590 * 18591 * To cancel a timeout request, call `$timeout.cancel(promise)`. 18592 * 18593 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 18594 * synchronously flush the queue of deferred functions. 18595 * 18596 * If you only want a promise that will be resolved after some specified delay 18597 * then you can call `$timeout` without the `fn` function. 18598 * 18599 * @param {function()=} fn A function, whose execution should be delayed. 18600 * @param {number=} [delay=0] Delay in milliseconds. 18601 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 18602 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 18603 * @param {...*=} Pass additional parameters to the executed function. 18604 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 18605 * will be resolved with the return value of the `fn` function. 18606 * 18607 */ 18608 function timeout(fn, delay, invokeApply) { 18609 if (!isFunction(fn)) { 18610 invokeApply = delay;
18611 delay = fn; 18612 fn = noop; 18613 } 18614 18615 var args = sliceArgs(arguments, 3), 18616 skipApply = (isDefined(invokeApply) && !invokeApply), 18617 deferred = (skipApply ? $$q : $q).defer(), 18618 promise = deferred.promise, 18619 timeoutId; 18620 18621 timeoutId = $browser.defer(function() { 18622 try { 18623 deferred.resolve(fn.apply(null, args)); 18624 } catch (e) { 18625 deferred.reject(e); 18626 $exceptionHandler(e); 18627 } 18628 finally { 18629 delete deferreds[promise.$$timeoutId]; 18630 } 18631 18632 if (!skipApply) $rootScope.$apply(); 18633 }, delay); 18634 18635 promise.$$timeoutId = timeoutId; 18636 deferreds[timeoutId] = deferred; 18637 18638 return promise; 18639 } 18640 18641 18642 /** 18643 * @ngdoc method 18644 * @name $timeout#cancel 18645 * 18646 * @description 18647 * Cancels a task associated with the `promise`. As a result of this, the promise will be 18648 * resolved with a rejection. 18649 * 18650 * @param {Promise=} promise Promise returned by the `$timeout` function. 18651 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 18652 * canceled. 18653 */ 18654 timeout.cancel = function(promise) { 18655 if (promise && promise.$$timeoutId in deferreds) { 18656 deferreds[promise.$$timeoutId].reject('canceled'); 18657 delete deferreds[promise.$$timeoutId]; 18658 return $browser.defer.cancel(promise.$$timeoutId); 18659 } 18660 return false; 18661 }; 18662 18663 return timeout; 18664 }]; 18665} 18666 18667// NOTE: The usage of window and document instead of $window and $document here is 18668// deliberate. This service depends on the specific behavior of anchor nodes created by the 18669// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 18670// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 18671// doesn't know about mocked locations and resolves URLs to the real document - which is 18672// exactly the behavior needed here. There is little value is mocking these out for this 18673// service. 18674var urlParsingNode = document.createElement("a"); 18675var originUrl = urlResolve(window.location.href); 18676 18677 18678/** 18679 * 18680 * Implementation Notes for non-IE browsers 18681 * ---------------------------------------- 18682 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 18683 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 18684 * URL will be resolved into an absolute URL in the context of the application document. 18685 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 18686 * properties are all populated to reflect the normalized URL. This approach has wide 18687 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 18688 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 18689 * 18690 * Implementation Notes for IE 18691 * --------------------------- 18692 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 18693 * browsers. However, the parsed components will not be set if the URL assigned did not specify 18694 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 18695 * work around that by performing the parsing in a 2nd step by taking a previously normalized 18696 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 18697 * properties such as protocol, hostname, port, etc. 18698 * 18699 * References: 18700 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 18701 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 18702 * http://url.spec.whatwg.org/#urlutils 18703 * https://github.com/angular/angular.js/pull/2902 18704 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 18705 * 18706 * @kind function 18707 * @param {string} url The URL to be parsed. 18708 * @description Normalizes and parses a URL. 18709 * @returns {object} Returns the normalized URL as a dictionary. 18710 * 18711 * | member name | Description | 18712 * |---------------|----------------| 18713 * | href | A normalized version of the provided URL if it was not an absolute URL | 18714 * | protocol | The protocol including the trailing colon | 18715 * | host | The host and port (if the port is non-default) of the normalizedUrl | 18716 * | search | The search params, minus the question mark | 18717 * | hash | The hash string, minus the hash symbol 18718 * | hostname | The hostname 18719 * | port | The port, without ":" 18720 * | pathname | The pathname, beginning with "/" 18721 * 18722 */ 18723function urlResolve(url) { 18724 var href = url; 18725 18726 if (msie) { 18727 // Normalize before parse. Refer Implementation Notes on why this is 18728 // done in two steps on IE. 18729 urlParsingNode.setAttribute("href", href); 18730 href = urlParsingNode.href; 18731 } 18732 18733 urlParsingNode.setAttribute('href', href); 18734 18735 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 18736 return { 18737 href: urlParsingNode.href, 18738 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 18739 host: urlParsingNode.host, 18740 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 18741 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 18742 hostname: urlParsingNode.hostname, 18743 port: urlParsingNode.port, 18744 pathname: (urlParsingNode.pathname.charAt(0) === '/') 18745 ? urlParsingNode.pathname 18746 : '/' + urlParsingNode.pathname 18747 }; 18748} 18749 18750/** 18751 * Parse a request URL and determine whether this is a same-origin request as the application document. 18752 * 18753 * @param {string|object} requestUrl The url of the request as a string that will be resolved 18754 * or a parsed URL object. 18755 * @returns {boolean} Whether the request is for the same origin as the application document. 18756 */ 18757function urlIsSameOrigin(requestUrl) { 18758 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 18759 return (parsed.protocol === originUrl.protocol && 18760 parsed.host === originUrl.host); 18761} 18762 18763/** 18764 * @ngdoc service 18765 * @name $window 18766 * 18767 * @description 18768 * A reference to the browser's `window` object. While `window` 18769 * is globally available in JavaScript, it causes testability problems, because 18770 * it is a global variable. In angular we always refer to it through the 18771 * `$window` service, so it may be overridden, removed or mocked for testing. 18772 * 18773 * Expressions, like the one defined for the `ngClick` directive in the example 18774 * below, are evaluated with respect to the current scope. Therefore, there is 18775 * no risk of inadvertently coding in a dependency on a global value in such an 18776 * expression. 18777 * 18778 * @example 18779 <example module="windowExample"> 18780 <file name="index.html"> 18781 <script> 18782 angular.module('windowExample', [])
18783 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 18784 $scope.greeting = 'Hello, World!'; 18785 $scope.doGreeting = function(greeting) { 18786 $window.alert(greeting); 18787 }; 18788 }]); 18789 </script> 18790 <div ng-controller="ExampleController"> 18791 <input type="text" ng-model="greeting" aria-label="greeting" /> 18792 <button ng-click="doGreeting(greeting)">ALERT</button> 18793 </div> 18794 </file> 18795 <file name="protractor.js" type="protractor"> 18796 it('should display the greeting in the input box', function() { 18797 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 18798 // If we click the button it will block the test runner 18799 // element(':button').click(); 18800 }); 18801 </file> 18802 </example> 18803 */ 18804function $WindowProvider() { 18805 this.$get = valueFn(window); 18806} 18807 18808/** 18809 * @name $$cookieReader 18810 * @requires $document 18811 * 18812 * @description 18813 * This is a private service for reading cookies used by $http and ngCookies 18814 * 18815 * @return {Object} a key/value map of the current cookies 18816 */ 18817function $$CookieReader($document) { 18818 var rawDocument = $document[0] || {}; 18819 var lastCookies = {}; 18820 var lastCookieString = ''; 18821 18822 function safeDecodeURIComponent(str) { 18823 try { 18824 return decodeURIComponent(str); 18825 } catch (e) { 18826 return str; 18827 } 18828 } 18829 18830 return function() { 18831 var cookieArray, cookie, i, index, name; 18832 var currentCookieString = rawDocument.cookie || ''; 18833 18834 if (currentCookieString !== lastCookieString) { 18835 lastCookieString = currentCookieString; 18836 cookieArray = lastCookieString.split('; '); 18837 lastCookies = {}; 18838 18839 for (i = 0; i < cookieArray.length; i++) { 18840 cookie = cookieArray[i]; 18841 index = cookie.indexOf('='); 18842 if (index > 0) { //ignore nameless cookies 18843 name = safeDecodeURIComponent(cookie.substring(0, index)); 18844 // the first value that is seen for a cookie is the most 18845 // specific one. values for the same cookie name that 18846 // follow are for less specific paths. 18847 if (isUndefined(lastCookies[name])) { 18848 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 18849 } 18850 } 18851 } 18852 } 18853 return lastCookies; 18854 }; 18855} 18856 18857$$CookieReader.$inject = ['$document']; 18858 18859function $$CookieReaderProvider() { 18860 this.$get = $$CookieReader; 18861} 18862 18863/* global currencyFilter: true, 18864 dateFilter: true, 18865 filterFilter: true, 18866 jsonFilter: true, 18867 limitToFilter: true, 18868 lowercaseFilter: true, 18869 numberFilter: true, 18870 orderByFilter: true, 18871 uppercaseFilter: true, 18872 */ 18873 18874/** 18875 * @ngdoc provider 18876 * @name $filterProvider 18877 * @description 18878 * 18879 * Filters are just functions which transform input to an output. However filters need to be 18880 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 18881 * annotated with dependencies and is responsible for creating a filter function. 18882 * 18883 * <div class="alert alert-warning"> 18884 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18885 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18886 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18887 * (`myapp_subsection_filterx`). 18888 * </div> 18889 * 18890 * ```js 18891 * // Filter registration 18892 * function MyModule($provide, $filterProvider) { 18893 * // create a service to demonstrate injection (not always needed) 18894 * $provide.value('greet', function(name){ 18895 * return 'Hello ' + name + '!'; 18896 * }); 18897 * 18898 * // register a filter factory which uses the 18899 * // greet service to demonstrate DI. 18900 * $filterProvider.register('greet', function(greet){ 18901 * // return the filter function which uses the greet service 18902 * // to generate salutation 18903 * return function(text) { 18904 * // filters need to be forgiving so check input validity 18905 * return text && greet(text) || text; 18906 * }; 18907 * }); 18908 * } 18909 * ``` 18910 * 18911 * The filter function is registered with the `$injector` under the filter name suffix with 18912 * `Filter`. 18913 * 18914 * ```js 18915 * it('should be the same instance', inject( 18916 * function($filterProvider) { 18917 * $filterProvider.register('reverse', function(){ 18918 * return ...; 18919 * }); 18920 * }, 18921 * function($filter, reverseFilter) { 18922 * expect($filter('reverse')).toBe(reverseFilter); 18923 * }); 18924 * ``` 18925 * 18926 * 18927 * For more information about how angular filters work, and how to create your own filters, see 18928 * {@link guide/filter Filters} in the Angular Developer Guide. 18929 */ 18930 18931/** 18932 * @ngdoc service 18933 * @name $filter 18934 * @kind function 18935 * @description 18936 * Filters are used for formatting data displayed to the user. 18937 * 18938 * The general syntax in templates is as follows: 18939 * 18940 * {{ expression [| filter_name[:parameter_value] ... ] }} 18941 * 18942 * @param {String} name Name of the filter function to retrieve 18943 * @return {Function} the filter function 18944 * @example 18945 <example name="$filter" module="filterExample"> 18946 <file name="index.html"> 18947 <div ng-controller="MainCtrl"> 18948 <h3>{{ originalText }}</h3> 18949 <h3>{{ filteredText }}</h3> 18950 </div> 18951 </file> 18952 18953 <file name="script.js"> 18954 angular.module('filterExample', []) 18955 .controller('MainCtrl', function($scope, $filter) { 18956 $scope.originalText = 'hello'; 18957 $scope.filteredText = $filter('uppercase')($scope.originalText); 18958 }); 18959 </file> 18960 </example> 18961 */ 18962$FilterProvider.$inject = ['$provide']; 18963function $FilterProvider($provide) { 18964 var suffix = 'Filter'; 18965 18966 /** 18967 * @ngdoc method 18968 * @name $filterProvider#register 18969 * @param {string|Object} name Name of the filter function, or an object map of filters where 18970 * the keys are the filter names and the values are the filter factories. 18971 * 18972 * <div class="alert alert-warning"> 18973 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18974 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18975 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18976 * (`myapp_subsection_filterx`). 18977 * </div> 18978 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 18979 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 18980 * of the registered filter instances. 18981 */ 18982 function register(name, factory) { 18983 if (isObject(name)) { 18984 var filters = {};
18985 forEach(name, function(filter, key) { 18986 filters[key] = register(key, filter); 18987 }); 18988 return filters; 18989 } else { 18990 return $provide.factory(name + suffix, factory); 18991 } 18992 } 18993 this.register = register; 18994 18995 this.$get = ['$injector', function($injector) { 18996 return function(name) { 18997 return $injector.get(name + suffix); 18998 }; 18999 }]; 19000 19001 //////////////////////////////////////// 19002 19003 /* global 19004 currencyFilter: false, 19005 dateFilter: false, 19006 filterFilter: false, 19007 jsonFilter: false, 19008 limitToFilter: false, 19009 lowercaseFilter: false, 19010 numberFilter: false, 19011 orderByFilter: false, 19012 uppercaseFilter: false, 19013 */ 19014 19015 register('currency', currencyFilter); 19016 register('date', dateFilter); 19017 register('filter', filterFilter); 19018 register('json', jsonFilter); 19019 register('limitTo', limitToFilter); 19020 register('lowercase', lowercaseFilter); 19021 register('number', numberFilter); 19022 register('orderBy', orderByFilter); 19023 register('uppercase', uppercaseFilter); 19024} 19025 19026/** 19027 * @ngdoc filter 19028 * @name filter 19029 * @kind function 19030 * 19031 * @description 19032 * Selects a subset of items from `array` and returns it as a new array. 19033 * 19034 * @param {Array} array The source array. 19035 * @param {string|Object|function()} expression The predicate to be used for selecting items from 19036 * `array`. 19037 * 19038 * Can be one of: 19039 * 19040 * - `string`: The string is used for matching against the contents of the `array`. All strings or 19041 * objects with string properties in `array` that match this string will be returned. This also 19042 * applies to nested object properties. 19043 * The predicate can be negated by prefixing the string with `!`. 19044 * 19045 * - `Object`: A pattern object can be used to filter specific properties on objects contained 19046 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 19047 * which have property `name` containing "M" and property `phone` containing "1". A special 19048 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 19049 * property of the object or its nested object properties. That's equivalent to the simple 19050 * substring match with a `string` as described above. The predicate can be negated by prefixing 19051 * the string with `!`. 19052 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 19053 * not containing "M". 19054 * 19055 * Note that a named property will match properties on the same level only, while the special 19056 * `$` property will match properties on the same level or deeper. E.g. an array item like 19057 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 19058 * **will** be matched by `{$: 'John'}`. 19059 * 19060 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 19061 * The function is called for each element of the array, with the element, its index, and 19062 * the entire array itself as arguments. 19063 * 19064 * The final result is an array of those elements that the predicate returned true for. 19065 * 19066 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 19067 * determining if the expected value (from the filter expression) and actual value (from 19068 * the object in the array) should be considered a match. 19069 * 19070 * Can be one of: 19071 * 19072 * - `function(actual, expected)`: 19073 * The function will be given the object value and the predicate value to compare and 19074 * should return true if both values should be considered equal. 19075 * 19076 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 19077 * This is essentially strict comparison of expected and actual. 19078 * 19079 * - `false|undefined`: A short hand for a function which will look for a substring match in case 19080 * insensitive way. 19081 * 19082 * Primitive values are converted to strings. Objects are not compared against primitives, 19083 * unless they have a custom `toString` method (e.g. `Date` objects). 19084 * 19085 * @example 19086 <example> 19087 <file name="index.html"> 19088 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 19089 {name:'Mary', phone:'800-BIG-MARY'}, 19090 {name:'Mike', phone:'555-4321'}, 19091 {name:'Adam', phone:'555-5678'}, 19092 {name:'Julie', phone:'555-8765'}, 19093 {name:'Juliette', phone:'555-5678'}]"></div> 19094 19095 <label>Search: <input ng-model="searchText"></label> 19096 <table id="searchTextResults"> 19097 <tr><th>
19097Name</th><th>Phone</th></tr> 19098 <tr ng-repeat="friend in friends | filter:searchText"> 19099 <td>{{friend.name}}</td> 19100 <td>{{friend.phone}}</td> 19101 </tr> 19102 </table> 19103 <hr> 19104 <label>Any: <input ng-model="search.$"></label> <br> 19105 <label>Name only <input ng-model="search.name"></label><br> 19106 <label>Phone only <input ng-model="search.phone"></label><br> 19107 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 19108 <table id="searchObjResults"> 19109 <tr><th>Name</th><th>Phone</th></tr> 19110 <tr ng-repeat="friendObj in friends | filter:search:strict"> 19111 <td>{{friendObj.name}}</td> 19112 <td>{{friendObj.phone}}</td> 19113 </tr> 19114 </table> 19115 </file> 19116 <file name="protractor.js" type="protractor"> 19117 var expectFriendNames = function(expectedNames, key) { 19118 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 19119 arr.forEach(function(wd, i) { 19120 expect(wd.getText()).toMatch(expectedNames[i]); 19121 }); 19122 }); 19123 }; 19124 19125 it('should search across all fields when filtering with a string', function() { 19126 var searchText = element(by.model('searchText')); 19127 searchText.clear(); 19128 searchText.sendKeys('m'); 19129 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 19130 19131 searchText.clear(); 19132 searchText.sendKeys('76'); 19133 expectFriendNames(['John', 'Julie'], 'friend'); 19134 }); 19135 19136 it('should search in specific fields when filtering with a predicate object', function() { 19137 var searchAny = element(by.model('search.$')); 19138 searchAny.clear(); 19139 searchAny.sendKeys('i'); 19140 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 19141 }); 19142 it('should use a equal comparison when comparator is true', function() { 19143 var searchName = element(by.model('search.name')); 19144 var strict = element(by.model('strict')); 19145 searchName.clear(); 19146 searchName.sendKeys('Julie'); 19147 strict.click(); 19148 expectFriendNames(['Julie'], 'friendObj'); 19149 }); 19150 </file> 19151 </example> 19152 */ 19153function filterFilter() { 19154 return function(array, expression, comparator) { 19155 if (!isArrayLike(array)) { 19156 if (array == null) { 19157 return array; 19158 } else { 19159 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 19160 } 19161 } 19162 19163 var expressionType = getTypeForFilter(expression); 19164 var predicateFn; 19165 var matchAgainstAnyProp; 19166 19167 switch (expressionType) { 19168 case 'function': 19169 predicateFn = expression; 19170 break; 19171 case 'boolean': 19172 case 'null': 19173 case 'number': 19174 case 'string': 19175 matchAgainstAnyProp = true; 19176 //jshint -W086 19177 case 'object': 19178 //jshint +W086 19179 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 19180 break; 19181 default: 19182 return array; 19183 } 19184 19185 return Array.prototype.filter.call(array, predicateFn); 19186 }; 19187} 19188 19189// Helper functions for `filterFilter` 19190function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 19191 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 19192 var predicateFn; 19193 19194 if (comparator === true) { 19195 comparator = equals; 19196 } else if (!isFunction(comparator)) { 19197 comparator = function(actual, expected) { 19198 if (isUndefined(actual)) { 19199 // No substring matching against `undefined` 19200 return false; 19201 } 19202 if ((actual === null) || (expected === null)) { 19203 // No substring matching against `null`; only match against `null` 19204 return actual === expected; 19205 } 19206 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 19207 // Should not compare primitives against objects, unless they have custom `toString` method 19208 return false; 19209 } 19210 19211 actual = lowercase('' + actual); 19212 expected = lowercase('' + expected); 19213 return actual.indexOf(expected) !== -1; 19214 }; 19215 } 19216 19217 predicateFn = function(item) { 19218 if (shouldMatchPrimitives && !isObject(item)) { 19219 return deepCompare(item, expression.$, comparator, false); 19220 } 19221 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 19222 }; 19223 19224 return predicateFn; 19225} 19226 19227function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 19228 var actualType = getTypeForFilter(actual); 19229 var expectedType = getTypeForFilter(expected); 19230 19231 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 19232 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 19233 } else if (isArray(actual)) { 19234 // In case `actual` is an array, consider it a match 19235 // if ANY of it's items matches `expected` 19236 return actual.some(function(item) { 19237 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 19238 });
19239 } 19240 19241 switch (actualType) { 19242 case 'object': 19243 var key; 19244 if (matchAgainstAnyProp) { 19245 for (key in actual) { 19246 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 19247 return true; 19248 } 19249 } 19250 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 19251 } else if (expectedType === 'object') { 19252 for (key in expected) { 19253 var expectedVal = expected[key]; 19254 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 19255 continue; 19256 } 19257 19258 var matchAnyProperty = key === '$'; 19259 var actualVal = matchAnyProperty ? actual : actual[key]; 19260 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 19261 return false; 19262 } 19263 } 19264 return true; 19265 } else { 19266 return comparator(actual, expected); 19267 } 19268 break; 19269 case 'function': 19270 return false; 19271 default: 19272 return comparator(actual, expected); 19273 } 19274} 19275 19276// Used for easily differentiating between `null` and actual `object` 19277function getTypeForFilter(val) { 19278 return (val === null) ? 'null' : typeof val; 19279} 19280 19281var MAX_DIGITS = 22; 19282var DECIMAL_SEP = '.'; 19283var ZERO_CHAR = '0'; 19284 19285/** 19286 * @ngdoc filter 19287 * @name currency 19288 * @kind function 19289 * 19290 * @description 19291 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 19292 * symbol for current locale is used. 19293 * 19294 * @param {number} amount Input to filter. 19295 * @param {string=} symbol Currency symbol or identifier to be displayed. 19296 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 19297 * @returns {string} Formatted number. 19298 * 19299 * 19300 * @example 19301 <example module="currencyExample"> 19302 <file name="index.html"> 19303 <script> 19304 angular.module('currencyExample', []) 19305 .controller('ExampleController', ['$scope', function($scope) { 19306 $scope.amount = 1234.56; 19307 }]); 19308 </script>
19309 <div ng-controller="ExampleController"> 19310 <input type="number" ng-model="amount" aria-label="amount"> <br> 19311 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 19312 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 19313 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 19314 </div> 19315 </file> 19316 <file name="protractor.js" type="protractor"> 19317 it('should init with 1234.56', function() { 19318 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 19319 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 19320 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 19321 }); 19322 it('should update', function() { 19323 if (browser.params.browser == 'safari') { 19324 // Safari does not understand the minus key. See 19325 // https://github.com/angular/protractor/issues/481 19326 return; 19327 } 19328 element(by.model('amount')).clear(); 19329 element(by.model('amount')).sendKeys('-1234'); 19330 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 19331 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 19332 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 19333 }); 19334 </file> 19335 </example> 19336 */ 19337currencyFilter.$inject = ['$locale']; 19338function currencyFilter($locale) { 19339 var formats = $locale.NUMBER_FORMATS; 19340 return function(amount, currencySymbol, fractionSize) { 19341 if (isUndefined(currencySymbol)) { 19342 currencySymbol = formats.CURRENCY_SYM; 19343 } 19344 19345 if (isUndefined(fractionSize)) { 19346 fractionSize = formats.PATTERNS[1].maxFrac; 19347 } 19348 19349 // if null or undefined pass it through 19350 return (amount == null) 19351 ? amount 19352 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 19353 replace(/\u00A4/g, currencySymbol); 19354 }; 19355} 19356 19357/** 19358 * @ngdoc filter 19359 * @name number 19360 * @kind function 19361 * 19362 * @description 19363 * Formats a number as text. 19364 * 19365 * If the input is null or undefined, it will just be returned. 19366 * If the input is infinite (Infinity/-Infinity) the Infinity symbol 'â' is returned. 19367 * If the input is not a number an empty string is returned. 19368 * 19369 * 19370 * @param {number|string} number Number to format. 19371 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 19372 * If this is not provided then the fraction size is computed from the current locale's number 19373 * formatting pattern. In the case of the default locale, it will be 3. 19374 * @returns {string} Number rounded to fractionSize and places a â,â after each third digit. 19375 * 19376 * @example 19377 <example module="numberFilterExample"> 19378 <file name="index.html"> 19379 <script> 19380 angular.module('numberFilterExample', []) 19381 .controller('ExampleController', ['$scope', function($scope) { 19382 $scope.val = 1234.56789; 19383 }]); 19384 </script>
19385 <div ng-controller="ExampleController"> 19386 <label>Enter number: <input ng-model='val'></label><br> 19387 Default formatting: <span id='number-default'>{{val | number}}</span><br> 19388 No fractions: <span>{{val | number:0}}</span><br> 19389 Negative number: <span>{{-val | number:4}}</span> 19390 </div> 19391 </file> 19392 <file name="protractor.js" type="protractor"> 19393 it('should format numbers', function() { 19394 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 19395 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 19396 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 19397 }); 19398 19399 it('should update', function() { 19400 element(by.model('val')).clear(); 19401 element(by.model('val')).sendKeys('3374.333'); 19402 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 19403 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 19404 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 19405 }); 19406 </file> 19407 </example> 19408 */ 19409numberFilter.$inject = ['$locale']; 19410function numberFilter($locale) { 19411 var formats = $locale.NUMBER_FORMATS; 19412 return function(number, fractionSize) { 19413 19414 // if null or undefined pass it through 19415 return (number == null) 19416 ? number 19417 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 19418 fractionSize); 19419 }; 19420} 19421 19422/** 19423 * Parse a number (as a string) into three components that can be used 19424 * for formatting the number. 19425 * 19426 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 19427 * 19428 * @param {string} numStr The number to parse 19429 * @return {object} An object describing this number, containing the following keys: 19430 * - d : an array of digits containing leading zeros as necessary 19431 * - i : the number of the digits in `d` that are to the left of the decimal point 19432 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 19433 * 19434 */ 19435function parse(numStr) { 19436 var exponent = 0, digits, numberOfIntegerDigits; 19437 var i, j, zeros; 19438 19439 // Decimal point? 19440 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 19441 numStr = numStr.replace(DECIMAL_SEP, ''); 19442 } 19443 19444 // Exponential form?
vendor: 10,817 bytes, lines 19445-19765
19445 if ((i = numStr.search(/e/i)) > 0) { 19446 // Work out the exponent. 19447 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 19448 numberOfIntegerDigits += +numStr.slice(i + 1); 19449 numStr = numStr.substring(0, i); 19450 } else if (numberOfIntegerDigits < 0) { 19451 // There was no decimal point or exponent so it is an integer. 19452 numberOfIntegerDigits = numStr.length; 19453 } 19454 19455 // Count the number of leading zeros. 19456 for (i = 0; numStr.charAt(i) == ZERO_CHAR; i++); 19457 19458 if (i == (zeros = numStr.length)) { 19459 // The digits are all zero. 19460 digits = [0]; 19461 numberOfIntegerDigits = 1; 19462 } else { 19463 // Count the number of trailing zeros 19464 zeros--; 19465 while (numStr.charAt(zeros) == ZERO_CHAR) zeros--; 19466 19467 // Trailing zeros are insignificant so ignore them 19468 numberOfIntegerDigits -= i; 19469 digits = []; 19470 // Convert string to array of digits without leading/trailing zeros. 19471 for (j = 0; i <= zeros; i++, j++) { 19472 digits[j] = +numStr.charAt(i); 19473 } 19474 } 19475 19476 // If the number overflows the maximum allowed digits then use an exponent. 19477 if (numberOfIntegerDigits > MAX_DIGITS) { 19478 digits = digits.splice(0, MAX_DIGITS - 1); 19479 exponent = numberOfIntegerDigits - 1; 19480 numberOfIntegerDigits = 1; 19481 } 19482 19483 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 19484} 19485 19486/** 19487 * Round the parsed number to the specified number of decimal places 19488 * This function changed the parsedNumber in-place 19489 */ 19490function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 19491 var digits = parsedNumber.d; 19492 var fractionLen = digits.length - parsedNumber.i; 19493 19494 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 19495 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 19496 19497 // The index of the digit to where rounding is to occur 19498 var roundAt = fractionSize + parsedNumber.i; 19499 var digit = digits[roundAt]; 19500 19501 if (roundAt > 0) { 19502 digits.splice(roundAt); 19503 } else { 19504 // We rounded to zero so reset the parsedNumber 19505 parsedNumber.i = 1; 19506 digits.length = roundAt = fractionSize + 1; 19507 for (var i=0; i < roundAt; i++) digits[i] = 0; 19508 } 19509 19510 if (digit >= 5) digits[roundAt - 1]++; 19511 19512 // Pad out with zeros to get the required fraction length 19513 for (; fractionLen < fractionSize; fractionLen++) digits.push(0); 19514 19515 19516 // Do any carrying, e.g. a digit was rounded up to 10 19517 var carry = digits.reduceRight(function(carry, d, i, digits) { 19518 d = d + carry; 19519 digits[i] = d % 10; 19520 return Math.floor(d / 10); 19521 }, 0); 19522 if (carry) { 19523 digits.unshift(carry); 19524 parsedNumber.i++; 19525 } 19526} 19527 19528/** 19529 * Format a number into a string 19530 * @param {number} number The number to format 19531 * @param {{ 19532 * minFrac, // the minimum number of digits required in the fraction part of the number 19533 * maxFrac, // the maximum number of digits required in the fraction part of the number 19534 * gSize, // number of digits in each group of separated digits 19535 * lgSize, // number of digits in the last group of digits before the decimal separator 19536 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 19537 * posPre, // the string to go in front of a positive number 19538 * negSuf, // the string to go after a negative number (e.g. `)`) 19539 * posSuf // the string to go after a positive number 19540 * }} pattern 19541 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 19542 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 19543 * @param {[type]} fractionSize The size of the fractional part of the number 19544 * @return {string} The number formatted as a string 19545 */ 19546function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 19547 19548 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 19549 19550 var isInfinity = !isFinite(number); 19551 var isZero = false; 19552 var numStr = Math.abs(number) + '', 19553 formattedText = '', 19554 parsedNumber; 19555 19556 if (isInfinity) { 19557 formattedText = '\u221e'; 19558 } else { 19559 parsedNumber = parse(numStr); 19560 19561 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 19562 19563 var digits = parsedNumber.d; 19564 var integerLen = parsedNumber.i; 19565 var exponent = parsedNumber.e; 19566 var decimals = []; 19567 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 19568 19569 // pad zeros for small numbers 19570 while (integerLen < 0) { 19571 digits.unshift(0); 19572 integerLen++; 19573 } 19574 19575 // extract decimals digits 19576 if (integerLen > 0) { 19577 decimals = digits.splice(integerLen); 19578 } else { 19579 decimals = digits; 19580 digits = [0]; 19581 } 19582 19583 // format the integer digits with grouping separators 19584 var groups = []; 19585 if (digits.length > pattern.lgSize) { 19586 groups.unshift(digits.splice(-pattern.lgSize).join('')); 19587 } 19588 while (digits.length > pattern.gSize) { 19589 groups.unshift(digits.splice(-pattern.gSize).join('')); 19590 } 19591 if (digits.length) { 19592 groups.unshift(digits.join('')); 19593 } 19594 formattedText = groups.join(groupSep); 19595 19596 // append the decimal digits 19597 if (decimals.length) { 19598 formattedText += decimalSep + decimals.join(''); 19599 } 19600 19601 if (exponent) { 19602 formattedText += 'e+' + exponent; 19603 } 19604 } 19605 if (number < 0 && !isZero) { 19606 return pattern.negPre + formattedText + pattern.negSuf; 19607 } else { 19608 return pattern.posPre + formattedText + pattern.posSuf; 19609 } 19610} 19611 19612function padNumber(num, digits, trim) { 19613 var neg = ''; 19614 if (num < 0) { 19615 neg = '-'; 19616 num = -num; 19617 } 19618 num = '' + num; 19619 while (num.length < digits) num = ZERO_CHAR + num; 19620 if (trim) { 19621 num = num.substr(num.length - digits); 19622 } 19623 return neg + num; 19624} 19625 19626 19627function dateGetter(name, size, offset, trim) { 19628 offset = offset || 0; 19629 return function(date) { 19630 var value = date['get' + name](); 19631 if (offset > 0 || value > -offset) { 19632 value += offset; 19633 } 19634 if (value === 0 && offset == -12) value = 12; 19635 return padNumber(value, size, trim); 19636 }; 19637} 19638 19639function dateStrGetter(name, shortForm) { 19640 return function(date, formats) { 19641 var value = date['get' + name](); 19642 var get = uppercase(shortForm ? ('SHORT' + name) : name); 19643 19644 return formats[get][value]; 19645 }; 19646} 19647 19648function timeZoneGetter(date, formats, offset) { 19649 var zone = -1 * offset; 19650 var paddedZone = (zone >= 0) ? "+" : ""; 19651 19652 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 19653 padNumber(Math.abs(zone % 60), 2); 19654 19655 return paddedZone; 19656} 19657 19658function getFirstThursdayOfYear(year) { 19659 // 0 = index of January 19660 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 19661 // 4 = index of Thursday (+1 to account for 1st = 5) 19662 // 11 = index of *next* Thursday (+1 account for 1st = 12) 19663 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 19664} 19665 19666function getThursdayThisWeek(datetime) { 19667 return new Date(datetime.getFullYear(), datetime.getMonth(), 19668 // 4 = index of Thursday 19669 datetime.getDate() + (4 - datetime.getDay())); 19670} 19671 19672function weekGetter(size) { 19673 return function(date) { 19674 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 19675 thisThurs = getThursdayThisWeek(date); 19676 19677 var diff = +thisThurs - +firstThurs, 19678 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 19679 19680 return padNumber(result, size); 19681 }; 19682} 19683 19684function ampmGetter(date, formats) { 19685 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 19686} 19687 19688function eraGetter(date, formats) { 19689 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 19690} 19691 19692function longEraGetter(date, formats) { 19693 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 19694} 19695 19696var DATE_FORMATS = { 19697 yyyy: dateGetter('FullYear', 4), 19698 yy: dateGetter('FullYear', 2, 0, true), 19699 y: dateGetter('FullYear', 1), 19700 MMMM: dateStrGetter('Month'), 19701 MMM: dateStrGetter('Month', true), 19702 MM: dateGetter('Month', 2, 1), 19703 M: dateGetter('Month', 1, 1), 19704 dd: dateGetter('Date', 2), 19705 d: dateGetter('Date', 1), 19706 HH: dateGetter('Hours', 2), 19707 H: dateGetter('Hours', 1), 19708 hh: dateGetter('Hours', 2, -12), 19709 h: dateGetter('Hours', 1, -12), 19710 mm: dateGetter('Minutes', 2), 19711 m: dateGetter('Minutes', 1), 19712 ss: dateGetter('Seconds', 2), 19713 s: dateGetter('Seconds', 1), 19714 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 19715 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 19716 sss: dateGetter('Milliseconds', 3), 19717 EEEE: dateStrGetter('Day'), 19718 EEE: dateStrGetter('Day', true), 19719 a: ampmGetter, 19720 Z: timeZoneGetter, 19721 ww: weekGetter(2), 19722 w: weekGetter(1), 19723 G: eraGetter, 19724 GG: eraGetter, 19725 GGG: eraGetter, 19726 GGGG: longEraGetter 19727}; 19728 19729var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 19730 NUMBER_STRING = /^\-?\d+$/; 19731 19732/** 19733 * @ngdoc filter 19734 * @name date 19735 * @kind function 19736 * 19737 * @description 19738 * Formats `date` to a string based on the requested `format`. 19739 * 19740 * `format` string can be composed of the following elements: 19741 * 19742 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 19743 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 19744 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 19745 * * `'MMMM'`: Month in year (January-December) 19746 * * `'MMM'`: Month in year (Jan-Dec) 19747 * * `'MM'`: Month in year, padded (01-12) 19748 * * `'M'`: Month in year (1-12) 19749 * * `'dd'`: Day in month, padded (01-31) 19750 * * `'d'`: Day in month (1-31) 19751 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 19752 * * `'EEE'`: Day in Week, (Sun-Sat) 19753 * * `'HH'`: Hour in day, padded (00-23) 19754 * * `'H'`: Hour in day (0-23) 19755 * * `'hh'`: Hour in AM/PM, padded (01-12) 19756 * * `'h'`: Hour in AM/PM, (1-12) 19757 * * `'mm'`: Minute in hour, padded (00-59) 19758 * * `'m'`: Minute in hour (0-59) 19759 * * `'ss'`: Second in minute, padded (00-59) 19760 * * `'s'`: Second in minute (0-59) 19761 * * `'sss'`: Millisecond in second, padded (000-999) 19762 * * `'a'`: AM/PM marker 19763 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 19764 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 19765 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19766 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 19767 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 19768 * 19769 * `format` string can also be one of the following predefined 19770 * {@link guide/i18n localizable formats}: 19771 * 19772 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 19773 * (e.g. Sep 3, 2010 12:05:08 PM) 19774 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 19775 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 19776 * (e.g. Friday, September 3, 2010) 19777 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 19778 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 19779 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 19780 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 19781 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 19782 * 19783 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 19784 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 19785 * (e.g. `"h 'o''clock'"`). 19786 * 19787 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 19788 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 19789 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 19790 * specified in the string input, the time is considered to be in the local timezone. 19791 * @param {string=} format Formatting rules (see Description). If not specified, 19792 * `mediumDate` is used. 19793 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 19794 * continental US time zone abbreviations, but for general use, use a time zone offset, for 19795 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 19796 * If not specified, the timezone of the browser will be used. 19797 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 19798 * 19799 * @example 19800 <example> 19801 <file name="index.html"> 19802 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 19803 <span>{{1288323623006 | date:'medium'}}</span><br> 19804 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 19805 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 19806 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 19807 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 19808 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 19809 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 19810 </file> 19811 <file name="protractor.js" type="protractor"> 19812 it('should format date', function() { 19813 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 19814 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 19815 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 19816 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 19817 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 19818 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 19819 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 19820 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 19821 }); 19822 </file> 19823 </example> 19824 */ 19825dateFilter.$inject = ['$locale']; 19826function dateFilter($locale) { 19827 19828 19829 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 19830 // 1 2 3 4 5 6 7 8 9 10 11 19831 function jsonStringToDate(string) { 19832 var match; 19833 if (match = string.match(R_ISO8601_STR)) { 19834 var date = new Date(0), 19835 tzHour = 0, 19836 tzMin = 0, 19837 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 19838 timeSetter = match[8] ? date.setUTCHours : date.setHours; 19839 19840 if (match[9]) { 19841 tzHour = toInt(match[9] + match[10]); 19842 tzMin = toInt(match[9] + match[11]); 19843 } 19844 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 19845 var h = toInt(match[4] || 0) - tzHour; 19846 var m = toInt(match[5] || 0) - tzMin; 19847 var s = toInt(match[6] || 0); 19848 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 19849 timeSetter.call(date, h, m, s, ms); 19850 return date; 19851 } 19852 return string; 19853 } 19854 19855 19856 return function(date, format, timezone) { 19857 var text = '', 19858 parts = [], 19859 fn, match; 19860 19861 format = format || 'mediumDate'; 19862 format = $locale.DATETIME_FORMATS[format] || format; 19863 if (isString(date)) { 19864 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 19865 } 19866 19867 if (isNumber(date)) { 19868 date = new Date(date); 19869 } 19870 19871 if (!isDate(date) || !isFinite(date.getTime())) { 19872 return date; 19873 } 19874 19875 while (format) { 19876 match = DATE_FORMATS_SPLIT.exec(format); 19877 if (match) { 19878 parts = concat(parts, match, 1); 19879 format = parts.pop(); 19880 } else { 19881 parts.push(format); 19882 format = null; 19883 } 19884 } 19885 19886 var dateTimezoneOffset = date.getTimezoneOffset(); 19887 if (timezone) { 19888 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 19889 date = convertTimezoneToLocal(date, timezone, true); 19890 }
19891 forEach(parts, function(value) { 19892 fn = DATE_FORMATS[value]; 19893 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 19894 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 19895 }); 19896 19897 return text; 19898 }; 19899} 19900 19901 19902/** 19903 * @ngdoc filter 19904 * @name json 19905 * @kind function 19906 * 19907 * @description 19908 * Allows you to convert a JavaScript object into JSON string. 19909 * 19910 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 19911 * the binding is automatically converted to JSON. 19912 * 19913 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 19914 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. 19915 * @returns {string} JSON string. 19916 * 19917 * 19918 * @example 19919 <example> 19920 <file name="index.html"> 19921 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 19922 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 19923 </file> 19924 <file name="protractor.js" type="protractor"> 19925 it('should jsonify filtered objects', function() { 19926 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19927 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19928 }); 19929 </file> 19930 </example> 19931 * 19932 */ 19933function jsonFilter() { 19934 return function(object, spacing) { 19935 if (isUndefined(spacing)) { 19936 spacing = 2; 19937 } 19938 return toJson(object, spacing); 19939 }; 19940} 19941 19942 19943/** 19944 * @ngdoc filter 19945 * @name lowercase 19946 * @kind function 19947 * @description 19948 * Converts string to lowercase. 19949 * @see angular.lowercase 19950 */ 19951var lowercaseFilter = valueFn(lowercase); 19952 19953 19954/** 19955 * @ngdoc filter 19956 * @name uppercase 19957 * @kind function 19958 * @description 19959 * Converts string to uppercase. 19960 * @see angular.uppercase 19961 */ 19962var uppercaseFilter = valueFn(uppercase); 19963 19964/** 19965 * @ngdoc filter 19966 * @name limitTo 19967 * @kind function 19968 * 19969 * @description 19970 * Creates a new array or string containing only a specified number of elements. The elements 19971 * are taken from either the beginning or the end of the source array, string or number, as specified by 19972 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 19973 * converted to a string. 19974 * 19975 * @param {Array|string|number} input Source array, string or number to be limited. 19976 * @param {string|number} limit The length of the returned array or string. If the `limit` number 19977 * is positive, `limit` number of items from the beginning of the source array/string are copied. 19978 * If the number is negative, `limit` number of items from the end of the source array/string 19979 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 19980 * the input will be returned unchanged. 19981 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 19982 * indicates an offset from the end of `input`. Defaults to `0`. 19983 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 19984 * had less than `limit` elements. 19985 * 19986 * @example 19987 <example module="limitToExample"> 19988 <file name="index.html"> 19989 <script> 19990 angular.module('limitToExample', []) 19991 .controller('ExampleController', ['$scope', function($scope) { 19992 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 19993 $scope.letters = "abcdefghi"; 19994 $scope.longNumber = 2345432342; 19995 $scope.numLimit = 3; 19996 $scope.letterLimit = 3; 19997 $scope.longNumberLimit = 3; 19998 }]); 19999 </script>
20000 <div ng-controller="ExampleController"> 20001 <label> 20002 Limit {{numbers}} to: 20003 <input type="number" step="1" ng-model="numLimit"> 20004 </label> 20005 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 20006 <label> 20007 Limit {{letters}} to: 20008 <input type="number" step="1" ng-model="letterLimit"> 20009 </label> 20010 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 20011 <label> 20012 Limit {{longNumber}} to: 20013 <input type="number" step="1" ng-model="longNumberLimit"> 20014 </label> 20015 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 20016 </div> 20017 </file> 20018 <file name="protractor.js" type="protractor"> 20019 var numLimitInput = element(by.model('numLimit')); 20020 var letterLimitInput = element(by.model('letterLimit')); 20021 var longNumberLimitInput = element(by.model('longNumberLimit')); 20022 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 20023 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 20024 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 20025 20026 it('should limit the number array to first three items', function() { 20027 expect(numLimitInput.getAttribute('value')).toBe('3'); 20028 expect(letterLimitInput.getAttribute('value')).toBe('3'); 20029 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 20030 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 20031 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 20032 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 20033 }); 20034 20035 // There is a bug in safari and protractor that doesn't like the minus key 20036 // it('should update the output when -3 is entered', function() { 20037 // numLimitInput.clear(); 20038 // numLimitInput.sendKeys('-3'); 20039 // letterLimitInput.clear(); 20040 // letterLimitInput.sendKeys('-3'); 20041 // longNumberLimitInput.clear(); 20042 // longNumberLimitInput.sendKeys('-3'); 20043 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 20044 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 20045 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 20046 // }); 20047 20048 it('should not exceed the maximum size of input array', function() { 20049 numLimitInput.clear(); 20050 numLimitInput.sendKeys('100'); 20051 letterLimitInput.clear(); 20052 letterLimitInput.sendKeys('100'); 20053 longNumberLimitInput.clear(); 20054 longNumberLimitInput.sendKeys('100'); 20055 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 20056 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 20057 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 20058 }); 20059 </file> 20060 </example> 20061*/ 20062function limitToFilter() { 20063 return function(input, limit, begin) { 20064 if (Math.abs(Number(limit)) === Infinity) { 20065 limit = Number(limit); 20066 } else { 20067 limit = toInt(limit); 20068 } 20069 if (isNaN(limit)) return input; 20070 20071 if (isNumber(input)) input = input.toString(); 20072 if (!isArray(input) && !isString(input)) return input; 20073 20074 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 20075 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 20076 20077 if (limit >= 0) { 20078 return input.slice(begin, begin + limit); 20079 } else { 20080 if (begin === 0) { 20081 return input.slice(limit, input.length); 20082 } else { 20083 return input.slice(Math.max(0, begin + limit), begin); 20084 } 20085 } 20086 }; 20087} 20088 20089/** 20090 * @ngdoc filter 20091 * @name orderBy 20092 * @kind function 20093 * 20094 * @description 20095 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 20096 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 20097 * as expected, make sure they are actually being saved as numbers and not strings. 20098 * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported. 20099 * 20100 * @param {Array} array The array (or array-like object) to sort. 20101 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 20102 * used by the comparator to determine the order of elements. 20103 * 20104 * Can be one of: 20105 * 20106 * - `function`: Getter function. The result of this function will be sorted using the 20107 * `<`, `===`, `>` operator. 20108 * - `string`: An Angular expression. The result of this expression is used to compare elements 20109 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 20110 * 3 first characters of a property called `name`). The result of a constant expression 20111 * is interpreted as a property name to be used in comparisons (for example `"special name"` 20112 * to sort object by the value of their `special name` property). An expression can be 20113 * optionally prefixed with `+` or `-` to control ascending or descending sort order 20114 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 20115 * element itself is used to compare where sorting. 20116 * - `Array`: An array of function or string predicates. The first predicate in the array 20117 * is used for sorting, but when two items are equivalent, the next predicate is used. 20118 * 20119 * If the predicate is missing or empty then it defaults to `'+'`. 20120 * 20121 * @param {boolean=} reverse Reverse the order of the array. 20122 * @returns {Array} Sorted copy of the source array. 20123 * 20124 * 20125 * @example 20126 * The example below demonstrates a simple ngRepeat, where the data is sorted 20127 * by age in descending order (predicate is set to `'-age'`). 20128 * `reverse` is not set, which means it defaults to `false`. 20129 <example module="orderByExample"> 20130 <file name="index.html">
20131 <div ng-controller="ExampleController"> 20132 <table class="friend"> 20133 <tr> 20134 <th>Name</th> 20135 <th>Phone Number</th> 20136 <th>Age</th> 20137 </tr> 20138 <tr ng-repeat="friend in friends | orderBy:'-age'"> 20139 <td>{{friend.name}}</td> 20140 <td>{{friend.phone}}</td> 20141 <td>{{friend.age}}</td> 20142 </tr> 20143 </table> 20144 </div> 20145 </file> 20146 <file name="script.js"> 20147 angular.module('orderByExample', []) 20148 .controller('ExampleController', ['$scope', function($scope) { 20149 $scope.friends = 20150 [{name:'John', phone:'555-1212', age:10}, 20151 {name:'Mary', phone:'555-9876', age:19}, 20152 {name:'Mike', phone:'555-4321', age:21}, 20153 {name:'Adam', phone:'555-5678', age:35}, 20154 {name:'Julie', phone:'555-8765', age:29}]; 20155 }]); 20156 </file> 20157 </example> 20158 * 20159 * The predicate and reverse parameters can be controlled dynamically through scope properties, 20160 * as shown in the next example. 20161 * @example 20162 <example module="orderByExample"> 20163 <file name="index.html">
20164 <div ng-controller="ExampleController"> 20165 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 20166 <hr/> 20167 <button ng-click="predicate=''">Set to unsorted</button> 20168 <table class="friend"> 20169 <tr> 20170 <th> 20171 <button ng-click="order('name')">Name</button> 20172 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 20173 </th> 20174 <th> 20175 <button ng-click="order('phone')">Phone Number</button> 20176 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 20177 </th> 20178 <th> 20179 <button ng-click="order('age')">Age</button> 20180 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 20181 </th> 20182 </tr> 20183 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 20184 <td>{{friend.name}}</td> 20185 <td>{{friend.phone}}</td> 20186 <td>{{friend.age}}</td> 20187 </tr> 20188 </table> 20189 </div> 20190 </file> 20191 <file name="script.js"> 20192 angular.module('orderByExample', []) 20193 .controller('ExampleController', ['$scope', function($scope) { 20194 $scope.friends = 20195 [{name:'John', phone:'555-1212', age:10}, 20196 {name:'Mary', phone:'555-9876', age:19}, 20197 {name:'Mike', phone:'555-4321', age:21}, 20198 {name:'Adam', phone:'555-5678', age:35}, 20199 {name:'Julie', phone:'555-8765', age:29}]; 20200 $scope.predicate = 'age'; 20201 $scope.reverse = true; 20202 $scope.order = function(predicate) { 20203 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 20204 $scope.predicate = predicate; 20205 }; 20206 }]); 20207 </file> 20208 <file name="style.css"> 20209 .sortorder:after { 20210 content: '\25b2'; 20211 } 20212 .sortorder.reverse:after { 20213 content: '\25bc'; 20214 } 20215 </file> 20216 </example> 20217 * 20218 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 20219 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 20220 * desired parameters. 20221 * 20222 * Example: 20223 * 20224 * @example 20225 <example module="orderByExample"> 20226 <file name="index.html">
20227 <div ng-controller="ExampleController"> 20228 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 20229 <table class="friend"> 20230 <tr> 20231 <th> 20232 <button ng-click="order('name')">Name</button> 20233 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 20234 </th> 20235 <th> 20236 <button ng-click="order('phone')">Phone Number</button> 20237 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 20238 </th> 20239 <th> 20240 <button ng-click="order('age')">Age</button> 20241 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 20242 </th> 20243 </tr> 20244 <tr ng-repeat="friend in friends"> 20245 <td>{{friend.name}}</td> 20246 <td>{{friend.phone}}</td> 20247 <td>{{friend.age}}</td> 20248 </tr> 20249 </table> 20250 </div> 20251 </file> 20252 20253 <file name="script.js"> 20254 angular.module('orderByExample', []) 20255 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 20256 var orderBy = $filter('orderBy'); 20257 $scope.friends = [ 20258 { name: 'John', phone: '555-1212', age: 10 }, 20259 { name: 'Mary', phone: '555-9876', age: 19 }, 20260 { name: 'Mike', phone: '555-4321', age: 21 }, 20261 { name: 'Adam', phone: '555-5678', age: 35 }, 20262 { name: 'Julie', phone: '555-8765', age: 29 } 20263 ]; 20264 $scope.order = function(predicate) { 20265 $scope.predicate = predicate; 20266 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 20267 $scope.friends = orderBy($scope.friends, predicate, $scope.reverse); 20268 }; 20269 $scope.order('age', true); 20270 }]); 20271 </file> 20272 20273 <file name="style.css"> 20274 .sortorder:after { 20275 content: '\25b2'; 20276 } 20277 .sortorder.reverse:after { 20278 content: '\25bc'; 20279 } 20280 </file> 20281</example> 20282 */ 20283orderByFilter.$inject = ['$parse']; 20284function orderByFilter($parse) { 20285 return function(array, sortPredicate, reverseOrder) { 20286 20287 if (array == null) return array; 20288 if (!isArrayLike(array)) { 20289 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 20290 } 20291 20292 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 20293 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 20294 20295 var predicates = processPredicates(sortPredicate, reverseOrder); 20296 // Add a predicate at the end that evaluates to the element index. This makes the 20297 // sort stable as it works as a tie-breaker when all the input predicates cannot 20298 // distinguish between two elements. 20299 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 20300 20301 // The next three lines are a version of a Swartzian Transform idiom from Perl 20302 // (sometimes called the Decorate-Sort-Undecorate idiom) 20303 // See https://en.wikipedia.org/wiki/Schwartzian_transform 20304 var compareValues = Array.prototype.map.call(array, getComparisonObject); 20305 compareValues.sort(doComparison); 20306 array = compareValues.map(function(item) { return item.value; }); 20307 20308 return array; 20309 20310 function getComparisonObject(value, index) { 20311 return { 20312 value: value, 20313 predicateValues: predicates.map(function(predicate) { 20314 return getPredicateValue(predicate.get(value), index); 20315 }) 20316 }; 20317 } 20318 20319 function doComparison(v1, v2) { 20320 var result = 0; 20321 for (var index=0, length = predicates.length; index < length; ++index) { 20322 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 20323 if (result) break; 20324 } 20325 return result; 20326 } 20327 }; 20328 20329 function processPredicates(sortPredicate, reverseOrder) { 20330 reverseOrder = reverseOrder ? -1 : 1; 20331 return sortPredicate.map(function(predicate) { 20332 var descending = 1, get = identity; 20333 20334 if (isFunction(predicate)) { 20335 get = predicate; 20336 } else if (isString(predicate)) { 20337 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 20338 descending = predicate.charAt(0) == '-' ? -1 : 1; 20339 predicate = predicate.substring(1); 20340 } 20341 if (predicate !== '') { 20342 get = $parse(predicate); 20343 if (get.constant) { 20344 var key = get(); 20345 get = function(value) { return value[key]; }; 20346 } 20347 } 20348 }
20349 return { get: get, descending: descending * reverseOrder }; 20350 }); 20351 } 20352 20353 function isPrimitive(value) { 20354 switch (typeof value) { 20355 case 'number': /* falls through */ 20356 case 'boolean': /* falls through */ 20357 case 'string': 20358 return true; 20359 default: 20360 return false; 20361 } 20362 } 20363 20364 function objectValue(value, index) { 20365 // If `valueOf` is a valid function use that 20366 if (typeof value.valueOf === 'function') { 20367 value = value.valueOf(); 20368 if (isPrimitive(value)) return value; 20369 } 20370 // If `toString` is a valid function and not the one from `Object.prototype` use that 20371 if (hasCustomToString(value)) { 20372 value = value.toString(); 20373 if (isPrimitive(value)) return value; 20374 } 20375 // We have a basic object so we use the position of the object in the collection 20376 return index; 20377 } 20378 20379 function getPredicateValue(value, index) { 20380 var type = typeof value; 20381 if (value === null) { 20382 type = 'string'; 20383 value = 'null'; 20384 } else if (type === 'string') { 20385 value = value.toLowerCase(); 20386 } else if (type === 'object') { 20387 value = objectValue(value, index); 20388 } 20389 return { value: value, type: type }; 20390 } 20391 20392 function compare(v1, v2) { 20393 var result = 0; 20394 if (v1.type === v2.type) { 20395 if (v1.value !== v2.value) { 20396 result = v1.value < v2.value ? -1 : 1; 20397 } 20398 } else { 20399 result = v1.type < v2.type ? -1 : 1; 20400 } 20401 return result; 20402 } 20403} 20404 20405function ngDirective(directive) { 20406 if (isFunction(directive)) { 20407 directive = { 20408 link: directive 20409 }; 20410 } 20411 directive.restrict = directive.restrict || 'AC'; 20412 return valueFn(directive); 20413} 20414 20415/** 20416 * @ngdoc directive 20417 * @name a 20418 * @restrict E 20419 * 20420 * @description 20421 * Modifies the default behavior of the html A tag so that the default action is prevented when 20422 * the href attribute is empty. 20423 * 20424 * This change permits the easy creation of action links with the `ngClick` directive 20425 * without changing the location or causing page reloads, e.g.: 20426 * `<a href="" ng-click="list.addItem()">Add Item</a>` 20427 */ 20428var htmlAnchorDirective = valueFn({ 20429 restrict: 'E', 20430 compile: function(element, attr) { 20431 if (!attr.href && !attr.xlinkHref) { 20432 return function(scope, element) { 20433 // If the linked element is not an anchor tag anymore, do nothing 20434 if (element[0].nodeName.toLowerCase() !== 'a') return; 20435 20436 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 20437 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 20438 'xlink:href' : 'href'; 20439 element.on('click', function(event) { 20440 // if we have no href url, then don't navigate anywhere. 20441 if (!element.attr(href)) { 20442 event.preventDefault(); 20443 } 20444 }); 20445 }; 20446 } 20447 } 20448}); 20449 20450/** 20451 * @ngdoc directive 20452 * @name ngHref 20453 * @restrict A 20454 * @priority 99 20455 * 20456 * @description 20457 * Using Angular markup like `{{hash}}` in an href attribute will 20458 * make the link go to the wrong URL if the user clicks it before 20459 * Angular has a chance to replace the `{{hash}}` markup with its 20460 * value. Until Angular replaces the markup the link will be broken 20461 * and will most likely return a 404 error. The `ngHref` directive 20462 * solves this problem. 20463 * 20464 * The wrong way to write it: 20465 * ```html 20466 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 20467 * ``` 20468 * 20469 * The correct way to write it: 20470 * ```html 20471 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 20472 * ``` 20473 * 20474 * @element A 20475 * @param {template} ngHref any string which can contain `{{}}` markup. 20476 * 20477 * @example 20478 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 20479 * in links and their different behaviors: 20480 <example> 20481 <file name="index.html"> 20482 <input ng-model="value" /><br /> 20483 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 20484 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 20485 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 20486 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 20487 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 20488 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 20489 </file> 20490 <file name="protractor.js" type="protractor"> 20491 it('should execute ng-click but not reload when href without value', function() {
20492 element(by.id('link-1')).click(); 20493 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 20494 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 20495 }); 20496 20497 it('should execute ng-click but not reload when href empty string', function() { 20498 element(by.id('link-2')).click(); 20499 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 20500 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 20501 }); 20502 20503 it('should execute ng-click and change url when ng-href specified', function() { 20504 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 20505 20506 element(by.id('link-3')).click(); 20507 20508 // At this point, we navigate away from an Angular page, so we need 20509 // to use browser.driver to get the base webdriver. 20510 20511 browser.wait(function() { 20512 return browser.driver.getCurrentUrl().then(function(url) { 20513 return url.match(/\/123$/); 20514 }); 20515 }, 5000, 'page should navigate to /123'); 20516 }); 20517 20518 it('should execute ng-click but not reload when href empty string and name specified', function() { 20519 element(by.id('link-4')).click(); 20520 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 20521 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 20522 }); 20523 20524 it('should execute ng-click but not reload when no href but name specified', function() { 20525 element(by.id('link-5')).click(); 20526 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 20527 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 20528 }); 20529 20530 it('should only change url when only ng-href', function() { 20531 element(by.model('value')).clear(); 20532 element(by.model('value')).sendKeys('6'); 20533 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 20534 20535 element(by.id('link-6')).click(); 20536 20537 // At this point, we navigate away from an Angular page, so we need 20538 // to use browser.driver to get the base webdriver. 20539 browser.wait(function() { 20540 return browser.driver.getCurrentUrl().then(function(url) { 20541 return url.match(/\/6$/); 20542 }); 20543 }, 5000, 'page should navigate to /6'); 20544 }); 20545 </file> 20546 </example> 20547 */ 20548 20549/** 20550 * @ngdoc directive 20551 * @name ngSrc 20552 * @restrict A 20553 * @priority 99 20554 * 20555 * @description 20556 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 20557 * work right: The browser will fetch from the URL with the literal 20558 * text `{{hash}}` until Angular replaces the expression inside 20559 * `{{hash}}`. The `ngSrc` directive solves this problem. 20560 * 20561 * The buggy way to write it: 20562 * ```html 20563 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 20564 * ``` 20565 * 20566 * The correct way to write it: 20567 * ```html 20568 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 20569 * ``` 20570 * 20571 * @element IMG 20572 * @param {template} ngSrc any string which can contain `{{}}` markup. 20573 */ 20574 20575/** 20576 * @ngdoc directive 20577 * @name ngSrcset 20578 * @restrict A 20579 * @priority 99 20580 * 20581 * @description 20582 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 20583 * work right: The browser will fetch from the URL with the literal 20584 * text `{{hash}}` until Angular replaces the expression inside 20585 * `{{hash}}`. The `ngSrcset` directive solves this problem. 20586 * 20587 * The buggy way to write it: 20588 * ```html 20589 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 20590 * ``` 20591 * 20592 * The correct way to write it: 20593 * ```html 20594 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 20595 * ``` 20596 * 20597 * @element IMG 20598 * @param {template} ngSrcset any string which can contain `{{}}` markup. 20599 */ 20600 20601/** 20602 * @ngdoc directive 20603 * @name ngDisabled 20604 * @restrict A 20605 * @priority 100 20606 * 20607 * @description 20608 * 20609 * This directive sets the `disabled` attribute on the element if the 20610 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 20611 * 20612 * A special directive is necessary because we cannot use interpolation inside the `disabled` 20613 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20614 * 20615 * @example 20616 <example> 20617 <file name="index.html"> 20618 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 20619 <button ng-model="button" ng-disabled="checked">Button</button> 20620 </file> 20621 <file name="protractor.js" type="protractor"> 20622 it('should toggle button', function() { 20623 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
20624 element(by.model('checked')).click(); 20625 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 20626 }); 20627 </file> 20628 </example> 20629 * 20630 * @element INPUT 20631 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 20632 * then the `disabled` attribute will be set on the element 20633 */ 20634 20635 20636/** 20637 * @ngdoc directive 20638 * @name ngChecked 20639 * @restrict A 20640 * @priority 100 20641 * 20642 * @description 20643 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 20644 * 20645 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 20646 * as this can lead to unexpected behavior. 20647 * 20648 * A special directive is necessary because we cannot use interpolation inside the `checked` 20649 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20650 * 20651 * @example 20652 <example> 20653 <file name="index.html"> 20654 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 20655 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 20656 </file> 20657 <file name="protractor.js" type="protractor"> 20658 it('should check both checkBoxes', function() { 20659 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 20660 element(by.model('master')).click(); 20661 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 20662 }); 20663 </file> 20664 </example> 20665 * 20666 * @element INPUT 20667 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 20668 * then the `checked` attribute will be set on the element 20669 */ 20670 20671 20672/** 20673 * @ngdoc directive 20674 * @name ngReadonly 20675 * @restrict A 20676 * @priority 100 20677 * 20678 * @description 20679 * 20680 * Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy. 20681 * 20682 * A special directive is necessary because we cannot use interpolation inside the `readOnly` 20683 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20684 * 20685 * @example 20686 <example> 20687 <file name="index.html"> 20688 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 20689 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 20690 </file> 20691 <file name="protractor.js" type="protractor"> 20692 it('should toggle readonly attr', function() { 20693 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 20694 element(by.model('checked')).click(); 20695 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 20696 }); 20697 </file> 20698 </example> 20699 * 20700 * @element INPUT 20701 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 20702 * then special attribute "readonly" will be set on the element 20703 */ 20704 20705 20706/** 20707 * @ngdoc directive 20708 * @name ngSelected 20709 * @restrict A 20710 * @priority 100 20711 * 20712 * @description 20713 * 20714 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 20715 * 20716 * A special directive is necessary because we cannot use interpolation inside the `selected` 20717 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20718 * 20719 * @example 20720 <example> 20721 <file name="index.html"> 20722 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 20723 <select aria-label="ngSelected demo"> 20724 <option>Hello!</option> 20725 <option id="greet" ng-selected="selected">Greetings!</option> 20726 </select> 20727 </file> 20728 <file name="protractor.js" type="protractor"> 20729 it('should select Greetings!', function() { 20730 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 20731 element(by.model('selected')).click(); 20732 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 20733 }); 20734 </file> 20735 </example> 20736 * 20737 * @element OPTION 20738 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 20739 * then special attribute "selected" will be set on the element 20740 */ 20741 20742/** 20743 * @ngdoc directive 20744 * @name ngOpen 20745 * @restrict A 20746 * @priority 100 20747 * 20748 * @description 20749 * 20750 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 20751 * 20752 * A special directive is necessary because we cannot use interpolation inside the `open` 20753 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20754 * 20755 * @example 20756 <example> 20757 <file name="index.html"> 20758 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 20759 <details id="details" ng-open="open"> 20760 <summary>Show/Hide me</summary> 20761 </details> 20762 </file> 20763 <file name="protractor.js" type="protractor"> 20764 it('should toggle open', function() { 20765 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20766 element(by.model('open')).click(); 20767 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 20768 }); 20769 </file> 20770 </example> 20771 * 20772 * @element DETAILS 20773 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 20774 * then special attribute "open" will be set on the element 20775 */ 20776 20777var ngAttributeAliasDirectives = {}; 20778 20779// boolean attrs are evaluated 20780forEach(BOOLEAN_ATTR, function(propName, attrName) { 20781 // binding to multiple is not supported 20782 if (propName == "multiple") return; 20783 20784 function defaultLinkFn(scope, element, attr) { 20785 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 20786 attr.$set(attrName, !!value); 20787 }); 20788 } 20789 20790 var normalized = directiveNormalize('ng-' + attrName); 20791 var linkFn = defaultLinkFn; 20792 20793 if (propName === 'checked') { 20794 linkFn = function(scope, element, attr) { 20795 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 20796 if (attr.ngModel !== attr[normalized]) { 20797 defaultLinkFn(scope, element, attr); 20798 } 20799 }; 20800 } 20801 20802 ngAttributeAliasDirectives[normalized] = function() { 20803 return { 20804 restrict: 'A', 20805 priority: 100, 20806 link: linkFn 20807 }; 20808 }; 20809}); 20810 20811// aliased input attrs are evaluated 20812forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 20813 ngAttributeAliasDirectives[ngAttr] = function() { 20814 return { 20815 priority: 100, 20816 link: function(scope, element, attr) { 20817 //special case ngPattern when a literal regular expression value 20818 //is used as the expression (this way we don't have to watch anything). 20819 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 20820 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 20821 if (match) { 20822 attr.$set("ngPattern", new RegExp(match[1], match[2])); 20823 return; 20824 } 20825 } 20826 20827 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 20828 attr.$set(ngAttr, value); 20829 }); 20830 } 20831 }; 20832 }; 20833}); 20834 20835// ng-src, ng-srcset, ng-href are interpolated 20836forEach(['src', 'srcset', 'href'], function(attrName) { 20837 var normalized = directiveNormalize('ng-' + attrName); 20838 ngAttributeAliasDirectives[normalized] = function() { 20839 return { 20840 priority: 99, // it needs to run after the attributes are interpolated 20841 link: function(scope, element, attr) { 20842 var propName = attrName, 20843 name = attrName; 20844 20845 if (attrName === 'href' && 20846 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 20847 name = 'xlinkHref'; 20848 attr.$attr[name] = 'xlink:href'; 20849 propName = null; 20850 } 20851 20852 attr.$observe(normalized, function(value) { 20853 if (!value) { 20854 if (attrName === 'href') { 20855 attr.$set(name, null); 20856 } 20857 return; 20858 } 20859 20860 attr.$set(name, value); 20861 20862 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 20863 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 20864 // to set the property as well to achieve the desired effect. 20865 // we use attr[attrName] value since $set can sanitize the url. 20866 if (msie && propName) element.prop(propName, attr[name]); 20867 }); 20868 } 20869 }; 20870 }; 20871}); 20872 20873/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 20874 */ 20875var nullFormCtrl = { 20876 $addControl: noop, 20877 $$renameControl: nullFormRenameControl, 20878 $removeControl: noop, 20879 $setValidity: noop, 20880 $setDirty: noop, 20881 $setPristine: noop, 20882 $setSubmitted: noop 20883}, 20884SUBMITTED_CLASS = 'ng-submitted'; 20885 20886function nullFormRenameControl(control, name) { 20887 control.$name = name; 20888} 20889 20890/** 20891 * @ngdoc type 20892 * @name form.FormController 20893 * 20894 * @property {boolean} $pristine True if user has not interacted with the form yet. 20895 * @property {boolean} $dirty True if user has already interacted with the form. 20896 * @property {boolean} $valid True if all of the containing forms and controls are valid. 20897 * @property {boolean}
20897 $invalid True if at least one containing control or form is invalid. 20898 * @property {boolean} $pending True if at least one containing control or form is pending. 20899 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 20900 * 20901 * @property {Object} $error Is an object hash, containing references to controls or 20902 * forms with failing validators, where: 20903 * 20904 * - keys are validation tokens (error names), 20905 * - values are arrays of controls or forms that have a failing validator for given error name. 20906 * 20907 * Built-in validation tokens: 20908 * 20909 * - `email` 20910 * - `max` 20911 * - `maxlength` 20912 * - `min` 20913 * - `minlength` 20914 * - `number` 20915 * - `pattern` 20916 * - `required` 20917 * - `url` 20918 * - `date` 20919 * - `datetimelocal` 20920 * - `time` 20921 * - `week` 20922 * - `month` 20923 * 20924 * @description 20925 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 20926 * such as being valid/invalid or dirty/pristine. 20927 * 20928 * Each {@link ng.directive:form form} directive creates an instance 20929 * of `FormController`. 20930 * 20931 */ 20932//asks for $scope to fool the BC controller module 20933FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 20934function FormController(element, attrs, $scope, $animate, $interpolate) { 20935 var form = this, 20936 controls = []; 20937 20938 // init state 20939 form.$error = {}; 20940 form.$$success = {}; 20941 form.$pending = undefined; 20942 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 20943 form.$dirty = false; 20944 form.$pristine = true; 20945 form.$valid = true; 20946 form.$invalid = false; 20947 form.$submitted = false; 20948 form.$$parentForm = nullFormCtrl; 20949 20950 /** 20951 * @ngdoc method 20952 * @name form.FormController#$rollbackViewValue 20953 * 20954 * @description 20955 * Rollback all form controls pending updates to the `$modelValue`. 20956 * 20957 * Updates may be pending by a debounced event or because the input is waiting for a some future 20958 * event defined in `ng-model-options`. This method is typically needed by the reset button of 20959 * a form that uses `ng-model-options` to pend updates. 20960 */ 20961 form.$rollbackViewValue = function() { 20962 forEach(controls, function(control) { 20963 control.$rollbackViewValue(); 20964 }); 20965 }; 20966 20967 /** 20968 * @ngdoc method 20969 * @name form.FormController#$commitViewValue 20970 * 20971 * @description 20972 * Commit all form controls pending updates to the `$modelValue`. 20973 * 20974 * Updates may be pending by a debounced event or because the input is waiting for a some future 20975 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 20976 * usually handles calling this in response to input events. 20977 */ 20978 form.$commitViewValue = function() { 20979 forEach(controls, function(control) { 20980 control.$commitViewValue(); 20981 }); 20982 }; 20983 20984 /** 20985 * @ngdoc method 20986 * @name form.FormController#$addControl 20987 * @param {object} control control object, either a {@link form.FormController} or an 20988 * {@link ngModel.NgModelController} 20989 * 20990 * @description 20991 * Register a control with the form. Input elements using ngModelController do this automatically 20992 * when they are linked. 20993 * 20994 * Note that the current state of the control will not be reflected on the new parent form. This 20995 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 20996 * state. 20997 * 20998 * However, if the method is used programmatically, for example by adding dynamically created controls, 20999 * or controls that have been previously removed without destroying their corresponding DOM element, 21000 * it's the developers responsibility to make sure the current state propagates to the parent form. 21001 * 21002 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 21003 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 21004 */ 21005 form.$addControl = function(control) { 21006 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 21007 // and not added to the scope. Now we throw an error. 21008 assertNotHasOwnProperty(control.$name, 'input'); 21009 controls.push(control); 21010 21011 if (control.$name) { 21012 form[control.$name] = control; 21013 } 21014 21015 control.$$parentForm = form; 21016 }; 21017 21018 // Private API: rename a form control 21019 form.$$renameControl = function(control, newName) { 21020 var oldName = control.$name; 21021 21022 if (form[oldName] === control) { 21023 delete form[oldName]; 21024 } 21025 form[newName] = control; 21026 control.$name = newName; 21027 }; 21028 21029 /** 21030 * @ngdoc method
21031 * @name form.FormController#$removeControl 21032 * @param {object} control control object, either a {@link form.FormController} or an 21033 * {@link ngModel.NgModelController} 21034 * 21035 * @description 21036 * Deregister a control from the form. 21037 * 21038 * Input elements using ngModelController do this automatically when they are destroyed. 21039 * 21040 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 21041 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 21042 * different from case to case. For example, removing the only `$dirty` control from a form may or 21043 * may not mean that the form is still `$dirty`. 21044 */ 21045 form.$removeControl = function(control) { 21046 if (control.$name && form[control.$name] === control) { 21047 delete form[control.$name]; 21048 } 21049 forEach(form.$pending, function(value, name) { 21050 form.$setValidity(name, null, control); 21051 }); 21052 forEach(form.$error, function(value, name) { 21053 form.$setValidity(name, null, control); 21054 }); 21055 forEach(form.$$success, function(value, name) { 21056 form.$setValidity(name, null, control); 21057 }); 21058 21059 arrayRemove(controls, control); 21060 control.$$parentForm = nullFormCtrl; 21061 }; 21062 21063 21064 /** 21065 * @ngdoc method 21066 * @name form.FormController#$setValidity 21067 * 21068 * @description 21069 * Sets the validity of a form control. 21070 * 21071 * This method will also propagate to parent forms. 21072 */ 21073 addSetValidityMethod({ 21074 ctrl: this, 21075 $element: element, 21076 set: function(object, property, controller) { 21077 var list = object[property]; 21078 if (!list) { 21079 object[property] = [controller]; 21080 } else { 21081 var index = list.indexOf(controller); 21082 if (index === -1) { 21083 list.push(controller); 21084 } 21085 } 21086 }, 21087 unset: function(object, property, controller) { 21088 var list = object[property]; 21089 if (!list) { 21090 return; 21091 } 21092 arrayRemove(list, controller); 21093 if (list.length === 0) { 21094 delete object[property]; 21095 } 21096 }, 21097 $animate: $animate 21098 }); 21099 21100 /** 21101 * @ngdoc method 21102 * @name form.FormController#$setDirty 21103 * 21104 * @description 21105 * Sets the form to a dirty state. 21106 * 21107 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 21108 * state (ng-dirty class). This method will also propagate to parent forms. 21109 */ 21110 form.$setDirty = function() { 21111 $animate.removeClass(element, PRISTINE_CLASS); 21112 $animate.addClass(element, DIRTY_CLASS); 21113 form.$dirty = true; 21114 form.$pristine = false; 21115 form.$$parentForm.$setDirty(); 21116 }; 21117 21118 /** 21119 * @ngdoc method 21120 * @name form.FormController#$setPristine 21121 * 21122 * @description 21123 * Sets the form to its pristine state. 21124 * 21125 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 21126 * state (ng-pristine class). This method will also propagate to all the controls contained 21127 * in this form. 21128 * 21129 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 21130 * saving or resetting it. 21131 */ 21132 form.$setPristine = function() { 21133 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 21134 form.$dirty = false; 21135 form.$pristine = true; 21136 form.$submitted = false; 21137 forEach(controls, function(control) { 21138 control.$setPristine(); 21139 }); 21140 }; 21141 21142 /** 21143 * @ngdoc method 21144 * @name form.FormController#$setUntouched 21145 * 21146 * @description 21147 * Sets the form to its untouched state. 21148 * 21149 * This method can be called to remove the 'ng-touched' class and set the form controls to their 21150 * untouched state (ng-untouched class). 21151 * 21152 * Setting a form controls back to their untouched state is often useful when setting the form 21153 * back to its pristine state. 21154 */ 21155 form.$setUntouched = function() { 21156 forEach(controls, function(control) { 21157 control.$setUntouched(); 21158 }); 21159 }; 21160 21161 /** 21162 * @ngdoc method 21163 * @name form.FormController#$setSubmitted 21164 * 21165 * @description 21166 * Sets the form to its submitted state. 21167 */ 21168 form.$setSubmitted = function() {
21169 $animate.addClass(element, SUBMITTED_CLASS); 21170 form.$submitted = true; 21171 form.$$parentForm.$setSubmitted(); 21172 }; 21173} 21174 21175/** 21176 * @ngdoc directive 21177 * @name ngForm 21178 * @restrict EAC 21179 * 21180 * @description 21181 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 21182 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 21183 * sub-group of controls needs to be determined. 21184 * 21185 * Note: the purpose of `ngForm` is to group controls, 21186 * but not to be a replacement for the `<form>` tag with all of its capabilities 21187 * (e.g. posting to the server, ...). 21188 * 21189 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 21190 * related scope, under this name. 21191 * 21192 */ 21193 21194 /** 21195 * @ngdoc directive 21196 * @name form 21197 * @restrict E 21198 * 21199 * @description 21200 * Directive that instantiates 21201 * {@link form.FormController FormController}. 21202 * 21203 * If the `name` attribute is specified, the form controller is published onto the current scope under 21204 * this name. 21205 * 21206 * # Alias: {@link ng.directive:ngForm `ngForm`} 21207 * 21208 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 21209 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 21210 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 21211 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 21212 * of controls needs to be determined. 21213 * 21214 * # CSS classes 21215 * - `ng-valid` is set if the form is valid. 21216 * - `ng-invalid` is set if the form is invalid. 21217 * - `ng-pending` is set if the form is pending. 21218 * - `ng-pristine` is set if the form is pristine. 21219 * - `ng-dirty` is set if the form is dirty. 21220 * - `ng-submitted` is set if the form was submitted. 21221 * 21222 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 21223 * 21224 * 21225 * # Submitting a form and preventing the default action 21226 * 21227 * Since the role of forms in client-side Angular applications is different than in classical 21228 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 21229 * page reload that sends the data to the server. Instead some javascript logic should be triggered 21230 * to handle the form submission in an application-specific way. 21231 * 21232 * For this reason, Angular prevents the default action (form submission to the server) unless the 21233 * `<form>` element has an `action` attribute specified. 21234 * 21235 * You can use one of the following two ways to specify what javascript method should be called when 21236 * a form is submitted: 21237 * 21238 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 21239 * - {@link ng.directive:ngClick ngClick} directive on the first 21240 * button or input field of type submit (input[type=submit]) 21241 * 21242 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 21243 * or {@link ng.directive:ngClick ngClick} directives. 21244 * This is because of the following form submission rules in the HTML specification: 21245 * 21246 * - If a form has only one input field then hitting enter in this field triggers form submit 21247 * (`ngSubmit`) 21248 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 21249 * doesn't trigger submit 21250 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 21251 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 21252 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 21253 * 21254 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 21255 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 21256 * to have access to the updated model. 21257 * 21258 * ## Animation Hooks 21259 * 21260 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 21261 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 21262 * other validations that are performed within the form. Animations in ngForm are similar to how 21263 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 21264 * as JS animations. 21265 * 21266 * The following example shows a simple way to utilize CSS transitions to style a form element
21267 * that has been rendered as invalid after it has been validated: 21268 * 21269 * <pre> 21270 * //be sure to include ngAnimate as a module to hook into more 21271 * //advanced animations 21272 * .my-form { 21273 * transition:0.5s linear all; 21274 * background: white; 21275 * } 21276 * .my-form.ng-invalid { 21277 * background: red; 21278 * color:white; 21279 * } 21280 * </pre> 21281 * 21282 * @example 21283 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 21284 <file name="index.html"> 21285 <script> 21286 angular.module('formExample', []) 21287 .controller('FormController', ['$scope', function($scope) { 21288 $scope.userType = 'guest'; 21289 }]); 21290 </script> 21291 <style> 21292 .my-form { 21293 transition:all linear 0.5s; 21294 background: transparent; 21295 } 21296 .my-form.ng-invalid { 21297 background: red; 21298 } 21299 </style> 21300 <form name="myForm" ng-controller="FormController" class="my-form"> 21301 userType: <input name="input" ng-model="userType" required> 21302 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 21303 <code>userType = {{userType}}</code><br> 21304 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 21305 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 21306 <code>myForm.$valid = {{myForm.$valid}}</code><br> 21307 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 21308 </form> 21309 </file> 21310 <file name="protractor.js" type="protractor"> 21311 it('should initialize to model', function() { 21312 var userType = element(by.binding('userType')); 21313 var valid = element(by.binding('myForm.input.$valid')); 21314 21315 expect(userType.getText()).toContain('guest'); 21316 expect(valid.getText()).toContain('true'); 21317 }); 21318 21319 it('should be invalid if empty', function() { 21320 var userType = element(by.binding('userType')); 21321 var valid = element(by.binding('myForm.input.$valid')); 21322 var userInput = element(by.model('userType')); 21323 21324 userInput.clear(); 21325 userInput.sendKeys(''); 21326 21327 expect(userType.getText()).toEqual('userType ='); 21328 expect(valid.getText()).toContain('false'); 21329 }); 21330 </file> 21331 </example> 21332 * 21333 * @param {string=} name Name of the form. If specified, the form controller will be published into 21334 * related scope, under this name. 21335 */ 21336var formDirectiveFactory = function(isNgForm) { 21337 return ['$timeout', '$parse', function($timeout, $parse) { 21338 var formDirective = { 21339 name: 'form', 21340 restrict: isNgForm ? 'EAC' : 'E', 21341 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 21342 controller: FormController, 21343 compile: function ngFormCompile(formElement, attr) { 21344 // Setup initial state of the control 21345 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 21346 21347 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 21348 21349 return { 21350 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 21351 var controller = ctrls[0]; 21352 21353 // if `action` attr is not present on the form, prevent the default action (submission) 21354 if (!('action' in attr)) { 21355 // we can't use jq events because if a form is destroyed during submission the default 21356 // action is not prevented. see #1238 21357 // 21358 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 21359 // page reload if the form was destroyed by submission of the form via a click handler 21360 // on a button in the form. Looks like an IE9 specific bug. 21361 var handleFormSubmission = function(event) { 21362 scope.$apply(function() { 21363 controller.$commitViewValue(); 21364 controller.$setSubmitted(); 21365 }); 21366 21367 event.preventDefault(); 21368 }; 21369 21370 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 21371 21372 // unregister the preventDefault listener so that we don't not leak memory but in a 21373 // way that will achieve the prevention of the default action. 21374 formElement.on('$destroy', function() { 21375 $timeout(function() { 21376 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 21377 }, 0, false); 21378 }); 21379 } 21380 21381 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 21382 parentFormCtrl.$addControl(controller); 21383 21384 var setter = nameAttr ? getSetter(controller.$name) : noop; 21385 21386 if (nameAttr) { 21387 setter(scope, controller); 21388 attr.$observe(nameAttr, function(newValue) { 21389 if (controller.$name === newValue) return; 21390 setter(scope, undefined); 21391 controller.$$parentForm.$$renameControl(controller, newValue); 21392 setter = getSetter(controller.$name); 21393 setter(scope, controller); 21394 }); 21395 } 21396 formElement.on('$destroy', function() { 21397 controller.$$parentForm.$removeControl(controller); 21398 setter(scope, undefined); 21399 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 21400 }); 21401 } 21402 }; 21403 } 21404 }; 21405 21406 return formDirective; 21407 21408 function getSetter(expression) { 21409 if (expression === '') { 21410 //create an assignable expression, so forms with an empty name can be renamed later 21411 return $parse('this[""]').assign; 21412 } 21413 return $parse(expression).assign || noop; 21414 } 21415 }]; 21416}; 21417 21418var formDirective = formDirectiveFactory(); 21419var ngFormDirective = formDirectiveFactory(true); 21420 21421/* global VALID_CLASS: false, 21422 INVALID_CLASS: false, 21423 PRISTINE_CLASS: false, 21424 DIRTY_CLASS: false, 21425 UNTOUCHED_CLASS: false, 21426 TOUCHED_CLASS: false, 21427 ngModelMinErr: false, 21428*/ 21429 21430// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 21431var 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)/;
21432// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 21433// Note: We are being more lenient, because browsers are too. 21434// 1. Scheme 21435// 2. Slashes 21436// 3. Username 21437// 4. Password 21438// 5. Hostname 21439// 6. Port 21440// 7. Path 21441// 8. Query 21442// 9. Fragment 21443// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999 21444var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 21445var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 21446var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 21447var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 21448var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 21449var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 21450var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 21451var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 21452 21453var inputType = { 21454 21455 /** 21456 * @ngdoc input 21457 * @name input[text] 21458 * 21459 * @description 21460 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 21461 * 21462 * 21463 * @param {string} ngModel Assignable angular expression to data-bind to. 21464 * @param {string=} name Property name of the form under which the control is published. 21465 * @param {string=} required Adds `required` validation error key if the value is not entered. 21466 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21467 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21468 * `required` when you want to data-bind to the `required` attribute. 21469 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21470 * minlength. 21471 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21472 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21473 * any length. 21474 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21475 * that contains the regular expression body that will be converted to a regular expression 21476 * as in the ngPattern directive. 21477 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 21478 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 21479 * If the expression evaluates to a RegExp object, then this is used directly. 21480 * If the expression evaluates to a string, then it will be converted to a RegExp 21481 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21482 * `new RegExp('^abc$')`.<br /> 21483 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21484 * start at the index of the last search's match, thus not taking the whole input value into 21485 * account. 21486 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21487 * interaction with the input element. 21488 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 21489 * This parameter is ignored for input[type=password] controls, which will never trim the 21490 * input. 21491 * 21492 * @example 21493 <example name="text-input-directive" module="textInputExample"> 21494 <file name="index.html"> 21495 <script> 21496 angular.module('textInputExample', []) 21497 .controller('ExampleController', ['$scope', function($scope) { 21498 $scope.example = { 21499 text: 'guest', 21500 word: /^\s*\w*\s*$/ 21501 }; 21502 }]); 21503 </script>
21504 <form name="myForm" ng-controller="ExampleController"> 21505 <label>Single word: 21506 <input type="text" name="input" ng-model="example.text" 21507 ng-pattern="example.word" required ng-trim="false"> 21508 </label> 21509 <div role="alert"> 21510 <span class="error" ng-show="myForm.input.$error.required"> 21511 Required!</span> 21512 <span class="error" ng-show="myForm.input.$error.pattern"> 21513 Single word only!</span> 21514 </div> 21515 <tt>text = {{example.text}}</tt><br/> 21516 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21517 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21518 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21519 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21520 </form> 21521 </file> 21522 <file name="protractor.js" type="protractor"> 21523 var text = element(by.binding('example.text')); 21524 var valid = element(by.binding('myForm.input.$valid')); 21525 var input = element(by.model('example.text')); 21526 21527 it('should initialize to model', function() { 21528 expect(text.getText()).toContain('guest'); 21529 expect(valid.getText()).toContain('true'); 21530 }); 21531 21532 it('should be invalid if empty', function() { 21533 input.clear(); 21534 input.sendKeys(''); 21535 21536 expect(text.getText()).toEqual('text ='); 21537 expect(valid.getText()).toContain('false'); 21538 }); 21539 21540 it('should be invalid if multi word', function() { 21541 input.clear(); 21542 input.sendKeys('hello world'); 21543 21544 expect(valid.getText()).toContain('false'); 21545 }); 21546 </file> 21547 </example> 21548 */ 21549 'text': textInputType, 21550 21551 /** 21552 * @ngdoc input 21553 * @name input[date] 21554 * 21555 * @description 21556 * Input with date validation and transformation. In browsers that do not yet support 21557 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 21558 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 21559 * modern browsers do not yet support this input type, it is important to provide cues to users on the 21560 * expected input format via a placeholder or label. 21561 * 21562 * The model must always be a Date object, otherwise Angular will throw an error. 21563 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21564 * 21565 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21566 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21567 * 21568 * @param {string} ngModel Assignable angular expression to data-bind to. 21569 * @param {string=} name Property name of the form under which the control is published. 21570 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 21571 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 21572 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 21573 * constraint validation. 21574 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 21575 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 21576 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 21577 * constraint validation. 21578 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 21579 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21580 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 21581 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21582 * @param {string=} required Sets `required` validation error key if the value is not entered. 21583 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21584 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21585 * `required` when you want to data-bind to the `required` attribute. 21586 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21587 * interaction with the input element. 21588 * 21589 * @example 21590 <example name="date-input-directive" module="dateInputExample"> 21591 <file name="index.html"> 21592 <script> 21593 angular.module('dateInputExample', []) 21594 .controller('DateController', ['$scope', function($scope) { 21595 $scope.example = { 21596 value: new Date(2013, 9, 22) 21597 }; 21598 }]); 21599 </script> 21600 <form name="myForm" ng-controller="DateController as dateCtrl"> 21601 <label for="exampleInput">Pick a date in 2013:</label> 21602 <input type="date" id="exampleInput" name="input" ng-model="example.value" 21603 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 21604 <div role="alert">
21605 <span class="error" ng-show="myForm.input.$error.required"> 21606 Required!</span> 21607 <span class="error" ng-show="myForm.input.$error.date"> 21608 Not a valid date!</span> 21609 </div> 21610 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 21611 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21612 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21613 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21614 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21615 </form> 21616 </file> 21617 <file name="protractor.js" type="protractor"> 21618 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 21619 var valid = element(by.binding('myForm.input.$valid')); 21620 var input = element(by.model('example.value')); 21621 21622 // currently protractor/webdriver does not support 21623 // sending keys to all known HTML5 input controls 21624 // for various browsers (see https://github.com/angular/protractor/issues/562). 21625 function setInput(val) { 21626 // set the value of the element and force validation. 21627 var scr = "var ipt = document.getElementById('exampleInput'); " + 21628 "ipt.value = '" + val + "';" + 21629 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21630 browser.executeScript(scr); 21631 } 21632 21633 it('should initialize to model', function() { 21634 expect(value.getText()).toContain('2013-10-22'); 21635 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21636 }); 21637 21638 it('should be invalid if empty', function() { 21639 setInput(''); 21640 expect(value.getText()).toEqual('value ='); 21641 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21642 }); 21643 21644 it('should be invalid if over max', function() { 21645 setInput('2015-01-01'); 21646 expect(value.getText()).toContain(''); 21647 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21648 }); 21649 </file> 21650 </example> 21651 */ 21652 'date': createDateInputType('date', DATE_REGEXP, 21653 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 21654 'yyyy-MM-dd'), 21655 21656 /** 21657 * @ngdoc input 21658 * @name input[datetime-local] 21659 * 21660 * @description 21661 * Input with datetime validation and transformation. In browsers that do not yet support 21662 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21663 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 21664 * 21665 * The model must always be a Date object, otherwise Angular will throw an error. 21666 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21667 * 21668 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21669 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21670 * 21671 * @param {string} ngModel Assignable angular expression to data-bind to. 21672 * @param {string=} name Property name of the form under which the control is published. 21673 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21674 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 21675 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 21676 * Note that `min` will also add native HTML5 constraint validation. 21677 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21678 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 21679 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 21680 * Note that `max` will also add native HTML5 constraint validation. 21681 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 21682 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21683 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 21684 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21685 * @param {string=} required Sets `required` validation error key if the value is not entered. 21686 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21687 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21688 * `required` when you want to data-bind to the `required` attribute. 21689 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21690 * interaction with the input element. 21691 * 21692 * @example 21693 <example name="datetimelocal-input-directive" module="dateExample"> 21694 <file name="index.html"> 21695 <script> 21696 angular.module('dateExample', []) 21697 .controller('DateController', ['$scope', function($scope) { 21698 $scope.example = { 21699 value: new Date(2010, 11, 28, 14, 57) 21700 }; 21701 }]); 21702 </script> 21703 <form name="myForm" ng-controller="DateController as dateCtrl"> 21704 <label for="exampleInput">Pick a date between in 2013:</label> 21705 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 21706 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 21707 <div role="alert">
21708 <span class="error" ng-show="myForm.input.$error.required"> 21709 Required!</span> 21710 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 21711 Not a valid date!</span> 21712 </div> 21713 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 21714 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21715 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21716 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21717 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21718 </form> 21719 </file> 21720 <file name="protractor.js" type="protractor"> 21721 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 21722 var valid = element(by.binding('myForm.input.$valid')); 21723 var input = element(by.model('example.value')); 21724 21725 // currently protractor/webdriver does not support 21726 // sending keys to all known HTML5 input controls 21727 // for various browsers (https://github.com/angular/protractor/issues/562). 21728 function setInput(val) { 21729 // set the value of the element and force validation. 21730 var scr = "var ipt = document.getElementById('exampleInput'); " + 21731 "ipt.value = '" + val + "';" + 21732 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21733 browser.executeScript(scr); 21734 } 21735 21736 it('should initialize to model', function() { 21737 expect(value.getText()).toContain('2010-12-28T14:57:00'); 21738 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21739 }); 21740 21741 it('should be invalid if empty', function() { 21742 setInput(''); 21743 expect(value.getText()).toEqual('value ='); 21744 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21745 }); 21746 21747 it('should be invalid if over max', function() { 21748 setInput('2015-01-01T23:59:00'); 21749 expect(value.getText()).toContain(''); 21750 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21751 }); 21752 </file> 21753 </example> 21754 */ 21755 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 21756 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 21757 'yyyy-MM-ddTHH:mm:ss.sss'), 21758 21759 /** 21760 * @ngdoc input 21761 * @name input[time] 21762 * 21763 * @description 21764 * Input with time validation and transformation. In browsers that do not yet support 21765 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21766 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 21767 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 21768 * 21769 * The model must always be a Date object, otherwise Angular will throw an error. 21770 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21771 * 21772 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21773 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21774 * 21775 * @param {string} ngModel Assignable angular expression to data-bind to. 21776 * @param {string=} name Property name of the form under which the control is published. 21777 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21778 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 21779 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 21780 * native HTML5 constraint validation. 21781 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21782 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 21783 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 21784 * native HTML5 constraint validation. 21785 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 21786 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21787 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 21788 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21789 * @param {string=} required Sets `required` validation error key if the value is not entered. 21790 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21791 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21792 * `required` when you want to data-bind to the `required` attribute. 21793 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21794 * interaction with the input element. 21795 * 21796 * @example 21797 <example name="time-input-directive" module="timeExample"> 21798 <file name="index.html"> 21799 <script> 21800 angular.module('timeExample', []) 21801 .controller('DateController', ['$scope', function($scope) { 21802 $scope.example = { 21803 value: new Date(1970, 0, 1, 14, 57, 0) 21804 }; 21805 }]); 21806 </script> 21807 <form name="myForm" ng-controller="DateController as dateCtrl"> 21808 <label for="exampleInput">Pick a between 8am and 5pm:</label> 21809 <input type="time" id="exampleInput" name="input" ng-model="example.value" 21810 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 21811 <div role="alert">
21812 <span class="error" ng-show="myForm.input.$error.required"> 21813 Required!</span> 21814 <span class="error" ng-show="myForm.input.$error.time"> 21815 Not a valid date!</span> 21816 </div> 21817 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 21818 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21819 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21820 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21821 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21822 </form> 21823 </file> 21824 <file name="protractor.js" type="protractor"> 21825 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 21826 var valid = element(by.binding('myForm.input.$valid')); 21827 var input = element(by.model('example.value')); 21828 21829 // currently protractor/webdriver does not support 21830 // sending keys to all known HTML5 input controls 21831 // for various browsers (https://github.com/angular/protractor/issues/562). 21832 function setInput(val) { 21833 // set the value of the element and force validation. 21834 var scr = "var ipt = document.getElementById('exampleInput'); " + 21835 "ipt.value = '" + val + "';" + 21836 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21837 browser.executeScript(scr); 21838 } 21839 21840 it('should initialize to model', function() { 21841 expect(value.getText()).toContain('14:57:00'); 21842 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21843 }); 21844 21845 it('should be invalid if empty', function() { 21846 setInput(''); 21847 expect(value.getText()).toEqual('value ='); 21848 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21849 }); 21850 21851 it('should be invalid if over max', function() { 21852 setInput('23:59:00'); 21853 expect(value.getText()).toContain(''); 21854 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21855 }); 21856 </file> 21857 </example> 21858 */ 21859 'time': createDateInputType('time', TIME_REGEXP, 21860 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 21861 'HH:mm:ss.sss'), 21862 21863 /** 21864 * @ngdoc input 21865 * @name input[week] 21866 * 21867 * @description 21868 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 21869 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21870 * week format (yyyy-W##), for example: `2013-W02`. 21871 * 21872 * The model must always be a Date object, otherwise Angular will throw an error. 21873 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21874 * 21875 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21876 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21877 * 21878 * @param {string} ngModel Assignable angular expression to data-bind to. 21879 * @param {string=} name Property name of the form under which the control is published. 21880 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21881 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21882 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 21883 * native HTML5 constraint validation. 21884 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21885 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21886 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 21887 * native HTML5 constraint validation. 21888 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21889 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21890 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21891 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21892 * @param {string=} required Sets `required` validation error key if the value is not entered. 21893 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21894 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21895 * `required` when you want to data-bind to the `required` attribute. 21896 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21897 * interaction with the input element. 21898 * 21899 * @example 21900 <example name="week-input-directive" module="weekExample"> 21901 <file name="index.html"> 21902 <script> 21903 angular.module('weekExample', []) 21904 .controller('DateController', ['$scope', function($scope) { 21905 $scope.example = { 21906 value: new Date(2013, 0, 3) 21907 }; 21908 }]); 21909 </script> 21910 <form name="myForm" ng-controller="DateController as dateCtrl"> 21911 <label>Pick a date between in 2013: 21912 <input id="exampleInput" type="week" name="input" ng-model="example.value" 21913 placeholder="YYYY-W##" min="2012-W32" 21914 max="2013-W52" required /> 21915 </label> 21916 <div role="alert">
21917 <span class="error" ng-show="myForm.input.$error.required"> 21918 Required!</span> 21919 <span class="error" ng-show="myForm.input.$error.week"> 21920 Not a valid date!</span> 21921 </div> 21922 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 21923 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21924 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21925 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21926 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21927 </form> 21928 </file> 21929 <file name="protractor.js" type="protractor"> 21930 var value = element(by.binding('example.value | date: "yyyy-Www"')); 21931 var valid = element(by.binding('myForm.input.$valid')); 21932 var input = element(by.model('example.value')); 21933 21934 // currently protractor/webdriver does not support 21935 // sending keys to all known HTML5 input controls 21936 // for various browsers (https://github.com/angular/protractor/issues/562). 21937 function setInput(val) { 21938 // set the value of the element and force validation. 21939 var scr = "var ipt = document.getElementById('exampleInput'); " + 21940 "ipt.value = '" + val + "';" + 21941 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21942 browser.executeScript(scr); 21943 } 21944 21945 it('should initialize to model', function() { 21946 expect(value.getText()).toContain('2013-W01'); 21947 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21948 }); 21949 21950 it('should be invalid if empty', function() { 21951 setInput(''); 21952 expect(value.getText()).toEqual('value ='); 21953 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21954 }); 21955 21956 it('should be invalid if over max', function() { 21957 setInput('2015-W01'); 21958 expect(value.getText()).toContain(''); 21959 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21960 }); 21961 </file> 21962 </example> 21963 */ 21964 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 21965 21966 /** 21967 * @ngdoc input 21968 * @name input[month] 21969 * 21970 * @description 21971 * Input with month validation and transformation. In browsers that do not yet support 21972 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21973 * month format (yyyy-MM), for example: `2009-01`. 21974 * 21975 * The model must always be a Date object, otherwise Angular will throw an error. 21976 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21977 * If the model is not set to the first of the month, the next view to model update will set it 21978 * to the first of the month. 21979 * 21980 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21981 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21982 * 21983 * @param {string} ngModel Assignable angular expression to data-bind to. 21984 * @param {string=} name Property name of the form under which the control is published. 21985 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21986 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21987 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 21988 * native HTML5 constraint validation. 21989 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21990 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21991 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 21992 * native HTML5 constraint validation. 21993 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21994 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21995 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21996 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21997 21998 * @param {string=} required Sets `required` validation error key if the value is not entered. 21999 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22000 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22001 * `required` when you want to data-bind to the `required` attribute. 22002 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22003 * interaction with the input element. 22004 * 22005 * @example 22006 <example name="month-input-directive" module="monthExample"> 22007 <file name="index.html"> 22008 <script> 22009 angular.module('monthExample', []) 22010 .controller('DateController', ['$scope', function($scope) { 22011 $scope.example = { 22012 value: new Date(2013, 9, 1) 22013 }; 22014 }]); 22015 </script> 22016 <form name="myForm" ng-controller="DateController as dateCtrl"> 22017 <label for="exampleInput">Pick a month in 2013:</label> 22018 <input id="exampleInput" type="month" name="input" ng-model="example.value" 22019 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 22020 <div role="alert">
22021 <span class="error" ng-show="myForm.input.$error.required"> 22022 Required!</span> 22023 <span class="error" ng-show="myForm.input.$error.month"> 22024 Not a valid month!</span> 22025 </div> 22026 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 22027 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22028 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22029 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22030 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22031 </form> 22032 </file> 22033 <file name="protractor.js" type="protractor"> 22034 var value = element(by.binding('example.value | date: "yyyy-MM"')); 22035 var valid = element(by.binding('myForm.input.$valid')); 22036 var input = element(by.model('example.value')); 22037 22038 // currently protractor/webdriver does not support 22039 // sending keys to all known HTML5 input controls 22040 // for various browsers (https://github.com/angular/protractor/issues/562). 22041 function setInput(val) { 22042 // set the value of the element and force validation. 22043 var scr = "var ipt = document.getElementById('exampleInput'); " + 22044 "ipt.value = '" + val + "';" + 22045 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 22046 browser.executeScript(scr); 22047 } 22048 22049 it('should initialize to model', function() { 22050 expect(value.getText()).toContain('2013-10'); 22051 expect(valid.getText()).toContain('myForm.input.$valid = true'); 22052 }); 22053 22054 it('should be invalid if empty', function() { 22055 setInput(''); 22056 expect(value.getText()).toEqual('value ='); 22057 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22058 }); 22059 22060 it('should be invalid if over max', function() { 22061 setInput('2015-01'); 22062 expect(value.getText()).toContain(''); 22063 expect(valid.getText()).toContain('myForm.input.$valid = false'); 22064 }); 22065 </file> 22066 </example> 22067 */ 22068 'month': createDateInputType('month', MONTH_REGEXP, 22069 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 22070 'yyyy-MM'), 22071 22072 /** 22073 * @ngdoc input 22074 * @name input[number] 22075 * 22076 * @description 22077 * Text input with number validation and transformation. Sets the `number` validation 22078 * error if not a valid number. 22079 * 22080 * <div class="alert alert-warning"> 22081 * The model must always be of type `number` otherwise Angular will throw an error. 22082 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 22083 * error docs for more information and an example of how to convert your model if necessary. 22084 * </div> 22085 * 22086 * ## Issues with HTML5 constraint validation 22087 * 22088 * In browsers that follow the 22089 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 22090 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 22091 * If a non-number is entered in the input, the browser will report the value as an empty string, 22092 * which means the view / model values in `ngModel` and subsequently the scope value 22093 * will also be an empty string. 22094 * 22095 * 22096 * @param {string} ngModel Assignable angular expression to data-bind to. 22097 * @param {string=} name Property name of the form under which the control is published. 22098 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 22099 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 22100 * @param {string=} required Sets `required` validation error key if the value is not entered. 22101 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22102 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22103 * `required` when you want to data-bind to the `required` attribute. 22104 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22105 * minlength. 22106 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22107 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22108 * any length. 22109 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22110 * that contains the regular expression body that will be converted to a regular expression 22111 * as in the ngPattern directive. 22112 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22113 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22114 * If the expression evaluates to a RegExp object, then this is used directly. 22115 * If the expression evaluates to a string, then it will be converted to a RegExp 22116 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22117 * `new RegExp('^abc$')`.<br /> 22118 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22119 * start at the index of the last search's match, thus not taking the whole input value into 22120 * account. 22121 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22122 * interaction with the input element. 22123 * 22124 * @example 22125 <example name="number-input-directive" module="numberExample"> 22126 <file name="index.html"> 22127 <script> 22128 angular.module('numberExample', [])
22129 .controller('ExampleController', ['$scope', function($scope) { 22130 $scope.example = { 22131 value: 12 22132 }; 22133 }]); 22134 </script> 22135 <form name="myForm" ng-controller="ExampleController"> 22136 <label>Number: 22137 <input type="number" name="input" ng-model="example.value" 22138 min="0" max="99" required> 22139 </label> 22140 <div role="alert"> 22141 <span class="error" ng-show="myForm.input.$error.required"> 22142 Required!</span> 22143 <span class="error" ng-show="myForm.input.$error.number"> 22144 Not valid number!</span> 22145 </div> 22146 <tt>value = {{example.value}}</tt><br/> 22147 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22148 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22149 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22150 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22151 </form> 22152 </file> 22153 <file name="protractor.js" type="protractor"> 22154 var value = element(by.binding('example.value')); 22155 var valid = element(by.binding('myForm.input.$valid')); 22156 var input = element(by.model('example.value')); 22157 22158 it('should initialize to model', function() { 22159 expect(value.getText()).toContain('12'); 22160 expect(valid.getText()).toContain('true'); 22161 }); 22162 22163 it('should be invalid if empty', function() { 22164 input.clear(); 22165 input.sendKeys(''); 22166 expect(value.getText()).toEqual('value ='); 22167 expect(valid.getText()).toContain('false'); 22168 }); 22169 22170 it('should be invalid if over max', function() { 22171 input.clear(); 22172 input.sendKeys('123'); 22173 expect(value.getText()).toEqual('value ='); 22174 expect(valid.getText()).toContain('false'); 22175 }); 22176 </file> 22177 </example> 22178 */ 22179 'number': numberInputType, 22180 22181 22182 /** 22183 * @ngdoc input 22184 * @name input[url] 22185 * 22186 * @description 22187 * Text input with URL validation. Sets the `url` validation error key if the content is not a 22188 * valid URL. 22189 * 22190 * <div class="alert alert-warning"> 22191 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 22192 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 22193 * the built-in validators (see the {@link guide/forms Forms guide}) 22194 * </div> 22195 * 22196 * @param {string} ngModel Assignable angular expression to data-bind to. 22197 * @param {string=} name Property name of the form under which the control is published. 22198 * @param {string=} required Sets `required` validation error key if the value is not entered. 22199 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22200 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22201 * `required` when you want to data-bind to the `required` attribute. 22202 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22203 * minlength. 22204 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22205 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22206 * any length. 22207 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22208 * that contains the regular expression body that will be converted to a regular expression 22209 * as in the ngPattern directive. 22210 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22211 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22212 * If the expression evaluates to a RegExp object, then this is used directly. 22213 * If the expression evaluates to a string, then it will be converted to a RegExp 22214 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22215 * `new RegExp('^abc$')`.<br /> 22216 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22217 * start at the index of the last search's match, thus not taking the whole input value into 22218 * account. 22219 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22220 * interaction with the input element. 22221 * 22222 * @example 22223 <example name="url-input-directive" module="urlExample"> 22224 <file name="index.html"> 22225 <script> 22226 angular.module('urlExample', [])
22227 .controller('ExampleController', ['$scope', function($scope) { 22228 $scope.url = { 22229 text: 'http://google.com' 22230 }; 22231 }]); 22232 </script> 22233 <form name="myForm" ng-controller="ExampleController"> 22234 <label>URL: 22235 <input type="url" name="input" ng-model="url.text" required> 22236 <label> 22237 <div role="alert"> 22238 <span class="error" ng-show="myForm.input.$error.required"> 22239 Required!</span> 22240 <span class="error" ng-show="myForm.input.$error.url"> 22241 Not valid url!</span> 22242 </div> 22243 <tt>text = {{url.text}}</tt><br/> 22244 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22245 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22246 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22247 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22248 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 22249 </form> 22250 </file> 22251 <file name="protractor.js" type="protractor"> 22252 var text = element(by.binding('url.text')); 22253 var valid = element(by.binding('myForm.input.$valid')); 22254 var input = element(by.model('url.text')); 22255 22256 it('should initialize to model', function() { 22257 expect(text.getText()).toContain('http://google.com'); 22258 expect(valid.getText()).toContain('true'); 22259 }); 22260 22261 it('should be invalid if empty', function() { 22262 input.clear(); 22263 input.sendKeys(''); 22264 22265 expect(text.getText()).toEqual('text ='); 22266 expect(valid.getText()).toContain('false'); 22267 }); 22268 22269 it('should be invalid if not url', function() { 22270 input.clear(); 22271 input.sendKeys('box'); 22272 22273 expect(valid.getText()).toContain('false'); 22274 }); 22275 </file> 22276 </example> 22277 */ 22278 'url': urlInputType, 22279 22280 22281 /** 22282 * @ngdoc input 22283 * @name input[email] 22284 * 22285 * @description 22286 * Text input with email validation. Sets the `email` validation error key if not a valid email 22287 * address. 22288 * 22289 * <div class="alert alert-warning"> 22290 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 22291 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 22292 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 22293 * </div> 22294 * 22295 * @param {string} ngModel Assignable angular expression to data-bind to. 22296 * @param {string=} name Property name of the form under which the control is published. 22297 * @param {string=} required Sets `required` validation error key if the value is not entered. 22298 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22299 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22300 * `required` when you want to data-bind to the `required` attribute. 22301 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22302 * minlength. 22303 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22304 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 22305 * any length. 22306 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 22307 * that contains the regular expression body that will be converted to a regular expression 22308 * as in the ngPattern directive. 22309 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22310 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22311 * If the expression evaluates to a RegExp object, then this is used directly. 22312 * If the expression evaluates to a string, then it will be converted to a RegExp 22313 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22314 * `new RegExp('^abc$')`.<br /> 22315 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22316 * start at the index of the last search's match, thus not taking the whole input value into 22317 * account. 22318 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22319 * interaction with the input element. 22320 * 22321 * @example 22322 <example name="email-input-directive" module="emailExample"> 22323 <file name="index.html"> 22324 <script> 22325 angular.module('emailExample', [])
22326 .controller('ExampleController', ['$scope', function($scope) { 22327 $scope.email = { 22328 text: '[email protected]' 22329 }; 22330 }]); 22331 </script> 22332 <form name="myForm" ng-controller="ExampleController"> 22333 <label>Email: 22334 <input type="email" name="input" ng-model="email.text" required> 22335 </label> 22336 <div role="alert"> 22337 <span class="error" ng-show="myForm.input.$error.required"> 22338 Required!</span> 22339 <span class="error" ng-show="myForm.input.$error.email"> 22340 Not valid email!</span> 22341 </div> 22342 <tt>text = {{email.text}}</tt><br/> 22343 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 22344 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 22345 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22346 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22347 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 22348 </form> 22349 </file> 22350 <file name="protractor.js" type="protractor"> 22351 var text = element(by.binding('email.text')); 22352 var valid = element(by.binding('myForm.input.$valid')); 22353 var input = element(by.model('email.text')); 22354 22355 it('should initialize to model', function() { 22356 expect(text.getText()).toContain('[email protected]'); 22357 expect(valid.getText()).toContain('true'); 22358 }); 22359 22360 it('should be invalid if empty', function() { 22361 input.clear(); 22362 input.sendKeys(''); 22363 expect(text.getText()).toEqual('text ='); 22364 expect(valid.getText()).toContain('false'); 22365 }); 22366 22367 it('should be invalid if not email', function() { 22368 input.clear(); 22369 input.sendKeys('xxx'); 22370 22371 expect(valid.getText()).toContain('false'); 22372 }); 22373 </file> 22374 </example> 22375 */ 22376 'email': emailInputType, 22377 22378 22379 /** 22380 * @ngdoc input 22381 * @name input[radio] 22382 * 22383 * @description 22384 * HTML radio button. 22385 * 22386 * @param {string} ngModel Assignable angular expression to data-bind to. 22387 * @param {string} value The value to which the `ngModel` expression should be set when selected. 22388 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 22389 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 22390 * @param {string=} name Property name of the form under which the control is published. 22391 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22392 * interaction with the input element. 22393 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 22394 * is selected. Should be used instead of the `value` attribute if you need 22395 * a non-string `ngModel` (`boolean`, `array`, ...). 22396 * 22397 * @example 22398 <example name="radio-input-directive" module="radioExample"> 22399 <file name="index.html"> 22400 <script> 22401 angular.module('radioExample', []) 22402 .controller('ExampleController', ['$scope', function($scope) { 22403 $scope.color = { 22404 name: 'blue' 22405 }; 22406 $scope.specialValue = { 22407 "id": "12345", 22408 "value": "green" 22409 }; 22410 }]); 22411 </script>
22412 <form name="myForm" ng-controller="ExampleController"> 22413 <label> 22414 <input type="radio" ng-model="color.name" value="red"> 22415 Red 22416 </label><br/> 22417 <label> 22418 <input type="radio" ng-model="color.name" ng-value="specialValue"> 22419 Green 22420 </label><br/> 22421 <label> 22422 <input type="radio" ng-model="color.name" value="blue"> 22423 Blue 22424 </label><br/> 22425 <tt>color = {{color.name | json}}</tt><br/> 22426 </form> 22427 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 22428 </file> 22429 <file name="protractor.js" type="protractor"> 22430 it('should change state', function() { 22431 var color = element(by.binding('color.name')); 22432 22433 expect(color.getText()).toContain('blue'); 22434 22435 element.all(by.model('color.name')).get(0).click(); 22436 22437 expect(color.getText()).toContain('red'); 22438 }); 22439 </file> 22440 </example> 22441 */ 22442 'radio': radioInputType, 22443 22444 22445 /** 22446 * @ngdoc input 22447 * @name input[checkbox] 22448 * 22449 * @description 22450 * HTML checkbox. 22451 * 22452 * @param {string} ngModel Assignable angular expression to data-bind to. 22453 * @param {string=} name Property name of the form under which the control is published. 22454 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 22455 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 22456 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22457 * interaction with the input element. 22458 * 22459 * @example 22460 <example name="checkbox-input-directive" module="checkboxExample"> 22461 <file name="index.html"> 22462 <script> 22463 angular.module('checkboxExample', []) 22464 .controller('ExampleController', ['$scope', function($scope) {
22465 $scope.checkboxModel = { 22466 value1 : true, 22467 value2 : 'YES' 22468 }; 22469 }]); 22470 </script> 22471 <form name="myForm" ng-controller="ExampleController"> 22472 <label>Value1: 22473 <input type="checkbox" ng-model="checkboxModel.value1"> 22474 </label><br/> 22475 <label>Value2: 22476 <input type="checkbox" ng-model="checkboxModel.value2" 22477 ng-true-value="'YES'" ng-false-value="'NO'"> 22478 </label><br/> 22479 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 22480 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 22481 </form> 22482 </file> 22483 <file name="protractor.js" type="protractor"> 22484 it('should change state', function() { 22485 var value1 = element(by.binding('checkboxModel.value1')); 22486 var value2 = element(by.binding('checkboxModel.value2')); 22487 22488 expect(value1.getText()).toContain('true'); 22489 expect(value2.getText()).toContain('YES'); 22490 22491 element(by.model('checkboxModel.value1')).click(); 22492 element(by.model('checkboxModel.value2')).click(); 22493 22494 expect(value1.getText()).toContain('false'); 22495 expect(value2.getText()).toContain('NO'); 22496 }); 22497 </file> 22498 </example> 22499 */ 22500 'checkbox': checkboxInputType, 22501 22502 'hidden': noop, 22503 'button': noop, 22504 'submit': noop, 22505 'reset': noop, 22506 'file': noop 22507}; 22508 22509function stringBasedInputType(ctrl) { 22510 ctrl.$formatters.push(function(value) { 22511 return ctrl.$isEmpty(value) ? value : value.toString(); 22512 }); 22513} 22514 22515function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22516 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22517 stringBasedInputType(ctrl); 22518} 22519 22520function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22521 var type = lowercase(element[0].type); 22522 22523 // In composition mode, users are still inputing intermediate text buffer, 22524 // hold the listener until composition is done. 22525 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 22526 if (!$sniffer.android) { 22527 var composing = false; 22528 22529 element.on('compositionstart', function(data) { 22530 composing = true; 22531 }); 22532 22533 element.on('compositionend', function() { 22534 composing = false; 22535 listener(); 22536 }); 22537 } 22538 22539 var listener = function(ev) { 22540 if (timeout) { 22541 $browser.defer.cancel(timeout); 22542 timeout = null; 22543 } 22544 if (composing) return; 22545 var value = element.val(), 22546 event = ev && ev.type; 22547 22548 // By default we will trim the value 22549 // If the attribute ng-trim exists we will avoid trimming 22550 // If input type is 'password', the value is never trimmed 22551 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 22552 value = trim(value); 22553 } 22554 22555 // If a control is suffering from bad input (due to native validators), browsers discard its 22556 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 22557 // control's value is the same empty value twice in a row. 22558 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 22559 ctrl.$setViewValue(value, event); 22560 } 22561 }; 22562 22563 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 22564 // input event on backspace, delete or cut 22565 if ($sniffer.hasEvent('input')) { 22566 element.on('input', listener); 22567 } else { 22568 var timeout; 22569 22570 var deferListener = function(ev, input, origValue) { 22571 if (!timeout) { 22572 timeout = $browser.defer(function() { 22573 timeout = null; 22574 if (!input || input.value !== origValue) { 22575 listener(ev); 22576 } 22577 }); 22578 } 22579 }; 22580 22581 element.on('keydown', function(event) { 22582 var key = event.keyCode; 22583 22584 // ignore 22585 // command modifiers arrows 22586 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 22587 22588 deferListener(event, this, this.value); 22589 });
22590 22591 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 22592 if ($sniffer.hasEvent('paste')) { 22593 element.on('paste cut', deferListener); 22594 } 22595 } 22596 22597 // if user paste into input using mouse on older browser 22598 // or form autocomplete on newer browser, we need "change" event to catch it 22599 element.on('change', listener); 22600 22601 ctrl.$render = function() { 22602 // Workaround for Firefox validation #12102. 22603 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 22604 if (element.val() !== value) { 22605 element.val(value); 22606 } 22607 }; 22608} 22609 22610function weekParser(isoWeek, existingDate) { 22611 if (isDate(isoWeek)) { 22612 return isoWeek; 22613 } 22614 22615 if (isString(isoWeek)) { 22616 WEEK_REGEXP.lastIndex = 0; 22617 var parts = WEEK_REGEXP.exec(isoWeek); 22618 if (parts) { 22619 var year = +parts[1], 22620 week = +parts[2], 22621 hours = 0, 22622 minutes = 0, 22623 seconds = 0, 22624 milliseconds = 0, 22625 firstThurs = getFirstThursdayOfYear(year), 22626 addDays = (week - 1) * 7; 22627 22628 if (existingDate) { 22629 hours = existingDate.getHours(); 22630 minutes = existingDate.getMinutes(); 22631 seconds = existingDate.getSeconds(); 22632 milliseconds = existingDate.getMilliseconds(); 22633 } 22634 22635 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 22636 } 22637 } 22638 22639 return NaN; 22640} 22641 22642function createDateParser(regexp, mapping) { 22643 return function(iso, date) { 22644 var parts, map; 22645 22646 if (isDate(iso)) { 22647 return iso; 22648 } 22649 22650 if (isString(iso)) { 22651 // When a date is JSON'ified to wraps itself inside of an extra 22652 // set of double quotes. This makes the date parsing code unable 22653 // to match the date string and parse it as a date. 22654 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 22655 iso = iso.substring(1, iso.length - 1); 22656 } 22657 if (ISO_DATE_REGEXP.test(iso)) { 22658 return new Date(iso); 22659 } 22660 regexp.lastIndex = 0; 22661 parts = regexp.exec(iso); 22662 22663 if (parts) { 22664 parts.shift(); 22665 if (date) { 22666 map = { 22667 yyyy: date.getFullYear(), 22668 MM: date.getMonth() + 1, 22669 dd: date.getDate(), 22670 HH: date.getHours(), 22671 mm: date.getMinutes(), 22672 ss: date.getSeconds(), 22673 sss: date.getMilliseconds() / 1000 22674 }; 22675 } else { 22676 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 22677 } 22678 22679 forEach(parts, function(part, index) { 22680 if (index < mapping.length) { 22681 map[mapping[index]] = +part; 22682 } 22683 }); 22684 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 22685 } 22686 } 22687 22688 return NaN; 22689 }; 22690} 22691 22692function createDateInputType(type, regexp, parseDate, format) { 22693 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 22694 badInputChecker(scope, element, attr, ctrl); 22695 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22696 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 22697 var previousDate; 22698 22699 ctrl.$$parserName = type; 22700 ctrl.$parsers.push(function(value) { 22701 if (ctrl.$isEmpty(value)) return null; 22702 if (regexp.test(value)) { 22703 // Note: We cannot read ctrl.$modelValue, as there might be a different 22704 // parser/formatter in the processing chain so that the model 22705 // contains some different data format! 22706 var parsedDate = parseDate(value, previousDate); 22707 if (timezone) { 22708 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 22709 } 22710 return parsedDate; 22711 } 22712 return undefined; 22713 }); 22714 22715 ctrl.$formatters.push(function(value) { 22716 if (value && !isDate(value)) { 22717 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 22718 } 22719 if (isValidDate(value)) { 22720 previousDate = value; 22721 if (previousDate && timezone) { 22722 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 22723 } 22724 return $filter('date')(value, format, timezone); 22725 } else { 22726 previousDate = null; 22727 return ''; 22728 } 22729 });
22730 22731 if (isDefined(attr.min) || attr.ngMin) { 22732 var minVal; 22733 ctrl.$validators.min = function(value) { 22734 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 22735 }; 22736 attr.$observe('min', function(val) { 22737 minVal = parseObservedDateValue(val); 22738 ctrl.$validate(); 22739 }); 22740 } 22741 22742 if (isDefined(attr.max) || attr.ngMax) { 22743 var maxVal; 22744 ctrl.$validators.max = function(value) { 22745 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 22746 }; 22747 attr.$observe('max', function(val) { 22748 maxVal = parseObservedDateValue(val); 22749 ctrl.$validate(); 22750 }); 22751 } 22752 22753 function isValidDate(value) { 22754 // Invalid Date: getTime() returns NaN 22755 return value && !(value.getTime && value.getTime() !== value.getTime()); 22756 } 22757 22758 function parseObservedDateValue(val) { 22759 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 22760 } 22761 }; 22762} 22763 22764function badInputChecker(scope, element, attr, ctrl) { 22765 var node = element[0]; 22766 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 22767 if (nativeValidation) { 22768 ctrl.$parsers.push(function(value) { 22769 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 22770 return validity.badInput || validity.typeMismatch ? undefined : value; 22771 }); 22772 } 22773} 22774 22775function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22776 badInputChecker(scope, element, attr, ctrl); 22777 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22778 22779 ctrl.$$parserName = 'number'; 22780 ctrl.$parsers.push(function(value) { 22781 if (ctrl.$isEmpty(value)) return null; 22782 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 22783 return undefined; 22784 }); 22785 22786 ctrl.$formatters.push(function(value) { 22787 if (!ctrl.$isEmpty(value)) { 22788 if (!isNumber(value)) { 22789 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 22790 } 22791 value = value.toString(); 22792 } 22793 return value; 22794 }); 22795 22796 if (isDefined(attr.min) || attr.ngMin) { 22797 var minVal; 22798 ctrl.$validators.min = function(value) { 22799 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 22800 }; 22801 22802 attr.$observe('min', function(val) { 22803 if (isDefined(val) && !isNumber(val)) { 22804 val = parseFloat(val, 10); 22805 } 22806 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 22807 // TODO(matsko): implement validateLater to reduce number of validations 22808 ctrl.$validate(); 22809 }); 22810 } 22811 22812 if (isDefined(attr.max) || attr.ngMax) { 22813 var maxVal; 22814 ctrl.$validators.max = function(value) { 22815 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 22816 }; 22817 22818 attr.$observe('max', function(val) { 22819 if (isDefined(val) && !isNumber(val)) { 22820 val = parseFloat(val, 10); 22821 } 22822 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 22823 // TODO(matsko): implement validateLater to reduce number of validations 22824 ctrl.$validate(); 22825 }); 22826 } 22827} 22828 22829function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22830 // Note: no badInputChecker here by purpose as `url` is only a validation 22831 // in browsers, i.e. we can always read out input.value even if it is not valid! 22832 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22833 stringBasedInputType(ctrl); 22834 22835 ctrl.$$parserName = 'url'; 22836 ctrl.$validators.url = function(modelValue, viewValue) { 22837 var value = modelValue || viewValue; 22838 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 22839 }; 22840} 22841 22842function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22843 // Note: no badInputChecker here by purpose as `url` is only a validation 22844 // in browsers, i.e. we can always read out input.value even if it is not valid! 22845 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22846 stringBasedInputType(ctrl); 22847 22848 ctrl.$$parserName = 'email'; 22849 ctrl.$validators.email = function(modelValue, viewValue) { 22850 var value = modelValue || viewValue; 22851 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 22852 }; 22853} 22854 22855function radioInputType(scope, element, attr, ctrl) { 22856 // make the name unique, if not defined 22857 if (isUndefined(attr.name)) { 22858 element.attr('name', nextUid()); 22859 } 22860 22861 var listener = function(ev) { 22862 if (element[0].checked) { 22863 ctrl.$setViewValue(attr.value, ev && ev.type); 22864 } 22865 }; 22866 22867 element.on('click', listener); 22868 22869 ctrl.$render = function() { 22870 var value = attr.value; 22871 element[0].checked = (value == ctrl.$viewValue); 22872 }; 22873 22874 attr.$observe('value', ctrl.$render); 22875} 22876 22877function parseConstantExpr($parse, context, name, expression, fallback) { 22878 var parseFn; 22879 if (isDefined(expression)) { 22880 parseFn = $parse(expression); 22881 if (!parseFn.constant) { 22882 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 22883 '`{1}`.', name, expression); 22884 } 22885 return parseFn(context); 22886 } 22887 return fallback; 22888} 22889 22890function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 22891 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 22892 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 22893 22894 var listener = function(ev) { 22895 ctrl.$setViewValue(element[0].checked, ev && ev.type); 22896 }; 22897 22898 element.on('click', listener); 22899 22900 ctrl.$render = function() { 22901 element[0].checked = ctrl.$viewValue; 22902 }; 22903
22904 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 22905 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 22906 // it to a boolean. 22907 ctrl.$isEmpty = function(value) { 22908 return value === false; 22909 }; 22910 22911 ctrl.$formatters.push(function(value) { 22912 return equals(value, trueValue); 22913 }); 22914 22915 ctrl.$parsers.push(function(value) { 22916 return value ? trueValue : falseValue; 22917 }); 22918} 22919 22920 22921/** 22922 * @ngdoc directive 22923 * @name textarea 22924 * @restrict E 22925 * 22926 * @description 22927 * HTML textarea element control with angular data-binding. The data-binding and validation 22928 * properties of this element are exactly the same as those of the 22929 * {@link ng.directive:input input element}. 22930 * 22931 * @param {string} ngModel Assignable angular expression to data-bind to. 22932 * @param {string=} name Property name of the form under which the control is published. 22933 * @param {string=} required Sets `required` validation error key if the value is not entered. 22934 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22935 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22936 * `required` when you want to data-bind to the `required` attribute. 22937 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22938 * minlength. 22939 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22940 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22941 * length. 22942 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22943 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22944 * If the expression evaluates to a RegExp object, then this is used directly. 22945 * If the expression evaluates to a string, then it will be converted to a RegExp 22946 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22947 * `new RegExp('^abc$')`.<br /> 22948 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22949 * start at the index of the last search's match, thus not taking the whole input value into 22950 * account. 22951 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22952 * interaction with the input element. 22953 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22954 */ 22955 22956 22957/** 22958 * @ngdoc directive 22959 * @name input 22960 * @restrict E 22961 * 22962 * @description 22963 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 22964 * input state control, and validation. 22965 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 22966 * 22967 * <div class="alert alert-warning"> 22968 * **Note:** Not every feature offered is available for all input types. 22969 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 22970 * </div> 22971 * 22972 * @param {string} ngModel Assignable angular expression to data-bind to. 22973 * @param {string=} name Property name of the form under which the control is published. 22974 * @param {string=} required Sets `required` validation error key if the value is not entered. 22975 * @param {boolean=} ngRequired Sets `required` attribute if set to true 22976 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22977 * minlength. 22978 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22979 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22980 * length. 22981 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 22982 * value does not match a RegExp found by evaluating the Angular expression given in the attribute value. 22983 * If the expression evaluates to a RegExp object, then this is used directly. 22984 * If the expression evaluates to a string, then it will be converted to a RegExp 22985 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22986 * `new RegExp('^abc$')`.<br /> 22987 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22988 * start at the index of the last search's match, thus not taking the whole input value into 22989 * account. 22990 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22991 * interaction with the input element. 22992 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22993 * This parameter is ignored for input[type=password] controls, which will never trim the 22994 * input. 22995 * 22996 * @example 22997 <example name="input-directive" module="inputExample"> 22998 <file name="index.html"> 22999 <script> 23000 angular.module('inputExample', [])
23001 .controller('ExampleController', ['$scope', function($scope) { 23002 $scope.user = {name: 'guest', last: 'visitor'}; 23003 }]); 23004 </script> 23005 <div ng-controller="ExampleController"> 23006 <form name="myForm"> 23007 <label> 23008 User name: 23009 <input type="text" name="userName" ng-model="user.name" required> 23010 </label> 23011 <div role="alert"> 23012 <span class="error" ng-show="myForm.userName.$error.required"> 23013 Required!</span> 23014 </div> 23015 <label> 23016 Last name: 23017 <input type="text" name="lastName" ng-model="user.last" 23018 ng-minlength="3" ng-maxlength="10"> 23019 </label> 23020 <div role="alert"> 23021 <span class="error" ng-show="myForm.lastName.$error.minlength"> 23022 Too short!</span> 23023 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 23024 Too long!</span> 23025 </div> 23026 </form> 23027 <hr> 23028 <tt>user = {{user}}</tt><br/> 23029 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 23030 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 23031 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 23032 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 23033 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 23034 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 23035 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 23036 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 23037 </div> 23038 </file> 23039 <file name="protractor.js" type="protractor"> 23040 var user = element(by.exactBinding('user')); 23041 var userNameValid = element(by.binding('myForm.userName.$valid')); 23042 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 23043 var lastNameError = element(by.binding('myForm.lastName.$error')); 23044 var formValid = element(by.binding('myForm.$valid')); 23045 var userNameInput = element(by.model('user.name')); 23046 var userLastInput = element(by.model('user.last')); 23047 23048 it('should initialize to model', function() {
23049 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 23050 expect(userNameValid.getText()).toContain('true'); 23051 expect(formValid.getText()).toContain('true'); 23052 }); 23053 23054 it('should be invalid if empty when required', function() { 23055 userNameInput.clear(); 23056 userNameInput.sendKeys(''); 23057 23058 expect(user.getText()).toContain('{"last":"visitor"}'); 23059 expect(userNameValid.getText()).toContain('false'); 23060 expect(formValid.getText()).toContain('false'); 23061 }); 23062 23063 it('should be valid if empty when min length is set', function() { 23064 userLastInput.clear(); 23065 userLastInput.sendKeys(''); 23066 23067 expect(user.getText()).toContain('{"name":"guest","last":""}'); 23068 expect(lastNameValid.getText()).toContain('true'); 23069 expect(formValid.getText()).toContain('true'); 23070 }); 23071 23072 it('should be invalid if less than required min length', function() { 23073 userLastInput.clear(); 23074 userLastInput.sendKeys('xx'); 23075
23076 expect(user.getText()).toContain('{"name":"guest"}'); 23077 expect(lastNameValid.getText()).toContain('false'); 23078 expect(lastNameError.getText()).toContain('minlength'); 23079 expect(formValid.getText()).toContain('false'); 23080 }); 23081 23082 it('should be invalid if longer than max length', function() { 23083 userLastInput.clear(); 23084 userLastInput.sendKeys('some ridiculously long name'); 23085 23086 expect(user.getText()).toContain('{"name":"guest"}'); 23087 expect(lastNameValid.getText()).toContain('false'); 23088 expect(lastNameError.getText()).toContain('maxlength'); 23089 expect(formValid.getText()).toContain('false'); 23090 }); 23091 </file> 23092 </example> 23093 */ 23094var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 23095 function($browser, $sniffer, $filter, $parse) { 23096 return { 23097 restrict: 'E', 23098 require: ['?ngModel'], 23099 link: { 23100 pre: function(scope, element, attr, ctrls) { 23101 if (ctrls[0]) { 23102 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 23103 $browser, $filter, $parse); 23104 } 23105 } 23106 } 23107 }; 23108}]; 23109 23110 23111 23112var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 23113/** 23114 * @ngdoc directive 23115 * @name ngValue 23116 * 23117 * @description 23118 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 23119 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 23120 * the bound value. 23121 * 23122 * `ngValue` is useful when dynamically generating lists of radio buttons using 23123 * {@link ngRepeat `ngRepeat`}, as shown below. 23124 * 23125 * Likewise, `ngValue` can be used to generate `<option>` elements for 23126 * the {@link select `select`} element. In that case however, only strings are supported 23127 * for the `value `attribute, so the resulting `ngModel` will always be a string. 23128 * Support for `select` models with non-string values is available via `ngOptions`. 23129 * 23130 * @element input 23131 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 23132 * of the `input` element 23133 * 23134 * @example 23135 <example name="ngValue-directive" module="valueExample"> 23136 <file name="index.html"> 23137 <script> 23138 angular.module('valueExample', []) 23139 .controller('ExampleController', ['$scope', function($scope) { 23140 $scope.names = ['pizza', 'unicorns', 'robots']; 23141 $scope.my = { favorite: 'unicorns' }; 23142 }]); 23143 </script>
23144 <form ng-controller="ExampleController"> 23145 <h2>Which is your favorite?</h2> 23146 <label ng-repeat="name in names" for="{{name}}"> 23147 {{name}} 23148 <input type="radio" 23149 ng-model="my.favorite" 23150 ng-value="name" 23151 id="{{name}}" 23152 name="favorite"> 23153 </label> 23154 <div>You chose {{my.favorite}}</div> 23155 </form> 23156 </file> 23157 <file name="protractor.js" type="protractor"> 23158 var favorite = element(by.binding('my.favorite')); 23159 23160 it('should initialize to model', function() { 23161 expect(favorite.getText()).toContain('unicorns'); 23162 }); 23163 it('should bind the values to the inputs', function() { 23164 element.all(by.model('my.favorite')).get(0).click(); 23165 expect(favorite.getText()).toContain('pizza'); 23166 }); 23167 </file> 23168 </example> 23169 */ 23170var ngValueDirective = function() { 23171 return { 23172 restrict: 'A', 23173 priority: 100, 23174 compile: function(tpl, tplAttr) { 23175 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 23176 return function ngValueConstantLink(scope, elm, attr) { 23177 attr.$set('value', scope.$eval(attr.ngValue)); 23178 }; 23179 } else { 23180 return function ngValueLink(scope, elm, attr) { 23181 scope.$watch(attr.ngValue, function valueWatchAction(value) { 23182 attr.$set('value', value); 23183 }); 23184 }; 23185 } 23186 } 23187 }; 23188}; 23189 23190/** 23191 * @ngdoc directive 23192 * @name ngBind 23193 * @restrict AC 23194 * 23195 * @description 23196 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 23197 * with the value of a given expression, and to update the text content when the value of that 23198 * expression changes. 23199 * 23200 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 23201 * `{{ expression }}` which is similar but less verbose. 23202 * 23203 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 23204 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 23205 * element attribute, it makes the bindings invisible to the user while the page is loading. 23206 * 23207 * An alternative solution to this problem would be using the 23208 * {@link ng.directive:ngCloak ngCloak} directive. 23209 * 23210 * 23211 * @element ANY 23212 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 23213 * 23214 * @example 23215 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 23216 <example module="bindExample"> 23217 <file name="index.html"> 23218 <script> 23219 angular.module('bindExample', []) 23220 .controller('ExampleController', ['$scope', function($scope) { 23221 $scope.name = 'Whirled'; 23222 }]); 23223 </script>
23224 <div ng-controller="ExampleController"> 23225 <label>Enter name: <input type="text" ng-model="name"></label><br> 23226 Hello <span ng-bind="name"></span>! 23227 </div> 23228 </file> 23229 <file name="protractor.js" type="protractor"> 23230 it('should check ng-bind', function() { 23231 var nameInput = element(by.model('name')); 23232 23233 expect(element(by.binding('name')).getText()).toBe('Whirled'); 23234 nameInput.clear(); 23235 nameInput.sendKeys('world'); 23236 expect(element(by.binding('name')).getText()).toBe('world'); 23237 }); 23238 </file> 23239 </example> 23240 */ 23241var ngBindDirective = ['$compile', function($compile) { 23242 return { 23243 restrict: 'AC', 23244 compile: function ngBindCompile(templateElement) { 23245 $compile.$$addBindingClass(templateElement); 23246 return function ngBindLink(scope, element, attr) { 23247 $compile.$$addBindingInfo(element, attr.ngBind); 23248 element = element[0]; 23249 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 23250 element.textContent = isUndefined(value) ? '' : value; 23251 }); 23252 }; 23253 } 23254 }; 23255}]; 23256 23257 23258/** 23259 * @ngdoc directive 23260 * @name ngBindTemplate 23261 * 23262 * @description 23263 * The `ngBindTemplate` directive specifies that the element 23264 * text content should be replaced with the interpolation of the template 23265 * in the `ngBindTemplate` attribute. 23266 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 23267 * expressions. This directive is needed since some HTML elements 23268 * (such as TITLE and OPTION) cannot contain SPAN elements. 23269 * 23270 * @element ANY 23271 * @param {string} ngBindTemplate template of form 23272 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 23273 * 23274 * @example 23275 * Try it here: enter text in text box and watch the greeting change. 23276 <example module="bindExample"> 23277 <file name="index.html"> 23278 <script> 23279 angular.module('bindExample', []) 23280 .controller('ExampleController', ['$scope', function($scope) { 23281 $scope.salutation = 'Hello'; 23282 $scope.name = 'World'; 23283 }]); 23284 </script>
23285 <div ng-controller="ExampleController"> 23286 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 23287 <label>Name: <input type="text" ng-model="name"></label><br> 23288 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 23289 </div> 23290 </file> 23291 <file name="protractor.js" type="protractor"> 23292 it('should check ng-bind', function() { 23293 var salutationElem = element(by.binding('salutation')); 23294 var salutationInput = element(by.model('salutation')); 23295 var nameInput = element(by.model('name')); 23296 23297 expect(salutationElem.getText()).toBe('Hello World!'); 23298 23299 salutationInput.clear(); 23300 salutationInput.sendKeys('Greetings'); 23301 nameInput.clear(); 23302 nameInput.sendKeys('user'); 23303 23304 expect(salutationElem.getText()).toBe('Greetings user!'); 23305 }); 23306 </file> 23307 </example> 23308 */ 23309var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 23310 return { 23311 compile: function ngBindTemplateCompile(templateElement) { 23312 $compile.$$addBindingClass(templateElement); 23313 return function ngBindTemplateLink(scope, element, attr) { 23314 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 23315 $compile.$$addBindingInfo(element, interpolateFn.expressions); 23316 element = element[0]; 23317 attr.$observe('ngBindTemplate', function(value) { 23318 element.textContent = isUndefined(value) ? '' : value; 23319 }); 23320 }; 23321 } 23322 }; 23323}]; 23324 23325 23326/** 23327 * @ngdoc directive 23328 * @name ngBindHtml 23329 * 23330 * @description 23331 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 23332 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 23333 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 23334 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 23335 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 23336 * 23337 * You may also bypass sanitization for values you know are safe. To do so, bind to 23338 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 23339 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 23340 * 23341 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 23342 * will have an exception (instead of an exploit.) 23343 * 23344 * @element ANY 23345 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 23346 * 23347 * @example 23348 23349 <example module="bindHtmlExample" deps="angular-sanitize.js"> 23350 <file name="index.html">
23351 <div ng-controller="ExampleController"> 23352 <p ng-bind-html="myHTML"></p> 23353 </div> 23354 </file> 23355 23356 <file name="script.js"> 23357 angular.module('bindHtmlExample', ['ngSanitize']) 23358 .controller('ExampleController', ['$scope', function($scope) { 23359 $scope.myHTML = 23360 'I am an <code>HTML</code>string with ' + 23361 '<a href="#">links!</a> and other <em>stuff</em>'; 23362 }]); 23363 </file> 23364 23365 <file name="protractor.js" type="protractor"> 23366 it('should check ng-bind-html', function() { 23367 expect(element(by.binding('myHTML')).getText()).toBe( 23368 'I am an HTMLstring with links! and other stuff'); 23369 }); 23370 </file> 23371 </example> 23372 */ 23373var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 23374 return { 23375 restrict: 'A', 23376 compile: function ngBindHtmlCompile(tElement, tAttrs) { 23377 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 23378 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 23379 return (value || '').toString(); 23380 }); 23381 $compile.$$addBindingClass(tElement); 23382 23383 return function ngBindHtmlLink(scope, element, attr) { 23384 $compile.$$addBindingInfo(element, attr.ngBindHtml); 23385 23386 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 23387 // we re-evaluate the expr because we want a TrustedValueHolderType 23388 // for $sce, not a string 23389 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 23390 }); 23391 }; 23392 } 23393 }; 23394}]; 23395 23396/** 23397 * @ngdoc directive 23398 * @name ngChange 23399 * 23400 * @description 23401 * Evaluate the given expression when the user changes the input. 23402 * The expression is evaluated immediately, unlike the JavaScript onchange event 23403 * which only triggers at the end of a change (usually, when the user leaves the 23404 * form element or presses the return key). 23405 * 23406 * The `ngChange` expression is only evaluated when a change in the input value causes 23407 * a new value to be committed to the model. 23408 * 23409 * It will not be evaluated: 23410 * * if the value returned from the `$parsers` transformation pipeline has not changed 23411 * * if the input has continued to be invalid since the model will stay `null` 23412 * * if the model is changed programmatically and not by a change to the input value 23413 * 23414 * 23415 * Note, this directive requires `ngModel` to be present. 23416 * 23417 * @element input 23418 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 23419 * in input value. 23420 * 23421 * @example 23422 * <example name="ngChange-directive" module="changeExample"> 23423 * <file name="index.html"> 23424 * <script> 23425 * angular.module('changeExample', []) 23426 * .controller('ExampleController', ['$scope', function($scope) { 23427 * $scope.counter = 0; 23428 * $scope.change = function() { 23429 * $scope.counter++; 23430 * }; 23431 * }]); 23432 * </script>
23433 * <div ng-controller="ExampleController"> 23434 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 23435 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 23436 * <label for="ng-change-example2">Confirmed</label><br /> 23437 * <tt>debug = {{confirmed}}</tt><br/> 23438 * <tt>counter = {{counter}}</tt><br/> 23439 * </div> 23440 * </file> 23441 * <file name="protractor.js" type="protractor"> 23442 * var counter = element(by.binding('counter')); 23443 * var debug = element(by.binding('confirmed')); 23444 * 23445 * it('should evaluate the expression if changing from view', function() { 23446 * expect(counter.getText()).toContain('0'); 23447 * 23448 * element(by.id('ng-change-example1')).click(); 23449 * 23450 * expect(counter.getText()).toContain('1'); 23451 * expect(debug.getText()).toContain('true'); 23452 * }); 23453 * 23454 * it('should not evaluate the expression if changing from model', function() { 23455 * element(by.id('ng-change-example2')).click(); 23456 23457 * expect(counter.getText()).toContain('0'); 23458 * expect(debug.getText()).toContain('true'); 23459 * }); 23460 * </file> 23461 * </example> 23462 */ 23463var ngChangeDirective = valueFn({ 23464 restrict: 'A', 23465 require: 'ngModel', 23466 link: function(scope, element, attr, ctrl) { 23467 ctrl.$viewChangeListeners.push(function() { 23468 scope.$eval(attr.ngChange); 23469 }); 23470 } 23471}); 23472 23473function classDirective(name, selector) { 23474 name = 'ngClass' + name; 23475 return ['$animate', function($animate) { 23476 return { 23477 restrict: 'AC', 23478 link: function(scope, element, attr) { 23479 var oldVal; 23480 23481 scope.$watch(attr[name], ngClassWatchAction, true); 23482 23483 attr.$observe('class', function(value) { 23484 ngClassWatchAction(scope.$eval(attr[name])); 23485 }); 23486 23487 23488 if (name !== 'ngClass') { 23489 scope.$watch('$index', function($index, old$index) { 23490 // jshint bitwise: false 23491 var mod = $index & 1; 23492 if (mod !== (old$index & 1)) { 23493 var classes = arrayClasses(scope.$eval(attr[name])); 23494 mod === selector ? 23495 addClasses(classes) : 23496 removeClasses(classes); 23497 } 23498 }); 23499 } 23500 23501 function addClasses(classes) { 23502 var newClasses = digestClassCounts(classes, 1); 23503 attr.$addClass(newClasses); 23504 } 23505 23506 function removeClasses(classes) { 23507 var newClasses = digestClassCounts(classes, -1); 23508 attr.$removeClass(newClasses); 23509 } 23510 23511 function digestClassCounts(classes, count) { 23512 // Use createMap() to prevent class assumptions involving property 23513 // names in Object.prototype 23514 var classCounts = element.data('$classCounts') || createMap(); 23515 var classesToUpdate = []; 23516 forEach(classes, function(className) { 23517 if (count > 0 || classCounts[className]) { 23518 classCounts[className] = (classCounts[className] || 0) + count; 23519 if (classCounts[className] === +(count > 0)) { 23520 classesToUpdate.push(className); 23521 } 23522 } 23523 }); 23524 element.data('$classCounts', classCounts); 23525 return classesToUpdate.join(' '); 23526 } 23527 23528 function updateClasses(oldClasses, newClasses) { 23529 var toAdd = arrayDifference(newClasses, oldClasses); 23530 var toRemove = arrayDifference(oldClasses, newClasses); 23531 toAdd = digestClassCounts(toAdd, 1); 23532 toRemove = digestClassCounts(toRemove, -1); 23533 if (toAdd && toAdd.length) { 23534 $animate.addClass(element, toAdd); 23535 } 23536 if (toRemove && toRemove.length) { 23537 $animate.removeClass(element, toRemove); 23538 } 23539 } 23540 23541 function ngClassWatchAction(newVal) { 23542 if (selector === true || scope.$index % 2 === selector) { 23543 var newClasses = arrayClasses(newVal || []); 23544 if (!oldVal) { 23545 addClasses(newClasses); 23546 } else if (!equals(newVal,oldVal)) {
23547 var oldClasses = arrayClasses(oldVal); 23548 updateClasses(oldClasses, newClasses); 23549 } 23550 } 23551 oldVal = shallowCopy(newVal); 23552 } 23553 } 23554 }; 23555 23556 function arrayDifference(tokens1, tokens2) { 23557 var values = []; 23558 23559 outer: 23560 for (var i = 0; i < tokens1.length; i++) { 23561 var token = tokens1[i]; 23562 for (var j = 0; j < tokens2.length; j++) { 23563 if (token == tokens2[j]) continue outer; 23564 } 23565 values.push(token); 23566 } 23567 return values; 23568 } 23569 23570 function arrayClasses(classVal) { 23571 var classes = []; 23572 if (isArray(classVal)) { 23573 forEach(classVal, function(v) { 23574 classes = classes.concat(arrayClasses(v)); 23575 }); 23576 return classes; 23577 } else if (isString(classVal)) { 23578 return classVal.split(' '); 23579 } else if (isObject(classVal)) { 23580 forEach(classVal, function(v, k) { 23581 if (v) { 23582 classes = classes.concat(k.split(' ')); 23583 } 23584 }); 23585 return classes; 23586 } 23587 return classVal; 23588 } 23589 }]; 23590} 23591 23592/** 23593 * @ngdoc directive 23594 * @name ngClass 23595 * @restrict AC 23596 * 23597 * @description 23598 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 23599 * an expression that represents all classes to be added. 23600 * 23601 * The directive operates in three different ways, depending on which of three types the expression 23602 * evaluates to: 23603 * 23604 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 23605 * names. 23606 * 23607 * 2. If the expression evaluates to an object, then for each key-value pair of the 23608 * object with a truthy value the corresponding key is used as a class name. 23609 * 23610 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 23611 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 23612 * to give you more control over what CSS classes appear. See the code below for an example of this. 23613 * 23614 * 23615 * The directive won't add duplicate classes if a particular class was already set. 23616 * 23617 * When the expression changes, the previously added classes are removed and only then are the 23618 * new classes added. 23619 * 23620 * @animations 23621 * **add** - happens just before the class is applied to the elements 23622 * 23623 * **remove** - happens just before the class is removed from the element 23624 * 23625 * @element ANY 23626 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 23627 * of the evaluation can be a string representing space delimited class 23628 * names, an array, or a map of class names to boolean values. In the case of a map, the 23629 * names of the properties whose values are truthy will be added as css classes to the 23630 * element. 23631 * 23632 * @example Example that demonstrates basic bindings via ngClass directive. 23633 <example> 23634 <file name="index.html"> 23635 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 23636 <label> 23637 <input type="checkbox" ng-model="deleted"> 23638 deleted (apply "strike" class) 23639 </label><br> 23640 <label> 23641 <input type="checkbox" ng-model="important"> 23642 important (apply "bold" class) 23643 </label><br> 23644 <label> 23645 <input type="checkbox" ng-model="error"> 23646 error (apply "has-error" class) 23647 </label> 23648 <hr> 23649 <p ng-class="style">Using String Syntax</p> 23650 <input type="text" ng-model="style" 23651 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 23652 <hr> 23653 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 23654 <input ng-model="style1" 23655 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 23656 <input ng-model="style2" 23657 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 23658 <input ng-model="style3" 23659 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 23660 <hr> 23661 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 23662 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 23663 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 23664 </file> 23665 <file name="style.css"> 23666 .strike { 23667 text-decoration: line-through; 23668 } 23669 .bold { 23670 font-weight: bold; 23671 } 23672 .red { 23673 color: red; 23674 } 23675 .has-error { 23676 color: red; 23677 background-color: yellow; 23678 } 23679 .orange { 23680 color: orange; 23681 } 23682 </file> 23683 <file name="protractor.js" type="protractor"> 23684 var ps = element.all(by.css('p')); 23685 23686 it('should let you toggle the class', function() { 23687 23688 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 23689 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 23690
23691 element(by.model('important')).click(); 23692 expect(ps.first().getAttribute('class')).toMatch(/bold/); 23693 23694 element(by.model('error')).click(); 23695 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 23696 }); 23697 23698 it('should let you toggle string example', function() { 23699 expect(ps.get(1).getAttribute('class')).toBe(''); 23700 element(by.model('style')).clear(); 23701 element(by.model('style')).sendKeys('red'); 23702 expect(ps.get(1).getAttribute('class')).toBe('red'); 23703 }); 23704 23705 it('array example should have 3 classes', function() { 23706 expect(ps.get(2).getAttribute('class')).toBe(''); 23707 element(by.model('style1')).sendKeys('bold'); 23708 element(by.model('style2')).sendKeys('strike'); 23709 element(by.model('style3')).sendKeys('red'); 23710 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 23711 }); 23712 23713 it('array with map example should have 2 classes', function() { 23714 expect(ps.last().getAttribute('class')).toBe(''); 23715 element(by.model('style4')).sendKeys('bold'); 23716 element(by.model('warning')).click(); 23717 expect(ps.last().getAttribute('class')).toBe('bold orange'); 23718 }); 23719 </file> 23720 </example> 23721 23722 ## Animations 23723 23724 The example below demonstrates how to perform animations using ngClass. 23725 23726 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 23727 <file name="index.html"> 23728 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 23729 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 23730 <br> 23731 <span class="base-class" ng-class="myVar">Sample Text</span> 23732 </file> 23733 <file name="style.css"> 23734 .base-class { 23735 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23736 } 23737 23738 .base-class.my-class { 23739 color: red; 23740 font-size:3em; 23741 } 23742 </file> 23743 <file name="protractor.js" type="protractor"> 23744 it('should check ng-class', function() { 23745 expect(element(by.css('.base-class')).getAttribute('class')).not. 23746 toMatch(/my-class/); 23747 23748 element(by.id('setbtn')).click(); 23749 23750 expect(element(by.css('.base-class')).getAttribute('class')). 23751 toMatch(/my-class/); 23752 23753 element(by.id('clearbtn')).click(); 23754 23755 expect(element(by.css('.base-class')).getAttribute('class')).not. 23756 toMatch(/my-class/); 23757 }); 23758 </file> 23759 </example> 23760 23761 23762 ## ngClass and pre-existing CSS3 Transitions/Animations 23763 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 23764 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 23765 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 23766 to view the step by step details of {@link $animate#addClass $animate.addClass} and 23767 {@link $animate#removeClass $animate.removeClass}. 23768 */ 23769var ngClassDirective = classDirective('', true); 23770 23771/** 23772 * @ngdoc directive 23773 * @name ngClassOdd 23774 * @restrict AC 23775 * 23776 * @description 23777 * The `ngClassOdd` and `ngClassEven` directives work exactly as 23778 * {@link ng.directive:ngClass ngClass}, except they work in 23779 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 23780 * 23781 * This directive can be applied only within the scope of an 23782 * {@link ng.directive:ngRepeat ngRepeat}. 23783 * 23784 * @element ANY 23785 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 23786 * of the evaluation can be a string representing space delimited class names or an array. 23787 * 23788 * @example 23789 <example> 23790 <file name="index.html"> 23791 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 23792 <li ng-repeat="name in names"> 23793 <span ng-class-odd="'odd'" ng-class-even="'even'"> 23794 {{name}} 23795 </span> 23796 </li> 23797 </ol> 23798 </file> 23799 <file name="style.css"> 23800 .odd { 23801 color: red; 23802 } 23803 .even { 23804 color: blue; 23805 } 23806 </file> 23807 <file name="protractor.js" type="protractor"> 23808 it('should check ng-class-odd and ng-class-even', function() { 23809 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 23810 toMatch(/odd/); 23811 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 23812 toMatch(/even/); 23813 }); 23814 </file> 23815 </example> 23816 */ 23817var ngClassOddDirective = classDirective('Odd', 0); 23818 23819/** 23820 * @ngdoc directive 23821 * @name ngClassEven 23822 * @restrict AC 23823 * 23824 * @description 23825 * The `ngClassOdd` and `ngClassEven` directives work exactly as 23826 * {@link ng.directive:ngClass ngClass}, except they work in 23827 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 23828 * 23829 * This directive can be applied only within the scope of an 23830 * {@link ng.directive:ngRepeat ngRepeat}. 23831 * 23832 * @element ANY 23833 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 23834 * result of the evaluation can be a string representing space delimited class names or an array. 23835 * 23836 * @example 23837 <example> 23838 <file name="index.html"> 23839 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 23840 <li ng-repeat="name in names">
23841 <span ng-class-odd="'odd'" ng-class-even="'even'"> 23842 {{name}} 23843 </span> 23844 </li> 23845 </ol> 23846 </file> 23847 <file name="style.css"> 23848 .odd { 23849 color: red; 23850 } 23851 .even { 23852 color: blue; 23853 } 23854 </file> 23855 <file name="protractor.js" type="protractor"> 23856 it('should check ng-class-odd and ng-class-even', function() { 23857 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 23858 toMatch(/odd/); 23859 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 23860 toMatch(/even/); 23861 }); 23862 </file> 23863 </example> 23864 */ 23865var ngClassEvenDirective = classDirective('Even', 1); 23866 23867/** 23868 * @ngdoc directive 23869 * @name ngCloak 23870 * @restrict AC 23871 * 23872 * @description 23873 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 23874 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 23875 * directive to avoid the undesirable flicker effect caused by the html template display. 23876 * 23877 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 23878 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 23879 * of the browser view. 23880 * 23881 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 23882 * `angular.min.js`. 23883 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 23884 * 23885 * ```css 23886 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 23887 * display: none !important; 23888 * } 23889 * ``` 23890 * 23891 * When this css rule is loaded by the browser, all html elements (including their children) that 23892 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 23893 * during the compilation of the template it deletes the `ngCloak` element attribute, making 23894 * the compiled element visible. 23895 * 23896 * For the best result, the `angular.js` script must be loaded in the head section of the html 23897 * document; alternatively, the css rule above must be included in the external stylesheet of the 23898 * application. 23899 * 23900 * @element ANY 23901 * 23902 * @example 23903 <example> 23904 <file name="index.html"> 23905 <div id="template1" ng-cloak>{{ 'hello' }}</div> 23906 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 23907 </file> 23908 <file name="protractor.js" type="protractor"> 23909 it('should remove the template directive and css class', function() { 23910 expect($('#template1').getAttribute('ng-cloak')). 23911 toBeNull(); 23912 expect($('#template2').getAttribute('ng-cloak')). 23913 toBeNull(); 23914 }); 23915 </file> 23916 </example> 23917 * 23918 */ 23919var ngCloakDirective = ngDirective({ 23920 compile: function(element, attr) { 23921 attr.$set('ngCloak', undefined); 23922 element.removeClass('ng-cloak'); 23923 } 23924}); 23925 23926/** 23927 * @ngdoc directive 23928 * @name ngController 23929 * 23930 * @description 23931 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 23932 * supports the principles behind the Model-View-Controller design pattern. 23933 * 23934 * MVC components in angular: 23935 * 23936 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 23937 * are accessed through bindings. 23938 * * View â The template (HTML with data bindings) that is rendered into the View. 23939 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 23940 * logic behind the application to decorate the scope with functions and values 23941 * 23942 * Note that you can also attach controllers to the DOM by declaring it in a route definition 23943 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 23944 * again using `ng-controller` in the template itself. This will cause the controller to be attached 23945 * and executed twice. 23946 * 23947 * @element ANY 23948 * @scope 23949 * @priority 500 23950 * @param {expression} ngController Name of a constructor function registered with the current 23951 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 23952 * that on the current scope evaluates to a constructor function. 23953 * 23954 * The controller instance can be published into a scope property by specifying 23955 * `ng-controller="as propertyName"`. 23956 * 23957 * If the current `$controllerProvider` is configured to use globals (via 23958 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 23959 * also be the name of a globally accessible constructor function (not recommended). 23960 * 23961 * @example 23962 * Here is a simple form for editing user contact information. Adding, remov
23962ing, clearing, and 23963 * greeting are methods declared on the controller (see source tab). These methods can 23964 * easily be called from the angular markup. Any changes to the data are automatically reflected 23965 * in the View without the need for a manual update. 23966 * 23967 * Two different declaration styles are included below: 23968 * 23969 * * one binds methods and properties directly onto the controller using `this`: 23970 * `ng-controller="SettingsController1 as settings"` 23971 * * one injects `$scope` into the controller: 23972 * `ng-controller="SettingsController2"` 23973 * 23974 * The second option is more common in the Angular community, and is generally used in boilerplates 23975 * and in this guide. However, there are advantages to binding properties directly to the controller 23976 * and avoiding scope. 23977 * 23978 * * Using `controller as` makes it obvious which controller you are accessing in the template when 23979 * multiple controllers apply to an element. 23980 * * If you are writing your controllers as classes you have easier access to the properties and 23981 * methods, which will appear on the scope, from inside the controller code. 23982 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 23983 * inheritance masking primitives. 23984 * 23985 * This example demonstrates the `controller as` syntax. 23986 * 23987 * <example name="ngControllerAs" module="controllerAsExample"> 23988 * <file name="index.html"> 23989 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 23990 * <label>Name: <input type="text" ng-model="settings.name"/></label> 23991 * <button ng-click="settings.greet()">greet</button><br/> 23992 * Contact: 23993 * <ul> 23994 * <li ng-repeat="contact in settings.contacts"> 23995 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 23996 * <option>phone</option> 23997 * <option>email</option> 23998 * </select> 23999 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 24000 * <button ng-click="settings.clearContact(contact)">clear</button> 24001 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 24002 * </li> 24003 * <li><button ng-click="settings.addContact()">add</button></li> 24004 * </ul> 24005 * </div> 24006 * </file> 24007 * <file name="app.js"> 24008 * angular.module('controllerAsExample', []) 24009 * .controller('SettingsController1', SettingsController1); 24010 * 24011 * function SettingsController1() { 24012 * this.name = "John Smith"; 24013 * this.contacts = [ 24014 * {type: 'phone', value: '408 555 1212'}, 24015 * {type: 'email', value: '[email protected]'} ]; 24016 * } 24017 * 24018 * SettingsController1.prototype.greet = function() { 24019 * alert(this.name); 24020 * }; 24021 * 24022 * SettingsController1.prototype.addContact = function() { 24023 * this.contacts.push({type: 'email', value: '[email protected]'}); 24024 * }; 24025 * 24026 * SettingsController1.prototype.removeContact = function(contactToRemove) { 24027 * var index = this.contacts.indexOf(contactToRemove); 24028 * this.contacts.splice(index, 1); 24029 * }; 24030 * 24031 * SettingsController1.prototype.clearContact = function(contact) { 24032 * contact.type = 'phone'; 24033 * contact.value = ''; 24034 * }; 24035 * </file> 24036 * <file name="protractor.js" type="protractor"> 24037 * it('should check controller as', function() { 24038 * var container = element(by.id('ctrl-as-exmpl')); 24039 * expect(container.element(by.model('settings.name')) 24040 * .getAttribute('value')).toBe('John Smith'); 24041 * 24042 * var firstRepeat = 24043 * container.element(by.repeater('contact in settings.contacts').row(0)); 24044 * var secondRepeat = 24045 * container.element(by.repeater('contact in settings.contacts').row(1)); 24046 * 24047 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24048 * .toBe('408 555 1212'); 24049 * 24050 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 24051 * .toBe('[email protected]'); 24052 * 24053 * firstRepeat.element(by.buttonText('clear')).click(); 24054 * 24055 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24056 * .toBe(''); 24057 * 24058 * container.element(by.buttonText('add')).click(); 24059 * 24060 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 24061 * .element(by.model('contact.value')) 24062 * .getAttribute('value')) 24063 * .toBe('[email protected]'); 24064 * }); 24065 * </file> 24066 * </example> 24067 * 24068 * This example demonstrates the "attach to `$scope`" style of controller. 24069 * 24070 * <example name="ngController" module="controllerExample"> 24071 * <file name="index.html"> 24072 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 24073 * <label>Name: <input type="text" ng-model="name"/></label> 24074 * <button ng-click="greet()">greet</button><br/> 24075 * Contact: 24076 * <ul> 24077 * <li ng-repeat="contact in contacts"> 24078 * <select ng-model="contact.type" id="select_{{$index}}"> 24079 * <option>phone</option> 24080 * <option>email</option> 24081 * </select> 24082 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 24083 * <button ng-click="clearContact(contact)">clear</button> 24084 * <button ng-click="removeContact(contact)">X</button> 24085 * </li> 24086 * <li>[ <button ng-click="addContact()">add</button> ]</li> 24087 * </ul> 24088 * </div> 24089 * </file> 24090 * <file name="app.js">
24091 * angular.module('controllerExample', []) 24092 * .controller('SettingsController2', ['$scope', SettingsController2]); 24093 * 24094 * function SettingsController2($scope) { 24095 * $scope.name = "John Smith"; 24096 * $scope.contacts = [ 24097 * {type:'phone', value:'408 555 1212'}, 24098 * {type:'email', value:'[email protected]'} ]; 24099 * 24100 * $scope.greet = function() { 24101 * alert($scope.name); 24102 * }; 24103 * 24104 * $scope.addContact = function() { 24105 * $scope.contacts.push({type:'email', value:'[email protected]'}); 24106 * }; 24107 * 24108 * $scope.removeContact = function(contactToRemove) { 24109 * var index = $scope.contacts.indexOf(contactToRemove); 24110 * $scope.contacts.splice(index, 1); 24111 * }; 24112 * 24113 * $scope.clearContact = function(contact) { 24114 * contact.type = 'phone'; 24115 * contact.value = ''; 24116 * }; 24117 * } 24118 * </file> 24119 * <file name="protractor.js" type="protractor"> 24120 * it('should check controller', function() { 24121 * var container = element(by.id('ctrl-exmpl')); 24122 * 24123 * expect(container.element(by.model('name')) 24124 * .getAttribute('value')).toBe('John Smith'); 24125 * 24126 * var firstRepeat = 24127 * container.element(by.repeater('contact in contacts').row(0)); 24128 * var secondRepeat = 24129 * container.element(by.repeater('contact in contacts').row(1)); 24130 * 24131 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24132 * .toBe('408 555 1212'); 24133 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 24134 * .toBe('[email protected]'); 24135 * 24136 * firstRepeat.element(by.buttonText('clear')).click(); 24137 * 24138 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 24139 * .toBe(''); 24140 * 24141 * container.element(by.buttonText('add')).click(); 24142 * 24143 * expect(container.element(by.repeater('contact in contacts').row(2)) 24144 * .element(by.model('contact.value')) 24145 * .getAttribute('value')) 24146 * .toBe('[email protected]'); 24147 * }); 24148 * </file> 24149 *</example> 24150 24151 */ 24152var ngControllerDirective = [function() { 24153 return { 24154 restrict: 'A', 24155 scope: true, 24156 controller: '@', 24157 priority: 500 24158 }; 24159}]; 24160 24161/** 24162 * @ngdoc directive 24163 * @name ngCsp 24164 * 24165 * @element html 24166 * @description 24167 * 24168 * Angular has some features that can break certain 24169 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 24170 * 24171 * If you intend to implement these rules then you must tell Angular not to use these features. 24172 * 24173 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 24174 * 24175 * 24176 * The following rules affect Angular: 24177 * 24178 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 24179 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 24180 * increase in the speed of evaluating Angular expressions. 24181 * 24182 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 24183 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 24184 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 24185 * `angular-csp.css` in your HTML manually. 24186 * 24187 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 24188 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 24189 * however, triggers a CSP error to be logged in the console: 24190 * 24191 * ``` 24192 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 24193 * script in the following Content Security Policy directive: "default-src 'self'". Note that 24194 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 24195 * ``` 24196 * 24197 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 24198 * directive on an element of the HTML document that appears before the `<script>` tag that loads 24199 * the `angular.js` file. 24200 * 24201 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 24202 * 24203 * You can specify which of the CSP related Angular features should be deactivated by providing 24204 * a value for the `ng-csp` attribute. The options are as follows: 24205 * 24206 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 24207 *
24208 * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings 24209 * 24210 * You can use these values in the following combinations: 24211 * 24212 * 24213 * * No declaration means that Angular will assume that you can do inline styles, but it will do 24214 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 24215 * of Angular. 24216 * 24217 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 24218 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 24219 * of Angular. 24220 * 24221 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 24222 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 24223 * 24224 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 24225 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 24226 * 24227 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 24228 * styles nor use eval, which is the same as an empty: ng-csp. 24229 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 24230 * 24231 * @example 24232 * This example shows how to apply the `ngCsp` directive to the `html` tag. 24233 ```html 24234 <!doctype html> 24235 <html ng-app ng-csp> 24236 ... 24237 ... 24238 </html> 24239 ``` 24240 * @example 24241 // Note: the suffix `.csp` in the example name triggers 24242 // csp mode in our http server! 24243 <example name="example.csp" module="cspExample" ng-csp="true"> 24244 <file name="index.html"> 24245 <div ng-controller="MainController as ctrl"> 24246 <div> 24247 <button ng-click="ctrl.inc()" id="inc">Increment</button> 24248 <span id="counter"> 24249 {{ctrl.counter}} 24250 </span> 24251 </div> 24252 24253 <div> 24254 <button ng-click="ctrl.evil()" id="evil">Evil</button> 24255 <span id="evilError"> 24256 {{ctrl.evilError}} 24257 </span> 24258 </div> 24259 </div> 24260 </file> 24261 <file name="script.js"> 24262 angular.module('cspExample', []) 24263 .controller('MainController', function() { 24264 this.counter = 0; 24265 this.inc = function() { 24266 this.counter++; 24267 }; 24268 this.evil = function() { 24269 // jshint evil:true 24270 try { 24271 eval('1+2'); 24272 } catch (e) { 24273 this.evilError = e.message; 24274 } 24275 }; 24276 }); 24277 </file> 24278 <file name="protractor.js" type="protractor"> 24279 var util, webdriver; 24280 24281 var incBtn = element(by.id('inc')); 24282 var counter = element(by.id('counter')); 24283 var evilBtn = element(by.id('evil')); 24284 var evilError = element(by.id('evilError')); 24285 24286 function getAndClearSevereErrors() { 24287 return browser.manage().logs().get('browser').then(function(browserLog) { 24288 return browserLog.filter(function(logEntry) { 24289 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 24290 }); 24291 }); 24292 } 24293 24294 function clearErrors() { 24295 getAndClearSevereErrors(); 24296 } 24297 24298 function expectNoErrors() { 24299 getAndClearSevereErrors().then(function(filteredLog) { 24300 expect(filteredLog.length).toEqual(0); 24301 if (filteredLog.length) { 24302 console.log('browser console errors: ' + util.inspect(filteredLog)); 24303 } 24304 }); 24305 } 24306 24307 function expectError(regex) { 24308 getAndClearSevereErrors().then(function(filteredLog) { 24309 var found = false; 24310 filteredLog.forEach(function(log) { 24311 if (log.message.match(regex)) { 24312 found = true; 24313 } 24314 }); 24315 if (!found) { 24316 throw new Error('expected an error that matches ' + regex); 24317 } 24318 }); 24319 } 24320 24321 beforeEach(function() { 24322 util = require('util'); 24323 webdriver = require('protractor/node_modules/selenium-webdriver'); 24324 }); 24325 24326 // For now, we only test on Chrome, 24327 // as Safari does not load the page with Protractor's injected scripts, 24328 // and Firefox webdriver always disables content security policy (#6358) 24329 if (browser.params.browser !== 'chrome') { 24330 return; 24331 } 24332 24333 it('should not report errors when the page is loaded', function() { 24334 // clear errors so we are not dependent on previous tests 24335 clearErrors(); 24336 // Need to reload the page as the page is already loaded when 24337 // we come here 24338 browser.driver.getCurrentUrl().then(function(url) { 24339 browser.get(url); 24340 }); 24341 expectNoErrors(); 24342 }); 24343 24344 it('should evaluate expressions', function() { 24345 expect(counter.getText()).toEqual('0'); 24346 incBtn.click(); 24347 expect(counter.getText()).toEqual('1'); 24348 expectNoErrors(); 24349 }); 24350 24351 it('should throw and report an error when using "eval"', function() { 24352 evilBtn.click(); 24353 expect(evilError.getText()).toMatch(/Content Security Policy/); 24354 expectError(/Content Security Policy/); 24355 }); 24356 </file> 24357 </example> 24358 */ 24359 24360// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
24361// bootstrap the system (before $parse is instantiated), for this reason we just have 24362// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 24363 24364/** 24365 * @ngdoc directive 24366 * @name ngClick 24367 * 24368 * @description 24369 * The ngClick directive allows you to specify custom behavior when 24370 * an element is clicked. 24371 * 24372 * @element ANY 24373 * @priority 0 24374 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 24375 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 24376 * 24377 * @example 24378 <example> 24379 <file name="index.html"> 24380 <button ng-click="count = count + 1" ng-init="count=0"> 24381 Increment 24382 </button> 24383 <span> 24384 count: {{count}} 24385 </span> 24386 </file> 24387 <file name="protractor.js" type="protractor"> 24388 it('should check ng-click', function() { 24389 expect(element(by.binding('count')).getText()).toMatch('0'); 24390 element(by.css('button')).click(); 24391 expect(element(by.binding('count')).getText()).toMatch('1'); 24392 }); 24393 </file> 24394 </example> 24395 */ 24396/* 24397 * A collection of directives that allows creation of custom event handlers that are defined as 24398 * angular expressions and are compiled and executed within the current scope. 24399 */ 24400var ngEventDirectives = {}; 24401 24402// For events that might fire synchronously during DOM manipulation 24403// we need to execute their event handlers asynchronously using $evalAsync, 24404// so that they are not executed in an inconsistent state. 24405var forceAsyncEvents = { 24406 'blur': true, 24407 'focus': true 24408}; 24409forEach( 24410 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 24411 function(eventName) { 24412 var directiveName = directiveNormalize('ng-' + eventName); 24413 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 24414 return { 24415 restrict: 'A', 24416 compile: function($element, attr) { 24417 // We expose the powerful $event object on the scope that provides access to the Window, 24418 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 24419 // checks at the cost of speed since event handler expressions are not executed as 24420 // frequently as regular change detection. 24421 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 24422 return function ngEventHandler(scope, element) { 24423 element.on(eventName, function(event) { 24424 var callback = function() { 24425 fn(scope, {$event:event}); 24426 }; 24427 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 24428 scope.$evalAsync(callback); 24429 } else { 24430 scope.$apply(callback); 24431 } 24432 }); 24433 }; 24434 } 24435 }; 24436 }]; 24437 } 24438); 24439 24440/** 24441 * @ngdoc directive 24442 * @name ngDblclick 24443 * 24444 * @description 24445 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 24446 * 24447 * @element ANY 24448 * @priority 0 24449 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 24450 * a dblclick. (The Event object is available as `$event`) 24451 * 24452 * @example 24453 <example> 24454 <file name="index.html"> 24455 <button ng-dblclick="count = count + 1" ng-init="count=0"> 24456 Increment (on double click) 24457 </button> 24458 count: {{count}} 24459 </file> 24460 </example> 24461 */ 24462 24463 24464/** 24465 * @ngdoc directive 24466 * @name ngMousedown 24467 * 24468 * @description 24469 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 24470 * 24471 * @element ANY 24472 * @priority 0 24473 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 24474 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 24475 * 24476 * @example 24477 <example> 24478 <file name="index.html"> 24479 <button ng-mousedown="count = count + 1" ng-init="count=0"> 24480 Increment (on mouse down) 24481 </button> 24482 count: {{count}} 24483 </file> 24484 </example> 24485 */ 24486 24487 24488/** 24489 * @ngdoc directive 24490 * @name ngMouseup 24491 * 24492 * @description 24493 * Specify custom behavior on mouseup event. 24494 * 24495 * @element ANY 24496 * @priority 0 24497 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 24498 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 24499 * 24500 * @example 24501 <example> 24502 <file name="index.html"> 24503 <button ng-mouseup="count = count + 1" ng-init="count=0"> 24504 Increment (on mouse up) 24505 </button> 24506 count: {{count}} 24507 </file> 24508 </example> 24509 */ 24510 24511/** 24512 * @ngdoc directive 24513 * @name ngMouseover 24514 * 24515 * @description 24516 * Specify custom behavior on mouseover event. 24517 * 24518 * @element ANY 24519 * @priority 0 24520 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 24521 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 24522 * 24523 * @example 24524 <example> 24525 <file name="index.html"> 24526 <button ng-mouseover="count = count + 1" ng-init="count=0"> 24527 Increment (when mouse is over) 24528 </button> 24529 count: {{count}} 24530 </file> 24531 </example> 24532 */ 24533 24534 24535/** 24536 * @ngdoc directive 24537 * @name ngMouseenter 24538 * 24539 * @description 24540 * Specify custom behavior on mouseenter event. 24541 * 24542 * @element ANY 24543 * @priority 0 24544 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 24545 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 24546 * 24547 * @example 24548 <example> 24549 <file name="index.html"> 24550 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 24551 Increment (when mouse enters) 24552 </button> 24553 count: {{count}} 24554 </file> 24555 </example> 24556 */ 24557 24558 24559/** 24560 * @ngdoc directive 24561 * @name ngMouseleave 24562 * 24563 * @description 24564 * Specify custom behavior on mouseleave event. 24565 * 24566 * @element ANY 24567 * @priority 0 24568 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 24569 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 24570 * 24571 * @example 24572 <example> 24573 <file name="index.html"> 24574 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 24575 Increment (when mouse leaves) 24576 </button> 24577 count: {{count}} 24578 </file> 24579 </example> 24580 */ 24581 24582 24583/** 24584 * @ngdoc directive 24585 * @name ngMousemove 24586 * 24587 * @description 24588 * Specify custom behavior on mousemove event. 24589 * 24590 * @element ANY 24591 * @priority 0 24592 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 24593 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 24594 * 24595 * @example 24596 <example> 24597 <file name="index.html"> 24598 <button ng-mousemove="count = count + 1" ng-init="count=0"> 24599 Increment (when mouse moves) 24600 </button> 24601 count: {{count}} 24602 </file> 24603 </example> 24604 */ 24605 24606 24607/** 24608 * @ngdoc directive 24609 * @name ngKeydown 24610 * 24611 * @description 24612 * Specify custom behavior on keydown event. 24613 * 24614 * @element ANY 24615 * @priority 0 24616 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 24617 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 24618 * 24619 * @example 24620 <example> 24621 <file name="index.html"> 24622 <input ng-keydown="count = count + 1" ng-init="count=0"> 24623 key down count: {{count}} 24624 </file> 24625 </example> 24626 */ 24627 24628 24629/** 24630 * @ngdoc directive 24631 * @name ngKeyup 24632 * 24633 * @description 24634 * Specify custom behavior on keyup event. 24635 * 24636 * @element ANY 24637 * @priority 0 24638 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 24639 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 24640 * 24641 * @example 24642 <example> 24643 <file name="index.html"> 24644 <p>Typing in the input box below updates the key count</p> 24645 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 24646 24647 <p>Typing in the input box below updates the keycode</p> 24648 <input ng-keyup="event=$event"> 24649 <p>event keyCode: {{ event.keyCode }}</p> 24650 <p>event altKey: {{ event.altKey }}</p> 24651 </file> 24652 </example> 24653 */ 24654 24655 24656/** 24657 * @ngdoc directive 24658 * @name ngKeypress 24659 * 24660 * @description 24661 * Specify custom behavior on keypress event. 24662 * 24663 * @element ANY 24664 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 24665 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 24666 * and can be interrogated for keyCode, altKey, etc.) 24667 * 24668 * @example 24669 <example> 24670 <file name="index.html"> 24671 <input ng-keypress="count = count + 1" ng-init="count=0"> 24672 key press count: {{count}} 24673 </file> 24674 </example> 24675 */ 24676 24677 24678/** 24679 * @ngdoc directive 24680 * @name ngSubmit 24681 * 24682 * @description 24683 * Enables binding angular expressions to onsubmit events. 24684 * 24685 * Additionally it prevents the default action (which for form means
24685sending the request to the 24686 * server and reloading the current page), but only if the form does not contain `action`, 24687 * `data-action`, or `x-action` attributes. 24688 * 24689 * <div class="alert alert-warning"> 24690 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 24691 * `ngSubmit` handlers together. See the 24692 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 24693 * for a detailed discussion of when `ngSubmit` may be triggered. 24694 * </div> 24695 * 24696 * @element form 24697 * @priority 0 24698 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 24699 * ({@link guide/expression#-event- Event object is available as `$event`}) 24700 * 24701 * @example 24702 <example module="submitExample"> 24703 <file name="index.html"> 24704 <script> 24705 angular.module('submitExample', []) 24706 .controller('ExampleController', ['$scope', function($scope) { 24707 $scope.list = []; 24708 $scope.text = 'hello'; 24709 $scope.submit = function() { 24710 if ($scope.text) { 24711 $scope.list.push(this.text); 24712 $scope.text = ''; 24713 } 24714 }; 24715 }]); 24716 </script>
24717 <form ng-submit="submit()" ng-controller="ExampleController"> 24718 Enter text and hit enter: 24719 <input type="text" ng-model="text" name="text" /> 24720 <input type="submit" id="submit" value="Submit" /> 24721 <pre>list={{list}}</pre> 24722 </form> 24723 </file> 24724 <file name="protractor.js" type="protractor"> 24725 it('should check ng-submit', function() { 24726 expect(element(by.binding('list')).getText()).toBe('list=[]'); 24727 element(by.css('#submit')).click(); 24728 expect(element(by.binding('list')).getText()).toContain('hello'); 24729 expect(element(by.model('text')).getAttribute('value')).toBe(''); 24730 }); 24731 it('should ignore empty strings', function() { 24732 expect(element(by.binding('list')).getText()).toBe('list=[]'); 24733 element(by.css('#submit')).click(); 24734 element(by.css('#submit')).click(); 24735 expect(element(by.binding('list')).getText()).toContain('hello'); 24736 }); 24737 </file> 24738 </example> 24739 */ 24740 24741/** 24742 * @ngdoc directive 24743 * @name ngFocus 24744 * 24745 * @description 24746 * Specify custom behavior on focus event. 24747 * 24748 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 24749 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 24750 * during an `$apply` to ensure a consistent state. 24751 * 24752 * @element window, input, select, textarea, a 24753 * @priority 0 24754 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 24755 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 24756 * 24757 * @example 24758 * See {@link ng.directive:ngClick ngClick} 24759 */ 24760 24761/** 24762 * @ngdoc directive 24763 * @name ngBlur 24764 * 24765 * @description 24766 * Specify custom behavior on blur event. 24767 * 24768 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 24769 * an element has lost focus. 24770 * 24771 * Note: As the `blur` event is executed synchronously also during DOM manipulations 24772 * (e.g. removing a focussed input), 24773 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 24774 * during an `$apply` to ensure a consistent state. 24775 * 24776 * @element window, input, select, textarea, a 24777 * @priority 0 24778 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 24779 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 24780 * 24781 * @example 24782 * See {@link ng.directive:ngClick ngClick} 24783 */ 24784 24785/** 24786 * @ngdoc directive 24787 * @name ngCopy 24788 * 24789 * @description 24790 * Specify custom behavior on copy event. 24791 * 24792 * @element window, input, select, textarea, a 24793 * @priority 0 24794 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 24795 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 24796 * 24797 * @example 24798 <example> 24799 <file name="index.html"> 24800 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 24801 copied: {{copied}} 24802 </file> 24803 </example> 24804 */ 24805 24806/** 24807 * @ngdoc directive 24808 * @name ngCut 24809 * 24810 * @description 24811 * Specify custom behavior on cut event. 24812 * 24813 * @element window, input, select, textarea, a 24814 * @priority 0 24815 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 24816 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 24817 * 24818 * @example 24819 <example> 24820 <file name="index.html"> 24821 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 24822 cut: {{cut}} 24823 </file> 24824 </example> 24825 */ 24826 24827/** 24828 * @ngdoc directive 24829 * @name ngPaste 24830 * 24831 * @description 24832 * Specify custom behavior on paste event. 24833 * 24834 * @element window, input, select, textarea, a 24835 * @priority 0 24836 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 24837 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 24838 * 24839 * @example 24840 <example> 24841 <file name="index.html"> 24842 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 24843 pasted: {{paste}} 24844 </file> 24845 </example> 24846 */ 24847 24848/** 24849 * @ngdoc directive 24850 * @name ngIf 24851 * @restrict A 24852 * @multiElement 24853 * 24854 * @description 24855 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 24856 * {expression}. If the expression assigned to `ngIf` evaluates to a false 24857 * value then the element is removed from the DOM, otherwise a clone of the 24858 * element is reinserted into the DOM. 24859 * 24860 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 24861 * element in the DOM rather than changing its visibility via the `display` css property. A common 24862 * case when this difference is significant is when using css selectors that rely on an element's 24863 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
24863s. 24864 * 24865 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 24866 * is created when the element is restored. The scope created within `ngIf` inherits from 24867 * its parent scope using 24868 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 24869 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 24870 * a javascript primitive defined in the parent scope. In this case any modifications made to the 24871 * variable within the child scope will override (hide) the value in the parent scope. 24872 * 24873 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 24874 * is if an element's class attribute is directly modified after it's compiled, using something like 24875 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 24876 * the added class will be lost because the original compiled state is used to regenerate the element. 24877 * 24878 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 24879 * and `leave` effects. 24880 * 24881 * @animations 24882 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 24883 * leave - happens just before the `ngIf` contents are removed from the DOM 24884 * 24885 * @element ANY 24886 * @scope 24887 * @priority 600 24888 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 24889 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 24890 * element is added to the DOM tree. 24891 * 24892 * @example 24893 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24894 <file name="index.html"> 24895 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 24896 Show when checked: 24897 <span ng-if="checked" class="animate-if"> 24898 This is removed when the checkbox is unchecked. 24899 </span> 24900 </file> 24901 <file name="animations.css"> 24902 .animate-if { 24903 background:white; 24904 border:1px solid black; 24905 padding:10px; 24906 } 24907 24908 .animate-if.ng-enter, .animate-if.ng-leave { 24909 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24910 } 24911 24912 .animate-if.ng-enter, 24913 .animate-if.ng-leave.ng-leave-active { 24914 opacity:0; 24915 } 24916 24917 .animate-if.ng-leave, 24918 .animate-if.ng-enter.ng-enter-active { 24919 opacity:1; 24920 } 24921 </file> 24922 </example> 24923 */ 24924var ngIfDirective = ['$animate', function($animate) { 24925 return { 24926 multiElement: true, 24927 transclude: 'element', 24928 priority: 600, 24929 terminal: true, 24930 restrict: 'A', 24931 $$tlb: true, 24932 link: function($scope, $element, $attr, ctrl, $transclude) { 24933 var block, childScope, previousElements; 24934 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 24935 24936 if (value) { 24937 if (!childScope) { 24938 $transclude(function(clone, newScope) { 24939 childScope = newScope; 24940 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 24941 // Note: We only need the first/last node of the cloned nodes. 24942 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 24943 // by a directive with templateUrl when its template arrives. 24944 block = { 24945 clone: clone 24946 }; 24947 $animate.enter(clone, $element.parent(), $element); 24948 }); 24949 } 24950 } else { 24951 if (previousElements) { 24952 previousElements.remove(); 24953 previousElements = null; 24954 } 24955 if (childScope) { 24956 childScope.$destroy(); 24957 childScope = null; 24958 } 24959 if (block) { 24960 previousElements = getBlockNodes(block.clone); 24961 $animate.leave(previousElements).then(function() { 24962 previousElements = null; 24963 }); 24964 block = null; 24965 } 24966 } 24967 }); 24968 } 24969 }; 24970}]; 24971 24972/** 24973 * @ngdoc directive 24974 * @name ngInclude 24975 * @restrict ECA 24976 * 24977 * @description 24978 * Fetches, compiles and includes an external HTML fragment. 24979 * 24980 * By default, the template URL is restricted to the same domain and protocol as the 24981 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 24982 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 24983 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 24984 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 24985 * ng.$sce Strict Contextual Escaping}. 24986 * 24987 * In addition, the browser's 24988 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 24989 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 24990 * policy may further restrict whether the template is successfully loaded. 24991 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 24992 * access on some browsers. 24993 * 24994 * @animations 24995 * enter - animation is used to bring new content into the browser. 24996 * leave - animation is used to animate existing content away. 24997 * 24998 * The enter and leave animation occur concurrently. 24999 * 25000 * @scope 25001 * @priority 400 25002 * 25003 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 25004 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 25005 * @param {string=} onload Expression to evaluate when a new partial is loaded. 25006 * <div class="alert alert-warning"> 25007 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 25008 * a function with the name on the window element, which will usually throw a 25009 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 25010 * different form that {@link guide/directive#normalization matches} `onload`. 25011 * </div> 25012 * 25013 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 25014 * $anchorScroll} to scroll the viewport after the content is loaded. 25015 * 25016 * - If the attribute is not set, disable scrolling. 25017 * - If the attribute is set without value, enable scrolling. 25018 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 25019 * 25020 * @example 25021 <example module="includeExample" deps="angular-animate.js" animations="true"> 25022 <file name="index.html">
25023 <div ng-controller="ExampleController"> 25024 <select ng-model="template" ng-options="t.name for t in templates"> 25025 <option value="">(blank)</option> 25026 </select> 25027 url of the template: <code>{{template.url}}</code> 25028 <hr/> 25029 <div class="slide-animate-container"> 25030 <div class="slide-animate" ng-include="template.url"></div> 25031 </div> 25032 </div> 25033 </file> 25034 <file name="script.js"> 25035 angular.module('includeExample', ['ngAnimate']) 25036 .controller('ExampleController', ['$scope', function($scope) { 25037 $scope.templates = 25038 [ { name: 'template1.html', url: 'template1.html'}, 25039 { name: 'template2.html', url: 'template2.html'} ]; 25040 $scope.template = $scope.templates[0]; 25041 }]); 25042 </file> 25043 <file name="template1.html"> 25044 Content of template1.html 25045 </file> 25046 <file name="template2.html"> 25047 Content of template2.html 25048 </file> 25049 <file name="animations.css"> 25050 .slide-animate-container { 25051 position:relative; 25052 background:white; 25053 border:1px solid black; 25054 height:40px; 25055 overflow:hidden; 25056 } 25057 25058 .slide-animate { 25059 padding:10px; 25060 } 25061 25062 .slide-animate.ng-enter, .slide-animate.ng-leave { 25063 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 25064 25065 position:absolute; 25066 top:0; 25067 left:0; 25068 right:0; 25069 bottom:0; 25070 display:block; 25071 padding:10px; 25072 } 25073 25074 .slide-animate.ng-enter { 25075 top:-50px; 25076 } 25077 .slide-animate.ng-enter.ng-enter-active { 25078 top:0; 25079 } 25080 25081 .slide-animate.ng-leave { 25082 top:0; 25083 } 25084 .slide-animate.ng-leave.ng-leave-active { 25085 top:50px; 25086 } 25087 </file> 25088 <file name="protractor.js" type="protractor"> 25089 var templateSelect = element(by.model('template')); 25090 var includeElem = element(by.css('[ng-include]')); 25091 25092 it('should load template1.html', function() { 25093 expect(includeElem.getText()).toMatch(/Content of template1.html/); 25094 }); 25095 25096 it('should load template2.html', function() { 25097 if (browser.params.browser == 'firefox') { 25098 // Firefox can't handle using selects 25099 // See https://github.com/angular/protractor/issues/480 25100 return; 25101 } 25102 templateSelect.click(); 25103 templateSelect.all(by.css('option')).get(2).click(); 25104 expect(includeElem.getText()).toMatch(/Content of template2.html/); 25105 }); 25106 25107 it('should change to blank', function() { 25108 if (browser.params.browser == 'firefox') { 25109 // Firefox can't handle using selects 25110 return; 25111 } 25112 templateSelect.click(); 25113 templateSelect.all(by.css('option')).get(0).click(); 25114 expect(includeElem.isPresent()).toBe(false); 25115 }); 25116 </file> 25117 </example> 25118 */ 25119 25120 25121/** 25122 * @ngdoc event 25123 * @name ngInclude#$includeContentRequested 25124 * @eventType emit on the scope ngInclude was declared in 25125 * @description 25126 * Emitted every time the ngInclude content is requested. 25127 * 25128 * @param {Object} angularEvent Synthetic event object. 25129 * @param {String} src URL of content to load. 25130 */ 25131 25132 25133/** 25134 * @ngdoc event 25135 * @name ngInclude#$includeContentLoaded 25136 * @eventType emit on the current ngInclude scope 25137 * @description 25138 * Emitted every time the ngInclude content is reloaded. 25139 * 25140 * @param {Object} angularEvent Synthetic event object. 25141 * @param {String} src URL of content to load. 25142 */ 25143 25144 25145/** 25146 * @ngdoc event 25147 * @name ngInclude#$includeContentError 25148 * @eventType emit on the scope ngInclude was declared in 25149 * @description 25150 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 25151 * 25152 * @param {Object} angularEvent Synthetic event object. 25153 * @param {String} src URL of content to load. 25154 */ 25155var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 25156 function($templateRequest, $anchorScroll, $animate) { 25157 return { 25158 restrict: 'ECA', 25159 priority: 400, 25160 terminal: true, 25161 transclude: 'element', 25162 controller: angular.noop, 25163 compile: function(element, attr) { 25164 var srcExp = attr.ngInclude || attr.src, 25165 onloadExp = attr.onload || '', 25166 autoScrollExp = attr.autoscroll; 25167 25168 return function(scope, $element, $attr, ctrl, $transclude) { 25169 var changeCounter = 0, 25170 currentScope, 25171 previousElement, 25172 currentElement; 25173 25174 var cleanupLastIncludeContent = function() { 25175 if (previousElement) { 25176 previousElement.remove(); 25177 previousElement = null; 25178 } 25179 if (currentScope) { 25180 currentScope.$destroy(); 25181 currentScope = null; 25182 } 25183 if (currentElement) { 25184 $animate.leave(currentElement).then(function() { 25185 previousElement = null; 25186 }); 25187 previousElement = currentElement; 25188 currentElement = null; 25189 } 25190 }; 25191 25192 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 25193 var afterAnimation = function() { 25194 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 25195 $anchorScroll(); 25196 } 25197 }; 25198 var thisChangeId = ++changeCounter; 25199 25200 if (src) { 25201 //set the 2nd param to true to ignore the template request error so that the inner 25202 //contents and scope can be cleaned up. 25203 $templateRequest(src, true).then(function(response) { 25204 if (scope.$$destroyed) return; 25205 25206 if (thisChangeId !== changeCounter) return; 25207 var newScope = scope.$new(); 25208 ctrl.template = response; 25209 25210 // Note: This will also link all children of ng-include that were contained in the original 25211 // html. If that content contains controllers, ... they could pollute/change the scope. 25212 // However, using ng-include on an element with additional content does not make sense... 25213 // Note: We can't remove them in the cloneAttchFn of $transclude as that 25214 // function is called before linking the content, which would apply child 25215 // directives to non existing elements. 25216 var clone = $transclude(newScope, function(clone) { 25217 cleanupLastIncludeContent(); 25218 $animate.enter(clone, null, $element).then(afterAnimation); 25219 }); 25220 25221 currentScope = newScope; 25222 currentElement = clone; 25223 25224 currentScope.$emit('$includeContentLoaded', src); 25225 scope.$eval(onloadExp); 25226 }, function() { 25227 if (scope.$$destroyed) return; 25228 25229 if (thisChangeId === changeCounter) { 25230 cleanupLastIncludeContent();
vendor: 2,918 bytes, lines 25231-25313
25231 scope.$emit('$includeContentError', src); 25232 } 25233 }); 25234 scope.$emit('$includeContentRequested', src); 25235 } else { 25236 cleanupLastIncludeContent(); 25237 ctrl.template = null; 25238 } 25239 }); 25240 }; 25241 } 25242 }; 25243}]; 25244 25245// This directive is called during the $transclude call of the first `ngInclude` directive. 25246// It will replace and compile the content of the element with the loaded template. 25247// We need this directive so that the element content is already filled when 25248// the link function of another directive on the same element as ngInclude 25249// is called. 25250var ngIncludeFillContentDirective = ['$compile', 25251 function($compile) { 25252 return { 25253 restrict: 'ECA', 25254 priority: -400, 25255 require: 'ngInclude', 25256 link: function(scope, $element, $attr, ctrl) { 25257 if (toString.call($element[0]).match(/SVG/)) { 25258 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 25259 // support innerHTML, so detect this here and try to generate the contents 25260 // specially. 25261 $element.empty(); 25262 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 25263 function namespaceAdaptedClone(clone) { 25264 $element.append(clone); 25265 }, {futureParentElement: $element}); 25266 return; 25267 } 25268 25269 $element.html(ctrl.template); 25270 $compile($element.contents())(scope); 25271 } 25272 }; 25273 }]; 25274 25275/** 25276 * @ngdoc directive 25277 * @name ngInit 25278 * @restrict AC 25279 * 25280 * @description 25281 * The `ngInit` directive allows you to evaluate an expression in the 25282 * current scope. 25283 * 25284 * <div class="alert alert-danger"> 25285 * This directive can be abused to add unnecessary amounts of logic into your templates. 25286 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 25287 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 25288 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 25289 * rather than `ngInit` to initialize values on a scope. 25290 * </div> 25291 * 25292 * <div class="alert alert-warning"> 25293 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 25294 * sure you have parentheses to ensure correct operator precedence: 25295 * <pre class="prettyprint"> 25296 * `<div ng-init="test1 = ($index | toString)"></div>` 25297 * </pre> 25298 * </div> 25299 * 25300 * @priority 450 25301 * 25302 * @element ANY 25303 * @param {expression} ngInit {@link guide/expression Expression} to eval. 25304 * 25305 * @example 25306 <example module="initExample"> 25307 <file name="index.html"> 25308 <script> 25309 angular.module('initExample', []) 25310 .controller('ExampleController', ['$scope', function($scope) { 25311 $scope.list = [['a', 'b'], ['c', 'd']]; 25312 }]); 25313 </script>
25314 <div ng-controller="ExampleController"> 25315 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 25316 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 25317 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 25318 </div> 25319 </div> 25320 </div> 25321 </file> 25322 <file name="protractor.js" type="protractor"> 25323 it('should alias index positions', function() { 25324 var elements = element.all(by.css('.example-init')); 25325 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 25326 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 25327 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 25328 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 25329 }); 25330 </file> 25331 </example> 25332 */ 25333var ngInitDirective = ngDirective({ 25334 priority: 450, 25335 compile: function() { 25336 return { 25337 pre: function(scope, element, attrs) { 25338 scope.$eval(attrs.ngInit); 25339 } 25340 }; 25341 } 25342}); 25343 25344/** 25345 * @ngdoc directive 25346 * @name ngList 25347 * 25348 * @description 25349 * Text input that converts between a delimited string and an array of strings. The default 25350 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 25351 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 25352 * 25353 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 25354 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 25355 * list item is respected. This implies that the user of the directive is responsible for 25356 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 25357 * tab or newline character. 25358 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 25359 * when joining the list items back together) and whitespace around each list item is stripped 25360 * before it is added to the model. 25361 * 25362 * ### Example with Validation 25363 * 25364 * <example name="ngList-directive" module="listExample"> 25365 * <file name="app.js"> 25366 * angular.module('listExample', []) 25367 * .controller('ExampleController', ['$scope', function($scope) { 25368 * $scope.names = ['morpheus', 'neo', 'trinity']; 25369 * }]); 25370 * </file> 25371 * <file name="index.html">
25372 * <form name="myForm" ng-controller="ExampleController"> 25373 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 25374 * <span role="alert"> 25375 * <span class="error" ng-show="myForm.namesInput.$error.required"> 25376 * Required!</span> 25377 * </span> 25378 * <br> 25379 * <tt>names = {{names}}</tt><br/> 25380 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 25381 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 25382 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25383 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25384 * </form> 25385 * </file> 25386 * <file name="protractor.js" type="protractor"> 25387 * var listInput = element(by.model('names')); 25388 * var names = element(by.exactBinding('names')); 25389 * var valid = element(by.binding('myForm.namesInput.$valid')); 25390 * var error = element(by.css('span.error')); 25391 * 25392 * it('should initialize to model', function() { 25393 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 25394 * expect(valid.getText()).toContain('true'); 25395 * expect(error.getCssValue('display')).toBe('none'); 25396 * }); 25397 * 25398 * it('should be invalid if empty', function() { 25399 * listInput.clear(); 25400 * listInput.sendKeys(''); 25401 * 25402 * expect(names.getText()).toContain(''); 25403 * expect(valid.getText()).toContain('false'); 25404 * expect(error.getCssValue('display')).not.toBe('none'); 25405 * }); 25406 * </file> 25407 * </example> 25408 * 25409 * ### Example - splitting on newline 25410 * <example name="ngList-directive-newlines"> 25411 * <file name="index.html"> 25412 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 25413 * <pre>{{ list | json }}</pre> 25414 * </file> 25415 * <file name="protractor.js" type="protractor"> 25416 * it("should split the text by newlines", function() { 25417 * var listInput = element(by.model('list')); 25418 * var output = element(by.binding('list | json')); 25419 * listInput.sendKeys('abc\ndef\nghi'); 25420 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 25421 * }); 25422 * </file> 25423 * </example> 25424 * 25425 * @element input 25426 * @param {string=} ngList optional delimiter that should be used to split the value. 25427 */ 25428var ngListDirective = function() { 25429 return { 25430 restrict: 'A', 25431 priority: 100, 25432 require: 'ngModel', 25433 link: function(scope, element, attr, ctrl) { 25434 // We want to control whitespace trimming so we use this convoluted approach 25435 // to access the ngList attribute, which doesn't pre-trim the attribute 25436 var ngList = element.attr(attr.$attr.ngList) || ', '; 25437 var trimValues = attr.ngTrim !== 'false'; 25438 var separator = trimValues ? trim(ngList) : ngList; 25439 25440 var parse = function(viewValue) { 25441 // If the viewValue is invalid (say required but empty) it will be `undefined` 25442 if (isUndefined(viewValue)) return; 25443 25444 var list = []; 25445 25446 if (viewValue) { 25447 forEach(viewValue.split(separator), function(value) { 25448 if (value) list.push(trimValues ? trim(value) : value); 25449 }); 25450 } 25451 25452 return list; 25453 }; 25454 25455 ctrl.$parsers.push(parse); 25456 ctrl.$formatters.push(function(value) { 25457 if (isArray(value)) { 25458 return value.join(ngList); 25459 } 25460 25461 return undefined; 25462 }); 25463 25464 // Override the standard $isEmpty because an empty array means the input is empty. 25465 ctrl.$isEmpty = function(value) { 25466 return !value || !value.length; 25467 }; 25468 } 25469 }; 25470}; 25471 25472/* global VALID_CLASS: true, 25473 INVALID_CLASS: true, 25474 PRISTINE_CLASS: true, 25475 DIRTY_CLASS: true, 25476 UNTOUCHED_CLASS: true, 25477 TOUCHED_CLASS: true, 25478*/ 25479 25480var VALID_CLASS = 'ng-valid', 25481 INVALID_CLASS = 'ng-invalid', 25482 PRISTINE_CLASS = 'ng-pristine', 25483 DIRTY_CLASS = 'ng-dirty', 25484 UNTOUCHED_CLASS = 'ng-untouched', 25485 TOUCHED_CLASS = 'ng-touched', 25486 PENDING_CLASS = 'ng-pending', 25487 EMPTY_CLASS = 'ng-empty', 25488 NOT_EMPTY_CLASS = 'ng-not-empty'; 25489 25490var ngModelMinErr = minErr('ngModel'); 25491 25492/** 25493 * @ngdoc type 25494 * @name ngModel.NgModelController 25495 * 25496 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 25497 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 25498 * is set. 25499 * @property {*} $modelValue The value in the model that the control is bound to. 25500 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 25501 the control reads value from the DOM. The functions are called in array order, each passing 25502 its return value through to the next. The last return value is forwarded to the 25503 {@link ngModel.NgModelController#$validators `$validators`} collection. 25504 25505Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 25506`$viewValue`}. 25507 25508Returning `undefined` from a parser means a parse error occurred. In that case, 25509no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 25510will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 25511is set to `true`. The parse error is stored in `ngModel.$error.parse`. 25512 25513 * 25514 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 25515 the model value changes. The functions are called in reverse array order, each passing the value through to the 25516 next. The last return value is used as the actual DOM value. 25517 Used to format / convert values for display in the control. 25518 * ```js 25519 * function formatter(value) { 25520 * if (value) { 25521 * return value.toUpperCase(); 25522 * } 25523 * } 25524 * ngModel.$formatters.push(formatter); 25525 * ``` 25526 * 25527 * @property {Object.<string, function>} $validators A collection of validators that are applied 25528 * whenever the model value changes. The key value within the object refers to the name of the 25529 * validator while the function refers to the validation operation. The validation operation is 25530 * provided with the model value as an argument and must return a true or false value depending 25531 * on the response of that validation. 25532 * 25533 * ```js 25534 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 25535 * var value = modelValue || viewValue; 25536 * return /[0-9]+/.test(value) && 25537 * /[a-z]+/.test(value) && 25538 * /[A-Z]+/.test(value) && 25539 * /\W+/.test(value); 25540 * }; 25541 * ``` 25542 * 25543 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 25544 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 25545 * is expected to return a promise when it is run during the model validation process. Once the promise 25546 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 25547 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 25548 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 25549 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 25550 * will only run once all synchronous validators have passed. 25551 * 25552 * Please note that if $http is used then it is important that the server returns a success HTTP response code 25553 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 25554 * 25555 * ```js 25556 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 25557 * var value = modelValue || viewValue; 25558 * 25559 * // Lookup user by username 25560 * return $http.get('/api/users/' + value). 25561 * then(function resolved() { 25562 * //username exists, this means validation fails 25563 * return $q.reject('exists'); 25564 * }, function rejected() { 25565 * //username does not exist, therefore this validation passes 25566 * return true; 25567 * }); 25568 * }; 25569 * ``` 25570 * 25571 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 25572 * view value has changed. It is called with no arguments, and its return value is ignored. 25573 * This can be used in place of additional $watches against the model value. 25574 * 25575 * @property {Object} $error An object hash with all failing validator ids as keys. 25576 * @property {Object} $pending An object hash with all pending validator ids as keys. 25577 * 25578 * @property {boolean} $untouched True if control has not lost focus yet. 25579 * @property {boolean} $touched True if control has lost focus. 25580 * @property {boolean} $pristine True if user has not interacted with the control yet. 25581 * @property {boolean} $dirty True if user has already interacted with the control. 25582 * @property {boolean} $valid True if there is no error. 25583 * @property {boolean}
25583 $invalid True if at least one error on the control. 25584 * @property {string} $name The name attribute of the control. 25585 * 25586 * @description 25587 * 25588 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 25589 * The controller contains services for data-binding, validation, CSS updates, and value formatting 25590 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 25591 * listening to DOM events. 25592 * Such DOM related logic should be provided by other directives which make use of 25593 * `NgModelController` for data-binding to control elements. 25594 * Angular provides this DOM logic for most {@link input `input`} elements. 25595 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 25596 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 25597 * 25598 * @example 25599 * ### Custom Control Example 25600 * This example shows how to use `NgModelController` with a custom control to achieve 25601 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 25602 * collaborate together to achieve the desired result. 25603 * 25604 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 25605 * contents be edited in place by the user. 25606 * 25607 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 25608 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 25609 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 25610 * that content using the `$sce` service. 25611 * 25612 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 25613 <file name="style.css"> 25614 [contenteditable] { 25615 border: 1px solid black; 25616 background-color: white; 25617 min-height: 20px; 25618 } 25619 25620 .ng-invalid { 25621 border: 1px solid red; 25622 } 25623 25624 </file> 25625 <file name="script.js"> 25626 angular.module('customControl', ['ngSanitize']). 25627 directive('contenteditable', ['$sce', function($sce) { 25628 return { 25629 restrict: 'A', // only activate on element attribute 25630 require: '?ngModel', // get a hold of NgModelController 25631 link: function(scope, element, attrs, ngModel) { 25632 if (!ngModel) return; // do nothing if no ng-model 25633 25634 // Specify how UI should be updated 25635 ngModel.$render = function() { 25636 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 25637 }; 25638 25639 // Listen for change events to enable binding 25640 element.on('blur keyup change', function() { 25641 scope.$evalAsync(read); 25642 }); 25643 read(); // initialize 25644 25645 // Write data to the model 25646 function read() { 25647 var html = element.html(); 25648 // When we clear the content editable the browser leaves a <br> behind 25649 // If strip-br attribute is provided then we strip this out 25650 if ( attrs.stripBr && html == '<br>' ) { 25651 html = ''; 25652 } 25653 ngModel.$setViewValue(html); 25654 } 25655 } 25656 }; 25657 }]); 25658 </file> 25659 <file name="index.html"> 25660 <form name="myForm"> 25661 <div contenteditable 25662 name="myWidget" ng-model="userContent" 25663 strip-br="true" 25664 required>Change me!</div> 25665 <span ng-show="myForm.myWidget.$error.required">Required!</span> 25666 <hr> 25667 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 25668 </form> 25669 </file> 25670 <file name="protractor.js" type="protractor"> 25671 it('should data-bind and become invalid', function() { 25672 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 25673 // SafariDriver can't handle contenteditable 25674 // and Firefox driver can't clear contenteditables very well 25675 return; 25676 } 25677 var contentEditable = element(by.css('[contenteditable]')); 25678 var content = 'Change me!'; 25679 25680 expect(contentEditable.getText()).toEqual(content); 25681 25682 contentEditable.clear(); 25683 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 25684 expect(contentEditable.getText()).toEqual(''); 25685 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 25686 });
25687 </file> 25688 * </example> 25689 * 25690 * 25691 */ 25692var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 25693 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 25694 this.$viewValue = Number.NaN; 25695 this.$modelValue = Number.NaN; 25696 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 25697 this.$validators = {}; 25698 this.$asyncValidators = {}; 25699 this.$parsers = []; 25700 this.$formatters = []; 25701 this.$viewChangeListeners = []; 25702 this.$untouched = true; 25703 this.$touched = false; 25704 this.$pristine = true; 25705 this.$dirty = false; 25706 this.$valid = true; 25707 this.$invalid = false; 25708 this.$error = {}; // keep invalid keys here 25709 this.$$success = {}; // keep valid keys here 25710 this.$pending = undefined; // keep pending keys here 25711 this.$name = $interpolate($attr.name || '', false)($scope); 25712 this.$$parentForm = nullFormCtrl; 25713 25714 var parsedNgModel = $parse($attr.ngModel), 25715 parsedNgModelAssign = parsedNgModel.assign, 25716 ngModelGet = parsedNgModel, 25717 ngModelSet = parsedNgModelAssign, 25718 pendingDebounce = null, 25719 parserValid, 25720 ctrl = this; 25721 25722 this.$$setOptions = function(options) { 25723 ctrl.$options = options; 25724 if (options && options.getterSetter) { 25725 var invokeModelGetter = $parse($attr.ngModel + '()'), 25726 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 25727 25728 ngModelGet = function($scope) { 25729 var modelValue = parsedNgModel($scope); 25730 if (isFunction(modelValue)) { 25731 modelValue = invokeModelGetter($scope); 25732 } 25733 return modelValue; 25734 }; 25735 ngModelSet = function($scope, newValue) { 25736 if (isFunction(parsedNgModel($scope))) { 25737 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 25738 } else { 25739 parsedNgModelAssign($scope, ctrl.$modelValue); 25740 } 25741 }; 25742 } else if (!parsedNgModel.assign) { 25743 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 25744 $attr.ngModel, startingTag($element)); 25745 } 25746 }; 25747 25748 /** 25749 * @ngdoc method 25750 * @name ngModel.NgModelController#$render 25751 * 25752 * @description 25753 * Called when the view needs to be updated. It is expected that the user of the ng-model 25754 * directive will implement this method. 25755 * 25756 * The `$render()` method is invoked in the following situations: 25757 * 25758 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 25759 * committed value then `$render()` is called to update the input control. 25760 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 25761 * the `$viewValue` are different from last time. 25762 * 25763 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 25764 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` 25765 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 25766 * invoked if you only change a property on the objects. 25767 */ 25768 this.$render = noop; 25769 25770 /** 25771 * @ngdoc method 25772 * @name ngModel.NgModelController#$isEmpty 25773 * 25774 * @description 25775 * This is called when we need to determine if the value of an input is empty. 25776 * 25777 * For instance, the required directive does this to work out if the input has data or not. 25778 * 25779 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 25780 * 25781 * You can override this for input directives whose concept of being empty is different from the 25782 * default. The `checkboxInputType` directive does this because in its case a value of `false` 25783 * implies empty. 25784 * 25785 * @param {*} value The value of the input to check for emptiness. 25786 * @returns {boolean} True if `value` is "empty". 25787 */ 25788 this.$isEmpty = function(value) { 25789 return isUndefined(value) || value === '' || value === null || value !== value; 25790 }; 25791 25792 this.$$updateEmptyClasses = function(value) { 25793 if (ctrl.$isEmpty(value)) { 25794 $animate.removeClass($element, NOT_EMPTY_CLASS); 25795 $animate.addClass($element, EMPTY_CLASS); 25796 } else { 25797 $animate.removeClass($element, EMPTY_CLASS); 25798 $animate.addClass($element, NOT_EMPTY_CLASS); 25799 } 25800 }; 25801 25802 25803 var currentValidationRunId = 0; 25804 25805 /** 25806 * @ngdoc method 25807 * @name ngModel.NgModelController#$setValidity 25808 * 25809 * @description 25810 * Change the validity state, and notify the form. 25811 * 25812 * This method can be called within $parsers/$formatters or a custom validation implementation. 25813 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 25814 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 25815 * 25816 * @param {string}
25816 validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 25817 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 25818 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 25819 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 25820 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 25821 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 25822 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 25823 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 25824 * Skipped is used by Angular when validators do not run because of parse errors and 25825 * when `$asyncValidators` do not run because any of the `$validators` failed. 25826 */ 25827 addSetValidityMethod({ 25828 ctrl: this, 25829 $element: $element, 25830 set: function(object, property) { 25831 object[property] = true; 25832 }, 25833 unset: function(object, property) { 25834 delete object[property]; 25835 }, 25836 $animate: $animate 25837 }); 25838 25839 /** 25840 * @ngdoc method 25841 * @name ngModel.NgModelController#$setPristine 25842 * 25843 * @description 25844 * Sets the control to its pristine state. 25845 * 25846 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 25847 * state (`ng-pristine` class). A model is considered to be pristine when the control 25848 * has not been changed from when first compiled. 25849 */ 25850 this.$setPristine = function() { 25851 ctrl.$dirty = false; 25852 ctrl.$pristine = true; 25853 $animate.removeClass($element, DIRTY_CLASS); 25854 $animate.addClass($element, PRISTINE_CLASS); 25855 }; 25856 25857 /** 25858 * @ngdoc method 25859 * @name ngModel.NgModelController#$setDirty 25860 * 25861 * @description 25862 * Sets the control to its dirty state. 25863 * 25864 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 25865 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 25866 * from when first compiled. 25867 */ 25868 this.$setDirty = function() { 25869 ctrl.$dirty = true; 25870 ctrl.$pristine = false; 25871 $animate.removeClass($element, PRISTINE_CLASS); 25872 $animate.addClass($element, DIRTY_CLASS); 25873 ctrl.$$parentForm.$setDirty(); 25874 }; 25875 25876 /** 25877 * @ngdoc method 25878 * @name ngModel.NgModelController#$setUntouched 25879 * 25880 * @description 25881 * Sets the control to its untouched state. 25882 * 25883 * This method can be called to remove the `ng-touched` class and set the control to its 25884 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 25885 * by default, however this function can be used to restore that state if the model has 25886 * already been touched by the user. 25887 */ 25888 this.$setUntouched = function() { 25889 ctrl.$touched = false; 25890 ctrl.$untouched = true; 25891 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 25892 }; 25893 25894 /** 25895 * @ngdoc method 25896 * @name ngModel.NgModelController#$setTouched 25897 * 25898 * @description 25899 * Sets the control to its touched state. 25900 * 25901 * This method can be called to remove the `ng-untouched` class and set the control to its 25902 * touched state (`ng-touched` class). A model is considered to be touched when the user has 25903 * first focused the control element and then shifted focus away from the control (blur event). 25904 */ 25905 this.$setTouched = function() { 25906 ctrl.$touched = true; 25907 ctrl.$untouched = false; 25908 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 25909 }; 25910 25911 /** 25912 * @ngdoc method 25913 * @name ngModel.NgModelController#$rollbackViewValue 25914 * 25915 * @description 25916 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 25917 * which may be caused by a pending debounced event or because the input is waiting for a some 25918 * future event. 25919 * 25920 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 25921 * depend on special events such as blur, you can have a situation where there is a period when 25922 * the `$viewValue` is out of sync with the ngModel's `$modelValue`. 25923 * 25924 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 25925 * and reset the input to the last committed view value. 25926 * 25927 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 25928 * programmatically before these debounced/future events have resolved/occurred, because Angular's 25929 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 25930 * 25931 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 25932 * input which may have such events pending. This is important in order to make sure that the 25933 * input field will be updated with the new model value and any pending operations are cancelled. 25934 * 25935 * <example name="ng-model-cancel-update" module="cancel-update-example"> 25936 * <file name="app.js">
25937 * angular.module('cancel-update-example', []) 25938 * 25939 * .controller('CancelUpdateController', ['$scope', function($scope) { 25940 * $scope.model = {}; 25941 * 25942 * $scope.setEmpty = function(e, value, rollback) { 25943 * if (e.keyCode == 27) { 25944 * e.preventDefault(); 25945 * if (rollback) { 25946 * $scope.myForm[value].$rollbackViewValue(); 25947 * } 25948 * $scope.model[value] = ''; 25949 * } 25950 * }; 25951 * }]); 25952 * </file> 25953 * <file name="index.html"> 25954 * <div ng-controller="CancelUpdateController"> 25955 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 25956 * empty them. Follow these steps and observe the difference:</p> 25957 * <ol> 25958 * <li>Type something in the input. You will see that the model is not yet updated</li> 25959 * <li>Press the Escape key. 25960 * <ol> 25961 * <li> In the first example, nothing happens, because the model is already '', and no 25962 * update is detected. If you blur the input, the model will be set to the current view. 25963 * </li> 25964 * <li> In the second example, the pending update is cancelled, and the input is set back 25965 * to the last committed view value (''). Blurring the input does nothing. 25966 * </li> 25967 * </ol> 25968 * </li> 25969 * </ol> 25970 * 25971 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 25972 * <div> 25973 * <p id="inputDescription1">Without $rollbackViewValue():</p> 25974 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 25975 * ng-keydown="setEmpty($event, 'value1')"> 25976 * value1: "{{ model.value1 }}" 25977 * </div> 25978 * 25979 * <div> 25980 * <p id="inputDescription2">With $rollbackViewValue():</p> 25981 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 25982 * ng-keydown="setEmpty($event, 'value2', true)"> 25983 * value2: "{{ model.value2 }}" 25984 * </div> 25985 * </form> 25986 * </div> 25987 * </file> 25988 <file name="style.css"> 25989 div { 25990 display: table-cell; 25991 } 25992 div:nth-child(1) { 25993 padding-right: 30px; 25994 } 25995 25996 </file> 25997 * </example> 25998 */ 25999 this.$rollbackViewValue = function() { 26000 $timeout.cancel(pendingDebounce); 26001 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 26002 ctrl.$render(); 26003 }; 26004 26005 /** 26006 * @ngdoc method 26007 * @name ngModel.NgModelController#$validate 26008 * 26009 * @description 26010 * Runs each of the registered validators (first synchronous validators and then 26011 * asynchronous validators). 26012 * If the validity changes to invalid, the model will be set to `undefined`, 26013 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 26014 * If the validity changes to valid, it will set the model to the last available valid 26015 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 26016 */ 26017 this.$validate = function() { 26018 // ignore $validate before model is initialized 26019 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 26020 return; 26021 } 26022 26023 var viewValue = ctrl.$$lastCommittedViewValue; 26024 // Note: we use the $$rawModelValue as $modelValue might have been 26025 // set to undefined during a view -> model update that found validation 26026 // errors. We can't parse the view here, since that could change 26027 // the model although neither viewValue nor the model on the scope changed 26028 var modelValue = ctrl.$$rawModelValue; 26029 26030 var prevValid = ctrl.$valid; 26031 var prevModelValue = ctrl.$modelValue; 26032 26033 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 26034 26035 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 26036 // If there was no change in validity, don't update the model 26037 // This prevents changing an invalid modelValue to undefined 26038 if (!allowInvalid && prevValid !== allValid) { 26039 // Note: Don't check ctrl.$valid here, as we could have 26040 // external validators (e.g. calculated on the server), 26041 // that just call $setValidity and need the model value 26042 // to calculate their validity. 26043 ctrl.$modelValue = allValid ? modelValue : undefined; 26044 26045 if (ctrl.$modelValue !== prevModelValue) { 26046 ctrl.$$writeModelToScope(); 26047 } 26048 } 26049 });
26050 26051 }; 26052 26053 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 26054 currentValidationRunId++; 26055 var localValidationRunId = currentValidationRunId; 26056 26057 // check parser error 26058 if (!processParseErrors()) { 26059 validationDone(false); 26060 return; 26061 } 26062 if (!processSyncValidators()) { 26063 validationDone(false); 26064 return; 26065 } 26066 processAsyncValidators(); 26067 26068 function processParseErrors() { 26069 var errorKey = ctrl.$$parserName || 'parse'; 26070 if (isUndefined(parserValid)) { 26071 setValidity(errorKey, null); 26072 } else { 26073 if (!parserValid) { 26074 forEach(ctrl.$validators, function(v, name) { 26075 setValidity(name, null); 26076 }); 26077 forEach(ctrl.$asyncValidators, function(v, name) { 26078 setValidity(name, null); 26079 }); 26080 } 26081 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 26082 setValidity(errorKey, parserValid); 26083 return parserValid; 26084 } 26085 return true; 26086 } 26087 26088 function processSyncValidators() { 26089 var syncValidatorsValid = true; 26090 forEach(ctrl.$validators, function(validator, name) { 26091 var result = validator(modelValue, viewValue); 26092 syncValidatorsValid = syncValidatorsValid && result; 26093 setValidity(name, result); 26094 }); 26095 if (!syncValidatorsValid) { 26096 forEach(ctrl.$asyncValidators, function(v, name) { 26097 setValidity(name, null); 26098 }); 26099 return false; 26100 } 26101 return true; 26102 } 26103 26104 function processAsyncValidators() { 26105 var validatorPromises = []; 26106 var allValid = true; 26107 forEach(ctrl.$asyncValidators, function(validator, name) { 26108 var promise = validator(modelValue, viewValue); 26109 if (!isPromiseLike(promise)) { 26110 throw ngModelMinErr("$asyncValidators", 26111 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 26112 } 26113 setValidity(name, undefined); 26114 validatorPromises.push(promise.then(function() { 26115 setValidity(name, true); 26116 }, function(error) { 26117 allValid = false; 26118 setValidity(name, false); 26119 })); 26120 }); 26121 if (!validatorPromises.length) { 26122 validationDone(true); 26123 } else { 26124 $q.all(validatorPromises).then(function() { 26125 validationDone(allValid); 26126 }, noop); 26127 } 26128 } 26129 26130 function setValidity(name, isValid) { 26131 if (localValidationRunId === currentValidationRunId) { 26132 ctrl.$setValidity(name, isValid); 26133 } 26134 } 26135 26136 function validationDone(allValid) { 26137 if (localValidationRunId === currentValidationRunId) { 26138 26139 doneCallback(allValid); 26140 } 26141 } 26142 }; 26143 26144 /** 26145 * @ngdoc method 26146 * @name ngModel.NgModelController#$commitViewValue 26147 * 26148 * @description 26149 * Commit a pending update to the `$modelValue`. 26150 * 26151 * Updates may be pending by a debounced event or because the input is waiting for a some future 26152 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 26153 * usually handles calling this in response to input events. 26154 */ 26155 this.$commitViewValue = function() { 26156 var viewValue = ctrl.$viewValue; 26157 26158 $timeout.cancel(pendingDebounce); 26159 26160 // If the view value has not changed then we should just exit, except in the case where there is 26161 // a native validator on the element. In this case the validation state may have changed even though 26162 // the viewValue has stayed empty. 26163 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 26164 return; 26165 } 26166 ctrl.$$updateEmptyClasses(viewValue); 26167 ctrl.$$lastCommittedViewValue = viewValue; 26168 26169 // change to dirty 26170 if (ctrl.$pristine) { 26171 this.$setDirty(); 26172 } 26173 this.$$parseAndValidate(); 26174 }; 26175 26176 this.$$parseAndValidate = function() { 26177 var viewValue = ctrl.$$lastCommittedViewValue; 26178 var modelValue = viewValue; 26179 parserValid = isUndefined(modelValue) ? undefined : true; 26180 26181 if (parserValid) { 26182 for (var i = 0; i < ctrl.$parsers.length; i++) { 26183 modelValue = ctrl.$parsers[i](modelValue); 26184 if (isUndefined(modelValue)) { 26185 parserValid = false; 26186 break; 26187 } 26188 } 26189 } 26190 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 26191 // ctrl.$modelValue has not been touched yet... 26192 ctrl.$modelValue = ngModelGet($scope); 26193 } 26194 var prevModelValue = ctrl.$modelValue; 26195 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 26196 ctrl.$$rawModelValue = modelValue; 26197 26198 if (allowInvalid) { 26199 ctrl.$modelValue = modelValue; 26200 writeToModelIfNeeded(); 26201 } 26202 26203 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 26204 // This can happen if e.g. $setViewValue is called from inside a parser 26205 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 26206 if (!allowInvalid) { 26207 // Note: Don't check ctrl.$valid here, as we could have 26208 // external validators (e.g. calculated on the server), 26209 // that just call $setValidity and need the model value 26210 // to calculate their validity. 26211 ctrl.$modelValue = allValid ? modelValue : undefined; 26212 writeToModelIfNeeded(); 26213 } 26214 });
26215 26216 function writeToModelIfNeeded() { 26217 if (ctrl.$modelValue !== prevModelValue) { 26218 ctrl.$$writeModelToScope(); 26219 } 26220 } 26221 }; 26222 26223 this.$$writeModelToScope = function() { 26224 ngModelSet($scope, ctrl.$modelValue); 26225 forEach(ctrl.$viewChangeListeners, function(listener) { 26226 try { 26227 listener(); 26228 } catch (e) { 26229 $exceptionHandler(e); 26230 } 26231 }); 26232 }; 26233 26234 /** 26235 * @ngdoc method 26236 * @name ngModel.NgModelController#$setViewValue 26237 * 26238 * @description 26239 * Update the view value. 26240 * 26241 * This method should be called when a control wants to change the view value; typically, 26242 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 26243 * directive calls it when the value of the input changes and {@link ng.directive:select select} 26244 * calls it when an option is selected. 26245 * 26246 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 26247 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 26248 * value sent directly for processing, finally to be applied to `$modelValue` and then the 26249 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 26250 * in the `$viewChangeListeners` list, are called. 26251 * 26252 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 26253 * and the `default` trigger is not listed, all those actions will remain pending until one of the 26254 * `updateOn` events is triggered on the DOM element. 26255 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 26256 * directive is used with a custom debounce for this particular event. 26257 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 26258 * is specified, once the timer runs out. 26259 * 26260 * When used with standard inputs, the view value will always be a string (which is in some cases 26261 * parsed into another type, such as a `Date` object for `input[date]`.) 26262 * However, custom controls might also pass objects to this method. In this case, we should make 26263 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 26264 * perform a deep watch of objects, it only looks for a change of identity. If you only change 26265 * the property of the object then ngModel will not realize that the object has changed and 26266 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 26267 * not change properties of the copy once it has been passed to `$setViewValue`. 26268 * Otherwise you may cause the model value on the scope to change incorrectly. 26269 * 26270 * <div class="alert alert-info"> 26271 * In any case, the value passed to the method should always reflect the current value 26272 * of the control. For example, if you are calling `$setViewValue` for an input element, 26273 * you should pass the input DOM value. Otherwise, the control and the scope model become 26274 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 26275 * the control's DOM value in any way. If we want to change the control's DOM value 26276 * programmatically, we should update the `ngModel` scope expression. Its new value will be 26277 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 26278 * to update the DOM, and finally call `$validate` on it. 26279 * </div> 26280 * 26281 * @param {*} value value from the view. 26282 * @param {string} trigger Event that triggered the update. 26283 */ 26284 this.$setViewValue = function(value, trigger) { 26285 ctrl.$viewValue = value; 26286 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 26287 ctrl.$$debounceViewValueCommit(trigger); 26288 } 26289 }; 26290 26291 this.$$debounceViewValueCommit = function(trigger) { 26292 var debounceDelay = 0, 26293 options = ctrl.$options, 26294 debounce; 26295 26296 if (options && isDefined(options.debounce)) { 26297 debounce = options.debounce; 26298 if (isNumber(debounce)) { 26299 debounceDelay = debounce; 26300 } else if (isNumber(debounce[trigger])) { 26301 debounceDelay = debounce[trigger]; 26302 } else if (isNumber(debounce['default'])) { 26303 debounceDelay = debounce['default']; 26304 } 26305 } 26306 26307 $timeout.cancel(pendingDebounce); 26308 if (debounceDelay) { 26309 pendingDebounce = $timeout(function() { 26310 ctrl.$commitViewValue(); 26311 }, debounceDelay); 26312 } else if ($rootScope.$$phase) { 26313 ctrl.$commitViewValue(); 26314 } else { 26315 $scope.$apply(function() { 26316 ctrl.$commitViewValue(); 26317 }); 26318 } 26319 }; 26320 26321 // model -> value 26322 // Note: we cannot use a normal scope.$watch as we want to detect the following: 26323 // 1. scope value is 'a' 26324 // 2. user enters 'b' 26325 // 3. ng-change kicks in and reverts scope value to 'a' 26326 // ->
26326 scope value did not change since the last digest as 26327 // ng-change executes in apply phase 26328 // 4. view should be changed back to 'a' 26329 $scope.$watch(function ngModelWatch() { 26330 var modelValue = ngModelGet($scope); 26331 26332 // if scope model value and ngModel value are out of sync 26333 // TODO(perf): why not move this to the action fn? 26334 if (modelValue !== ctrl.$modelValue && 26335 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 26336 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 26337 ) { 26338 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 26339 parserValid = undefined; 26340 26341 var formatters = ctrl.$formatters, 26342 idx = formatters.length; 26343 26344 var viewValue = modelValue; 26345 while (idx--) { 26346 viewValue = formatters[idx](viewValue); 26347 } 26348 if (ctrl.$viewValue !== viewValue) { 26349 ctrl.$$updateEmptyClasses(viewValue); 26350 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 26351 ctrl.$render(); 26352 26353 ctrl.$$runValidators(modelValue, viewValue, noop); 26354 } 26355 } 26356 26357 return modelValue; 26358 }); 26359}]; 26360 26361 26362/** 26363 * @ngdoc directive 26364 * @name ngModel 26365 * 26366 * @element input 26367 * @priority 1 26368 * 26369 * @description 26370 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 26371 * property on the scope using {@link ngModel.NgModelController NgModelController}, 26372 * which is created and exposed by this directive. 26373 * 26374 * `ngModel` is responsible for: 26375 * 26376 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 26377 * require. 26378 * - Providing validation behavior (i.e. required, number, email, url). 26379 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 26380 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 26381 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 26382 * - Registering the control with its parent {@link ng.directive:form form}. 26383 * 26384 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 26385 * current scope. If the property doesn't already exist on this scope, it will be created 26386 * implicitly and added to the scope. 26387 * 26388 * For best practices on using `ngModel`, see: 26389 * 26390 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 26391 * 26392 * For basic examples, how to use `ngModel`, see: 26393 * 26394 * - {@link ng.directive:input input} 26395 * - {@link input[text] text} 26396 * - {@link input[checkbox] checkbox} 26397 * - {@link input[radio] radio} 26398 * - {@link input[number] number} 26399 * - {@link input[email] email} 26400 * - {@link input[url] url} 26401 * - {@link input[date] date} 26402 * - {@link input[datetime-local] datetime-local} 26403 * - {@link input[time] time} 26404 * - {@link input[month] month} 26405 * - {@link input[week] week} 26406 * - {@link ng.directive:select select} 26407 * - {@link ng.directive:textarea textarea} 26408 * 26409 * # CSS classes 26410 * The following CSS classes are added and removed on the associated input/select/textarea element 26411 * depending on the validity of the model. 26412 * 26413 * - `ng-valid`: the model is valid 26414 * - `ng-invalid`: the model is invalid 26415 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 26416 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 26417 * - `ng-pristine`: the control hasn't been interacted with yet 26418 * - `ng-dirty`: the control has been interacted with 26419 * - `ng-touched`: the control has been blurred 26420 * - `ng-untouched`: the control hasn't been blurred 26421 * - `ng-pending`: any `$asyncValidators` are unfulfilled 26422 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 26423 * by the {@link ngModel.NgModelController#$isEmpty} method 26424 * - `ng-not-empty`: the view contains a non-empty value 26425 * 26426 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 26427 * 26428 * ## Animation Hooks 26429 * 26430 * Animations within models are triggered when any of the associated CSS classes are added and removed
26431 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 26432 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 26433 * The animations that are triggered within ngModel are similar to how they work in ngClass and 26434 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 26435 * 26436 * The following example shows a simple way to utilize CSS transitions to style an input element 26437 * that has been rendered as invalid after it has been validated: 26438 * 26439 * <pre> 26440 * //be sure to include ngAnimate as a module to hook into more 26441 * //advanced animations 26442 * .my-input { 26443 * transition:0.5s linear all; 26444 * background: white; 26445 * } 26446 * .my-input.ng-invalid { 26447 * background: red; 26448 * color:white; 26449 * } 26450 * </pre> 26451 * 26452 * @example 26453 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 26454 <file name="index.html"> 26455 <script> 26456 angular.module('inputExample', []) 26457 .controller('ExampleController', ['$scope', function($scope) { 26458 $scope.val = '1'; 26459 }]); 26460 </script> 26461 <style> 26462 .my-input { 26463 transition:all linear 0.5s; 26464 background: transparent; 26465 } 26466 .my-input.ng-invalid { 26467 color:white; 26468 background: red; 26469 } 26470 </style> 26471 <p id="inputDescription"> 26472 Update input to see transitions when valid/invalid. 26473 Integer is a valid value. 26474 </p>
26475 <form name="testForm" ng-controller="ExampleController"> 26476 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 26477 aria-describedby="inputDescription" /> 26478 </form> 26479 </file> 26480 * </example> 26481 * 26482 * ## Binding to a getter/setter 26483 * 26484 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 26485 * function that returns a representation of the model when called with zero arguments, and sets 26486 * the internal state of a model when called with an argument. It's sometimes useful to use this 26487 * for models that have an internal representation that's different from what the model exposes 26488 * to the view. 26489 * 26490 * <div class="alert alert-success"> 26491 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 26492 * frequently than other parts of your code. 26493 * </div> 26494 * 26495 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 26496 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 26497 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 26498 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 26499 * 26500 * The following example shows how to use `ngModel` with a getter/setter: 26501 * 26502 * @example 26503 * <example name="ngModel-getter-setter" module="getterSetterExample"> 26504 <file name="index.html">
26505 <div ng-controller="ExampleController"> 26506 <form name="userForm"> 26507 <label>Name: 26508 <input type="text" name="userName" 26509 ng-model="user.name" 26510 ng-model-options="{ getterSetter: true }" /> 26511 </label> 26512 </form> 26513 <pre>user.name = <span ng-bind="user.name()"></span></pre> 26514 </div> 26515 </file> 26516 <file name="app.js"> 26517 angular.module('getterSetterExample', []) 26518 .controller('ExampleController', ['$scope', function($scope) { 26519 var _name = 'Brian'; 26520 $scope.user = { 26521 name: function(newName) { 26522 // Note that newName can be undefined for two reasons: 26523 // 1. Because it is called as a getter and thus called with no arguments 26524 // 2. Because the property should actually be set to undefined. This happens e.g. if the 26525 // input is invalid 26526 return arguments.length ? (_name = newName) : _name; 26527 } 26528 }; 26529 }]); 26530 </file> 26531 * </example> 26532 */ 26533var ngModelDirective = ['$rootScope', function($rootScope) { 26534 return { 26535 restrict: 'A', 26536 require: ['ngModel', '^?form', '^?ngModelOptions'], 26537 controller: NgModelController, 26538 // Prelink needs to run before any input directive 26539 // so that we can set the NgModelOptions in NgModelController 26540 // before anyone else uses it. 26541 priority: 1, 26542 compile: function ngModelCompile(element) { 26543 // Setup initial state of the control 26544 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 26545 26546 return { 26547 pre: function ngModelPreLink(scope, element, attr, ctrls) { 26548 var modelCtrl = ctrls[0], 26549 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 26550 26551 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 26552 26553 // notify others, especially parent forms 26554 formCtrl.$addControl(modelCtrl); 26555 26556 attr.$observe('name', function(newValue) { 26557 if (modelCtrl.$name !== newValue) { 26558 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 26559 } 26560 }); 26561 26562 scope.$on('$destroy', function() { 26563 modelCtrl.$$parentForm.$removeControl(modelCtrl); 26564 }); 26565 }, 26566 post: function ngModelPostLink(scope, element, attr, ctrls) { 26567 var modelCtrl = ctrls[0]; 26568 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 26569 element.on(modelCtrl.$options.updateOn, function(ev) { 26570 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 26571 }); 26572 } 26573 26574 element.on('blur', function(ev) { 26575 if (modelCtrl.$touched) return; 26576 26577 if ($rootScope.$$phase) { 26578 scope.$evalAsync(modelCtrl.$setTouched); 26579 } else { 26580 scope.$apply(modelCtrl.$setTouched); 26581 } 26582 }); 26583 } 26584 }; 26585 } 26586 }; 26587}]; 26588 26589var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 26590 26591/** 26592 * @ngdoc directive 26593 * @name ngModelOptions 26594 * 26595 * @description 26596 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 26597 * events that will trigger a model update and/or a debouncing delay so that the actual update only 26598 * takes place when a timer expires; this timer will be reset after another change takes place. 26599 * 26600 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 26601 * be different from the value in the actual model. This means that if you update the model you 26602 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 26603 * order to make sure it is synchronized with the model and that any debounced action is canceled. 26604 * 26605 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 26606 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 26607 * important because `form` controllers are published to the related scope under the name in their 26608 * `name` attribute. 26609 * 26610 * Any pending changes will take place immediately when an enclosing form is submitted via the 26611 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 26612 * to have access to the updated model. 26613 * 26614 * `ngModelOptions` has an effect on the element it's declared on and its desce
26614ndants. 26615 * 26616 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 26617 * - `updateOn`: string specifying which event should the input be bound to. You can set several 26618 * events using an space delimited list. There is a special event called `default` that 26619 * matches the default events belonging of the control. 26620 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 26621 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 26622 * custom value for each event. For example: 26623 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 26624 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 26625 * not validate correctly instead of the default behavior of setting the model to undefined. 26626 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 26627 `ngModel` as getters/setters. 26628 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 26629 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 26630 * continental US time zone abbreviations, but for general use, use a time zone offset, for 26631 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 26632 * If not specified, the timezone of the browser will be used. 26633 * 26634 * @example 26635 26636 The following example shows how to override immediate updates. Changes on the inputs within the 26637 form will update the model only when the control loses focus (blur event). If `escape` key is 26638 pressed while the input field is focused, the value is reset to the value in the current model. 26639 26640 <example name="ngModelOptions-directive-blur" module="optionsExample"> 26641 <file name="index.html">
26642 <div ng-controller="ExampleController"> 26643 <form name="userForm"> 26644 <label>Name: 26645 <input type="text" name="userName" 26646 ng-model="user.name" 26647 ng-model-options="{ updateOn: 'blur' }" 26648 ng-keyup="cancel($event)" /> 26649 </label><br /> 26650 <label>Other data: 26651 <input type="text" ng-model="user.data" /> 26652 </label><br /> 26653 </form> 26654 <pre>user.name = <span ng-bind="user.name"></span></pre> 26655 <pre>user.data = <span ng-bind="user.data"></span></pre> 26656 </div> 26657 </file> 26658 <file name="app.js"> 26659 angular.module('optionsExample', []) 26660 .controller('ExampleController', ['$scope', function($scope) { 26661 $scope.user = { name: 'John', data: '' }; 26662 26663 $scope.cancel = function(e) { 26664 if (e.keyCode == 27) { 26665 $scope.userForm.userName.$rollbackViewValue(); 26666 } 26667 }; 26668 }]); 26669 </file> 26670 <file name="protractor.js" type="protractor"> 26671 var model = element(by.binding('user.name')); 26672 var input = element(by.model('user.name')); 26673 var other = element(by.model('user.data')); 26674 26675 it('should allow custom events', function() { 26676 input.sendKeys(' Doe'); 26677 input.click(); 26678 expect(model.getText()).toEqual('John'); 26679 other.click(); 26680 expect(model.getText()).toEqual('John Doe'); 26681 }); 26682 26683 it('should $rollbackViewValue when model changes', function() { 26684 input.sendKeys(' Doe'); 26685 expect(input.getAttribute('value')).toEqual('John Doe'); 26686 input.sendKeys(protractor.Key.ESCAPE); 26687 expect(input.getAttribute('value')).toEqual('John'); 26688 other.click(); 26689 expect(model.getText()).toEqual('John'); 26690 }); 26691 </file> 26692 </example> 26693 26694 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 26695 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 26696 26697 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 26698 <file name="index.html">
26699 <div ng-controller="ExampleController"> 26700 <form name="userForm"> 26701 <label>Name: 26702 <input type="text" name="userName" 26703 ng-model="user.name" 26704 ng-model-options="{ debounce: 1000 }" /> 26705 </label> 26706 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 26707 <br /> 26708 </form> 26709 <pre>user.name = <span ng-bind="user.name"></span></pre> 26710 </div> 26711 </file> 26712 <file name="app.js"> 26713 angular.module('optionsExample', []) 26714 .controller('ExampleController', ['$scope', function($scope) { 26715 $scope.user = { name: 'Igor' }; 26716 }]); 26717 </file> 26718 </example> 26719 26720 This one shows how to bind to getter/setters: 26721 26722 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 26723 <file name="index.html"> 26724 <div ng-controller="ExampleController"> 26725 <form name="userForm"> 26726 <label>Name: 26727 <input type="text" name="userName" 26728 ng-model="user.name" 26729 ng-model-options="{ getterSetter: true }" /> 26730 </label> 26731 </form> 26732 <pre>user.name = <span ng-bind="user.name()"></span></pre> 26733 </div> 26734 </file> 26735 <file name="app.js"> 26736 angular.module('getterSetterExample', []) 26737 .controller('ExampleController', ['$scope', function($scope) { 26738 var _name = 'Brian'; 26739 $scope.user = { 26740 name: function(newName) { 26741 // Note that newName can be undefined for two reasons: 26742 // 1. Because it is called as a getter and thus called with no arguments 26743 // 2. Because the property should actually be set to undefined. This happens e.g. if the 26744 // input is invalid 26745 return arguments.length ? (_name = newName) : _name; 26746 } 26747 }; 26748 }]); 26749 </file> 26750 </example> 26751 */ 26752var ngModelOptionsDirective = function() { 26753 return { 26754 restrict: 'A', 26755 controller: ['$scope', '$attrs', function($scope, $attrs) { 26756 var that = this; 26757 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 26758 // Allow adding/overriding bound events 26759 if (isDefined(this.$options.updateOn)) { 26760 this.$options.updateOnDefault = false; 26761 // extract "default" pseudo-event from list of events that can trigger a model update 26762 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 26763 that.$options.updateOnDefault = true; 26764 return ' '; 26765 })); 26766 } else { 26767 this.$options.updateOnDefault = true; 26768 } 26769 }] 26770 }; 26771}; 26772 26773 26774 26775// helper methods 26776function addSetValidityMethod(context) { 26777 var ctrl = context.ctrl, 26778 $element = context.$element, 26779 classCache = {}, 26780 set = context.set, 26781 unset = context.unset, 26782 $animate = context.$animate; 26783 26784 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 26785 26786 ctrl.$setValidity = setValidity; 26787 26788 function setValidity(validationErrorKey, state, controller) { 26789 if (isUndefined(state)) { 26790 createAndSet('$pending', validationErrorKey, controller); 26791 } else { 26792 unsetAndCleanup('$pending', validationErrorKey, controller); 26793 } 26794 if (!isBoolean(state)) { 26795 unset(ctrl.$error, validationErrorKey, controller); 26796 unset(ctrl.$$success, validationErrorKey, controller); 26797 } else { 26798 if (state) { 26799 unset(ctrl.$error, validationErrorKey, controller); 26800 set(ctrl.$$success, validationErrorKey, controller); 26801 } else { 26802 set(ctrl.$error, validationErrorKey, controller); 26803 unset(ctrl.$$success, validationErrorKey, controller); 26804 } 26805 } 26806 if (ctrl.$pending) { 26807 cachedToggleClass(PENDING_CLASS, true); 26808 ctrl.$valid = ctrl.$invalid = undefined; 26809 toggleValidationCss('', null); 26810 } else { 26811 cachedToggleClass(PENDING_CLASS, false); 26812 ctrl.$valid = isObjectEmpty(ctrl.$error);
26813 ctrl.$invalid = !ctrl.$valid; 26814 toggleValidationCss('', ctrl.$valid); 26815 } 26816 26817 // re-read the state as the set/unset methods could have 26818 // combined state in ctrl.$error[validationError] (used for forms), 26819 // where setting/unsetting only increments/decrements the value, 26820 // and does not replace it. 26821 var combinedState; 26822 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 26823 combinedState = undefined; 26824 } else if (ctrl.$error[validationErrorKey]) { 26825 combinedState = false; 26826 } else if (ctrl.$$success[validationErrorKey]) { 26827 combinedState = true; 26828 } else { 26829 combinedState = null; 26830 } 26831 26832 toggleValidationCss(validationErrorKey, combinedState); 26833 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 26834 } 26835 26836 function createAndSet(name, value, controller) { 26837 if (!ctrl[name]) { 26838 ctrl[name] = {}; 26839 } 26840 set(ctrl[name], value, controller); 26841 } 26842 26843 function unsetAndCleanup(name, value, controller) { 26844 if (ctrl[name]) { 26845 unset(ctrl[name], value, controller); 26846 } 26847 if (isObjectEmpty(ctrl[name])) { 26848 ctrl[name] = undefined; 26849 } 26850 } 26851 26852 function cachedToggleClass(className, switchValue) { 26853 if (switchValue && !classCache[className]) { 26854 $animate.addClass($element, className); 26855 classCache[className] = true; 26856 } else if (!switchValue && classCache[className]) { 26857 $animate.removeClass($element, className); 26858 classCache[className] = false; 26859 } 26860 } 26861 26862 function toggleValidationCss(validationErrorKey, isValid) { 26863 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 26864 26865 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 26866 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 26867 } 26868} 26869 26870function isObjectEmpty(obj) { 26871 if (obj) { 26872 for (var prop in obj) { 26873 if (obj.hasOwnProperty(prop)) { 26874 return false; 26875 } 26876 } 26877 } 26878 return true; 26879} 26880 26881/** 26882 * @ngdoc directive 26883 * @name ngNonBindable 26884 * @restrict AC 26885 * @priority 1000 26886 * 26887 * @description 26888 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 26889 * DOM element. This is useful if the element contains what appears to be Angular directives and 26890 * bindings but which should be ignored by Angular. This could be the case if you have a site that 26891 * displays snippets of code, for instance. 26892 * 26893 * @element ANY 26894 * 26895 * @example 26896 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 26897 * but the one wrapped in `ngNonBindable` is left alone. 26898 * 26899 * @example 26900 <example> 26901 <file name="index.html"> 26902 <div>Normal: {{1 + 2}}</div> 26903 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 26904 </file> 26905 <file name="protractor.js" type="protractor"> 26906 it('should check ng-non-bindable', function() { 26907 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 26908 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 26909 }); 26910 </file> 26911 </example> 26912 */ 26913var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 26914 26915/* global jqLiteRemove */ 26916 26917var ngOptionsMinErr = minErr('ngOptions'); 26918 26919/** 26920 * @ngdoc directive 26921 * @name ngOptions 26922 * @restrict A 26923 * 26924 * @description 26925 * 26926 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 26927 * elements for the `<select>` element using the array or object obtained by evaluating the 26928 * `ngOptions` comprehension expression. 26929 * 26930 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 26931 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 26932 * increasing speed by not creating a new scope for each repeated instance, as well as providing 26933 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 26934 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 26935 * to a non-string value. This is because an option element can only be bound to string values at 26936 * present. 26937 * 26938 * When an item in the `<select>` menu is selected, the array element or object property 26939 * represented by the selected option will be bound to the model identified by the `ngModel` 26940 * directive. 26941 * 26942 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 26943 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 26944 * option. See example below for demonstration. 26945 * 26946 * ## Complex Models (objects or collections) 26947 * 26948 * By default, `ngModel` watches the model by reference, not value. This is important to know when 26949 * binding the select to a model that is an object or a collection. 26950 * 26951 * One issue occurs if you want to preselect an option. For example, if you set 26952 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 26953 * because the objects are not identical. So by default, you should always reference the item in y
26953our collection 26954 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 26955 * 26956 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 26957 * of the item not by reference, but by the result of the `track by` expression. For example, if your 26958 * collection items have an id property, you would `track by item.id`. 26959 * 26960 * A different issue with objects or collections is that ngModel won't detect if an object property or 26961 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 26962 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 26963 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 26964 * has not changed identity, but only a property on the object or an item in the collection changes. 26965 * 26966 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 26967 * if the model is an array). This means that changing a property deeper than the first level inside the 26968 * object/collection will not trigger a re-rendering. 26969 * 26970 * ## `select` **`as`** 26971 * 26972 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 26973 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 26974 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 26975 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 26976 * 26977 * 26978 * ### `select` **`as`** and **`track by`** 26979 * 26980 * <div class="alert alert-warning"> 26981 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 26982 * </div> 26983 * 26984 * Given this array of items on the $scope: 26985 * 26986 * ```js 26987 * $scope.items = [{ 26988 * id: 1, 26989 * label: 'aLabel', 26990 * subItem: { name: 'aSubItem' } 26991 * }, { 26992 * id: 2, 26993 * label: 'bLabel', 26994 * subItem: { name: 'bSubItem' } 26995 * }]; 26996 * ``` 26997 * 26998 * This will work: 26999 * 27000 * ```html 27001 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 27002 * ``` 27003 * ```js 27004 * $scope.selected = $scope.items[0]; 27005 * ``` 27006 * 27007 * but this will not work: 27008 * 27009 * ```html 27010 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 27011 * ``` 27012 * ```js 27013 * $scope.selected = $scope.items[0].subItem; 27014 * ``` 27015 * 27016 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 27017 * `items` array. Because the selected option has been set programmatically in the controller, the 27018 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 27019 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 27020 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 27021 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 27022 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 27023 * 27024 * 27025 * @param {string} ngModel Assignable angular expression to data-bind to. 27026 * @param {string=} name Property name of the form under which the control is published. 27027 * @param {string=} required The control is considered valid only if value is entered. 27028 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27029 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27030 * `required` when you want to data-bind to the `required` attribute. 27031 * @param {comprehension_expression=} ngOptions in one of the following forms: 27032 * 27033 * * for array data sources: 27034 * * `label` **`for`** `value` **`in`** `array` 27035 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 27036 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 27037 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 27038 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 27039 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 27040 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 27041 * (for including a filter with `track by`) 27042 * * for object data sources:
27043 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 27044 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 27045 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 27046 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 27047 * * `select` **`as`** `label` **`group by`** `group` 27048 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 27049 * * `select` **`as`** `label` **`disable when`** `disable` 27050 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 27051 * 27052 * Where: 27053 * 27054 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 27055 * * `value`: local variable which will refer to each item in the `array` or each property value 27056 * of `object` during iteration. 27057 * * `key`: local variable which will refer to a property name in `object` during iteration. 27058 * * `label`: The result of this expression will be the label for `<option>` element. The 27059 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 27060 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 27061 * element. If not specified, `select` expression will default to `value`. 27062 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 27063 * DOM element. 27064 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 27065 * element. Return `true` to disable. 27066 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 27067 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 27068 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 27069 * even when the options are recreated (e.g. reloaded from the server). 27070 * 27071 * @example 27072 <example module="selectExample"> 27073 <file name="index.html"> 27074 <script> 27075 angular.module('selectExample', []) 27076 .controller('ExampleController', ['$scope', function($scope) { 27077 $scope.colors = [ 27078 {name:'black', shade:'dark'}, 27079 {name:'white', shade:'light', notAnOption: true}, 27080 {name:'red', shade:'dark'}, 27081 {name:'blue', shade:'dark', notAnOption: true}, 27082 {name:'yellow', shade:'light', notAnOption: false} 27083 ]; 27084 $scope.myColor = $scope.colors[2]; // red 27085 }]); 27086 </script>
27087 <div ng-controller="ExampleController"> 27088 <ul> 27089 <li ng-repeat="color in colors"> 27090 <label>Name: <input ng-model="color.name"></label> 27091 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 27092 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 27093 </li> 27094 <li> 27095 <button ng-click="colors.push({})">add</button> 27096 </li> 27097 </ul> 27098 <hr/> 27099 <label>Color (null not allowed): 27100 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 27101 </label><br/> 27102 <label>Color (null allowed): 27103 <span class="nullable"> 27104 <select ng-model="myColor" ng-options="color.name for color in colors"> 27105 <option value="">-- choose color --</option> 27106 </select> 27107 </span></label><br/> 27108 27109 <label>Color grouped by shade: 27110 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 27111 </select> 27112 </label><br/> 27113 27114 <label>Color grouped by shade, with some disabled: 27115 <select ng-model="myColor" 27116 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 27117 </select> 27118 </label><br/> 27119 27120 27121 27122 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 27123 <br/> 27124 <hr/> 27125 Currently selected: {{ {selected_color:myColor} }} 27126 <div style="border:solid 1px black; height:20px" 27127 ng-style="{'background-color':myColor.name}"> 27128 </div> 27129 </div> 27130 </file> 27131 <file name="protractor.js" type="protractor"> 27132 it('should check ng-options', function() { 27133 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 27134 element.all(by.model('myColor')).first().click(); 27135 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 27136 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 27137 element(by.css('.nullable select[ng-model="myColor"]')).click(); 27138 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 27139 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 27140 }); 27141 </file> 27142 </example> 27143 */ 27144 27145// jshint maxlen: false 27146// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 27147var 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]+?))?$/; 27148 // 1: value expression (valueFn) 27149 // 2: label expression (displayFn) 27150 // 3: group by expression (groupByFn) 27151 // 4: disable when expression (disableWhenFn) 27152 // 5: array item variable name 27153 // 6: object item key variable name 27154 // 7: object item value variable name 27155 // 8: collection expression 27156 // 9: track by expression 27157// jshint maxlen: 100 27158 27159 27160var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 27161 27162 function parseOptionsExpression(optionsExp, selectElement, scope) { 27163 27164 var match = optionsExp.match(NG_OPTIONS_REGEXP); 27165 if (!(match)) { 27166 throw ngOptionsMinErr('iexp', 27167 "Expected expression in form of " + 27168 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 27169 " but got '{0}'. Element: {1}", 27170 optionsExp, startingTag(selectElement)); 27171 } 27172 27173 // Extract the parts from the ngOptions expression 27174 27175 // The variable name for the value of the item in the collection 27176 var valueName = match[5] || match[7]; 27177 // The variable name for the key of the item in the collection 27178 var keyName = match[6]; 27179 27180 // An expression that generates the viewValue for an option if there is a label expression 27181 var selectAs = / as /.test(match[0]) && match[1]; 27182 // An expression that is used to track the id of each object in the options collection 27183 var trackBy = match[9]; 27184 // An expression that generates the viewValue for an option if there is no label expression 27185 var valueFn = $parse(match[2] ? match[1] : valueName); 27186 var selectAsFn = selectAs && $parse(selectAs); 27187 var viewValueFn = selectAsFn || valueFn; 27188 var trackByFn = trackBy && $parse(trackBy); 27189 27190 // Get the value by which we are going to track the option 27191 // if we have a trackFn then use that (passing scope and locals) 27192 // otherwise just hash the given viewValue 27193 var getTrackByValueFn = trackBy ? 27194 function(value, locals) { return trackByFn(scope, locals); } : 27195 function getHashOfValue(value) { return hashKey(value); }; 27196 var getTrackByValue = function(value, key) {
vendor: 5,697 bytes, lines 27197-27350
27197 return getTrackByValueFn(value, getLocals(value, key)); 27198 }; 27199 27200 var displayFn = $parse(match[2] || match[1]); 27201 var groupByFn = $parse(match[3] || ''); 27202 var disableWhenFn = $parse(match[4] || ''); 27203 var valuesFn = $parse(match[8]); 27204 27205 var locals = {}; 27206 var getLocals = keyName ? function(value, key) { 27207 locals[keyName] = key; 27208 locals[valueName] = value; 27209 return locals; 27210 } : function(value) { 27211 locals[valueName] = value; 27212 return locals; 27213 }; 27214 27215 27216 function Option(selectValue, viewValue, label, group, disabled) { 27217 this.selectValue = selectValue; 27218 this.viewValue = viewValue; 27219 this.label = label; 27220 this.group = group; 27221 this.disabled = disabled; 27222 } 27223 27224 function getOptionValuesKeys(optionValues) { 27225 var optionValuesKeys; 27226 27227 if (!keyName && isArrayLike(optionValues)) { 27228 optionValuesKeys = optionValues; 27229 } else { 27230 // if object, extract keys, in enumeration order, unsorted 27231 optionValuesKeys = []; 27232 for (var itemKey in optionValues) { 27233 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 27234 optionValuesKeys.push(itemKey); 27235 } 27236 } 27237 } 27238 return optionValuesKeys; 27239 } 27240 27241 return { 27242 trackBy: trackBy, 27243 getTrackByValue: getTrackByValue, 27244 getWatchables: $parse(valuesFn, function(optionValues) { 27245 // Create a collection of things that we would like to watch (watchedArray) 27246 // so that they can all be watched using a single $watchCollection 27247 // that only runs the handler once if anything changes 27248 var watchedArray = []; 27249 optionValues = optionValues || []; 27250 27251 var optionValuesKeys = getOptionValuesKeys(optionValues); 27252 var optionValuesLength = optionValuesKeys.length; 27253 for (var index = 0; index < optionValuesLength; index++) { 27254 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 27255 var value = optionValues[key]; 27256 27257 var locals = getLocals(optionValues[key], key); 27258 var selectValue = getTrackByValueFn(optionValues[key], locals); 27259 watchedArray.push(selectValue); 27260 27261 // Only need to watch the displayFn if there is a specific label expression 27262 if (match[2] || match[1]) { 27263 var label = displayFn(scope, locals); 27264 watchedArray.push(label); 27265 } 27266 27267 // Only need to watch the disableWhenFn if there is a specific disable expression 27268 if (match[4]) { 27269 var disableWhen = disableWhenFn(scope, locals); 27270 watchedArray.push(disableWhen); 27271 } 27272 } 27273 return watchedArray; 27274 }), 27275 27276 getOptions: function() { 27277 27278 var optionItems = []; 27279 var selectValueMap = {}; 27280 27281 // The option values were already computed in the `getWatchables` fn, 27282 // which must have been called to trigger `getOptions` 27283 var optionValues = valuesFn(scope) || []; 27284 var optionValuesKeys = getOptionValuesKeys(optionValues); 27285 var optionValuesLength = optionValuesKeys.length; 27286 27287 for (var index = 0; index < optionValuesLength; index++) { 27288 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 27289 var value = optionValues[key]; 27290 var locals = getLocals(value, key); 27291 var viewValue = viewValueFn(scope, locals); 27292 var selectValue = getTrackByValueFn(viewValue, locals); 27293 var label = displayFn(scope, locals); 27294 var group = groupByFn(scope, locals); 27295 var disabled = disableWhenFn(scope, locals); 27296 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 27297 27298 optionItems.push(optionItem); 27299 selectValueMap[selectValue] = optionItem; 27300 } 27301 27302 return { 27303 items: optionItems, 27304 selectValueMap: selectValueMap, 27305 getOptionFromViewValue: function(value) { 27306 return selectValueMap[getTrackByValue(value)]; 27307 }, 27308 getViewValueFromOption: function(option) { 27309 // If the viewValue could be an object that may be mutated by the application, 27310 // we need to make a copy and not return the reference to the value on the option. 27311 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 27312 } 27313 }; 27314 } 27315 }; 27316 } 27317 27318 27319 // we can't just jqLite('<option>') since jqLite is not smart enough 27320 // to create it in <select> and IE barfs otherwise. 27321 var optionTemplate = document.createElement('option'), 27322 optGroupTemplate = document.createElement('optgroup'); 27323 27324 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 27325 27326 var selectCtrl = ctrls[0]; 27327 var ngModelCtrl = ctrls[1]; 27328 var multiple = attr.multiple; 27329 27330 // The emptyOption allows the application developer to provide their own custom "empty" 27331 // option when the viewValue does not match any of the option values. 27332 var emptyOption; 27333 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 27334 if (children[i].value === '') { 27335 emptyOption = children.eq(i); 27336 break; 27337 } 27338 } 27339 27340 var providedEmptyOption = !!emptyOption; 27341 27342 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 27343 unknownOption.val('?'); 27344 27345 var options; 27346 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 27347 27348 27349 var renderEmptyOption = function() { 27350 if (!providedEmptyOption) {
27351 selectElement.prepend(emptyOption); 27352 } 27353 selectElement.val(''); 27354 emptyOption.prop('selected', true); // needed for IE 27355 emptyOption.attr('selected', true); 27356 }; 27357 27358 var removeEmptyOption = function() { 27359 if (!providedEmptyOption) { 27360 emptyOption.remove(); 27361 } 27362 }; 27363 27364 27365 var renderUnknownOption = function() { 27366 selectElement.prepend(unknownOption); 27367 selectElement.val('?'); 27368 unknownOption.prop('selected', true); // needed for IE 27369 unknownOption.attr('selected', true); 27370 }; 27371 27372 var removeUnknownOption = function() { 27373 unknownOption.remove(); 27374 }; 27375 27376 // Update the controller methods for multiple selectable options 27377 if (!multiple) { 27378 27379 selectCtrl.writeValue = function writeNgOptionsValue(value) { 27380 var option = options.getOptionFromViewValue(value); 27381 27382 if (option && !option.disabled) { 27383 if (selectElement[0].value !== option.selectValue) { 27384 removeUnknownOption(); 27385 removeEmptyOption(); 27386 27387 selectElement[0].value = option.selectValue; 27388 option.element.selected = true; 27389 option.element.setAttribute('selected', 'selected'); 27390 } 27391 } else { 27392 if (value === null || providedEmptyOption) { 27393 removeUnknownOption(); 27394 renderEmptyOption(); 27395 } else { 27396 removeEmptyOption(); 27397 renderUnknownOption(); 27398 } 27399 } 27400 }; 27401 27402 selectCtrl.readValue = function readNgOptionsValue() { 27403 27404 var selectedOption = options.selectValueMap[selectElement.val()]; 27405 27406 if (selectedOption && !selectedOption.disabled) { 27407 removeEmptyOption(); 27408 removeUnknownOption(); 27409 return options.getViewValueFromOption(selectedOption); 27410 } 27411 return null; 27412 }; 27413 27414 // If we are using `track by` then we must watch the tracked value on the model 27415 // since ngModel only watches for object identity change 27416 if (ngOptions.trackBy) { 27417 scope.$watch( 27418 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 27419 function() { ngModelCtrl.$render(); } 27420 ); 27421 } 27422 27423 } else { 27424 27425 ngModelCtrl.$isEmpty = function(value) { 27426 return !value || value.length === 0; 27427 }; 27428 27429 27430 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 27431 options.items.forEach(function(option) { 27432 option.element.selected = false; 27433 }); 27434 27435 if (value) { 27436 value.forEach(function(item) { 27437 var option = options.getOptionFromViewValue(item); 27438 if (option && !option.disabled) option.element.selected = true; 27439 }); 27440 } 27441 }; 27442 27443 27444 selectCtrl.readValue = function readNgOptionsMultiple() { 27445 var selectedValues = selectElement.val() || [], 27446 selections = []; 27447 27448 forEach(selectedValues, function(value) { 27449 var option = options.selectValueMap[value]; 27450 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 27451 }); 27452 27453 return selections; 27454 }; 27455 27456 // If we are using `track by` then we must watch these tracked values on the model 27457 // since ngModel only watches for object identity change 27458 if (ngOptions.trackBy) { 27459 27460 scope.$watchCollection(function() { 27461 if (isArray(ngModelCtrl.$viewValue)) { 27462 return ngModelCtrl.$viewValue.map(function(value) { 27463 return ngOptions.getTrackByValue(value); 27464 }); 27465 } 27466 }, function() { 27467 ngModelCtrl.$render(); 27468 }); 27469 27470 } 27471 } 27472 27473 27474 if (providedEmptyOption) { 27475 27476 // we need to remove it before calling selectElement.empty() because otherwise IE will 27477 // remove the label from the element. wtf? 27478 emptyOption.remove(); 27479 27480 // compile the element since there might be bindings in it 27481 $compile(emptyOption)(scope); 27482 27483 // remove the class, which is added automatically because we recompile the element and it 27484 // becomes the compilation root 27485 emptyOption.removeClass('ng-scope'); 27486 } else { 27487 emptyOption = jqLite(optionTemplate.cloneNode(false)); 27488 } 27489 27490 // We need to do this here to ensure that the options object is defined 27491 // when we first hit it in writeNgOptionsValue 27492 updateOptions(); 27493 27494 // We will re-render the option elements if the option values or labels change 27495 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 27496 27497 // ------------------------------------------------------------------ // 27498 27499 27500 function updateOptionElement(option, element) { 27501 option.element = element; 27502 element.disabled = option.disabled; 27503 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 27504 // selects in certain circumstances when multiple selects are next to each other and display 27505 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 27506 // See https://github.com/angular/angular.js/issues/11314 for more info. 27507 // This is unfortunately untestable with unit / e2e tests 27508 if (option.label !== element.label) { 27509 element.label = option.label; 27510 element.textContent = option.label; 27511 } 27512 if (option.value !== element.value) element.value = option.selectValue; 27513 } 27514 27515 function addOrReuseElement(parent, current, type, templateElement) { 27516 var element; 27517 // Check whether we can reuse the next element 27518 if (current && lowercase(current.nodeName) === type) { 27519 // The next element is the right type so reuse it 27520 element = current; 27521 } else { 27522 // The next element is not the right type so create a new one 27523 element = templateElement.cloneNode(false); 27524 if (!current) { 27525 // There are no more elements so just append it to the select 27526 parent.appendChild(element); 27527 } else { 27528 // The next element is not a group so insert the new one 27529 parent.insertBefore(element, current); 27530 } 27531 } 27532 return element; 27533 } 27534 27535 27536 function removeExcessElements(current) { 27537 var next; 27538 while (current) { 27539 next = current.nextSibling; 27540 jqLiteRemove(current); 27541 current = next; 27542 } 27543 } 27544 27545 27546 function skipEmptyAndUnknownOptions(current) { 27547 var emptyOption_ = emptyOption && emptyOption[0]; 27548 var unknownOption_ = unknownOption && unknownOption[0]; 27549 27550 // We cannot rely on the extracted empty option being the same as the compiled empty option, 27551 // because the compiled empty option might have been replaced by a comment because 27552 // it had an "element" transclusion directive on it (such as ngIf) 27553 if (emptyOption_ || unknownOption_) { 27554 while (current && 27555 (current === emptyOption_ || 27556 current === unknownOption_ || 27557 current.nodeType === NODE_TYPE_COMMENT || 27558 (nodeName_(current) === 'option' && current.value === ''))) { 27559 current = current.nextSibling; 27560 } 27561 } 27562 return current; 27563 } 27564 27565 27566 function updateOptions() { 27567 27568 var previousValue = options && selectCtrl.readValue(); 27569 27570 options = ngOptions.getOptions(); 27571 27572 var groupMap = {}; 27573 var currentElement = selectElement[0].firstChild; 27574 27575 // Ensure that the empty option is always there if it was explicitly provided 27576 if (providedEmptyOption) {
27577 selectElement.prepend(emptyOption); 27578 } 27579 27580 currentElement = skipEmptyAndUnknownOptions(currentElement); 27581 27582 options.items.forEach(function updateOption(option) { 27583 var group; 27584 var groupElement; 27585 var optionElement; 27586 27587 if (isDefined(option.group)) { 27588 27589 // This option is to live in a group 27590 // See if we have already created this group 27591 group = groupMap[option.group]; 27592 27593 if (!group) { 27594 27595 // We have not already created this group 27596 groupElement = addOrReuseElement(selectElement[0], 27597 currentElement, 27598 'optgroup', 27599 optGroupTemplate); 27600 // Move to the next element 27601 currentElement = groupElement.nextSibling; 27602 27603 // Update the label on the group element 27604 groupElement.label = option.group; 27605 27606 // Store it for use later 27607 group = groupMap[option.group] = { 27608 groupElement: groupElement, 27609 currentOptionElement: groupElement.firstChild 27610 }; 27611 27612 } 27613 27614 // So now we have a group for this option we add the option to the group 27615 optionElement = addOrReuseElement(group.groupElement, 27616 group.currentOptionElement, 27617 'option', 27618 optionTemplate); 27619 updateOptionElement(option, optionElement); 27620 // Move to the next element 27621 group.currentOptionElement = optionElement.nextSibling; 27622 27623 } else { 27624 27625 // This option is not in a group 27626 optionElement = addOrReuseElement(selectElement[0], 27627 currentElement, 27628 'option', 27629 optionTemplate); 27630 updateOptionElement(option, optionElement); 27631 // Move to the next element 27632 currentElement = optionElement.nextSibling; 27633 } 27634 }); 27635 27636 27637 // Now remove all excess options and group 27638 Object.keys(groupMap).forEach(function(key) { 27639 removeExcessElements(groupMap[key].currentOptionElement); 27640 }); 27641 removeExcessElements(currentElement); 27642 27643 ngModelCtrl.$render(); 27644 27645 // Check to see if the value has changed due to the update to the options 27646 if (!ngModelCtrl.$isEmpty(previousValue)) { 27647 var nextValue = selectCtrl.readValue(); 27648 var isNotPrimitive = ngOptions.trackBy || multiple; 27649 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 27650 ngModelCtrl.$setViewValue(nextValue); 27651 ngModelCtrl.$render(); 27652 } 27653 } 27654 27655 } 27656 } 27657 27658 return { 27659 restrict: 'A', 27660 terminal: true, 27661 require: ['select', 'ngModel'], 27662 link: { 27663 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 27664 // Deactivate the SelectController.register method to prevent 27665 // option directives from accidentally registering themselves 27666 // (and unwanted $destroy handlers etc.) 27667 ctrls[0].registerOption = noop; 27668 }, 27669 post: ngOptionsPostLink 27670 } 27671 }; 27672}]; 27673 27674/** 27675 * @ngdoc directive 27676 * @name ngPluralize 27677 * @restrict EA 27678 * 27679 * @description 27680 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 27681 * These rules are bundled with angular.js, but can be overridden 27682 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 27683 * by specifying the mappings between 27684 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 27685 * and the strings to be displayed. 27686 * 27687 * # Plural categories and explicit number rules 27688 * There are two 27689 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 27690 * in Angular's default en-US locale: "one" and "other". 27691 * 27692 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 27693 * any number that is not 1), an explicit number rule can only match one number. For example, the 27694 * explicit number rule for "3" matches the number 3. There are examples of plural categories 27695 * and explicit number rules throughout the rest of this documentation. 27696 * 27697 * # Configuring ngPluralize 27698 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 27699 * You can also provide an optional attribute, `offset`. 27700 * 27701 * The value of the `count` attribute can be either a string or an {@link guide/expression 27702 * Angular expression}; these are evaluated on the current scope for its bound value. 27703 * 27704 * The `when` attribute specifies the mappings between plural categories and the actual 27705 * string to be displayed. The value of the attribute should be a JSON object. 27706 * 27707 * The following example shows how to configure ngPluralize: 27708 * 27709 * ```html 27710 * <ng-pluralize count="personCount"
27711 when="{'0': 'Nobody is viewing.', 27712 * 'one': '1 person is viewing.', 27713 * 'other': '{} people are viewing.'}"> 27714 * </ng-pluralize> 27715 *``` 27716 * 27717 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 27718 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 27719 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 27720 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 27721 * show "a dozen people are viewing". 27722 * 27723 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 27724 * into pluralized strings. In the previous example, Angular will replace `{}` with 27725 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 27726 * for <span ng-non-bindable>{{numberExpression}}</span>. 27727 * 27728 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 27729 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 27730 * 27731 * # Configuring ngPluralize with offset 27732 * The `offset` attribute allows further customization of pluralized text, which can result in 27733 * a better user experience. For example, instead of the message "4 people are viewing this document", 27734 * you might display "John, Kate and 2 others are viewing this document". 27735 * The offset attribute allows you to offset a number by any desired value. 27736 * Let's take a look at an example: 27737 * 27738 * ```html 27739 * <ng-pluralize count="personCount" offset=2 27740 * when="{'0': 'Nobody is viewing.', 27741 * '1': '{{person1}} is viewing.', 27742 * '2': '{{person1}} and {{person2}} are viewing.', 27743 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 27744 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 27745 * </ng-pluralize> 27746 * ``` 27747 * 27748 * Notice that we are still using two plural categories(one, other), but we added 27749 * three explicit number rules 0, 1 and 2. 27750 * When one person, perhaps John, views the document, "John is viewing" will be shown. 27751 * When three people view the document, no explicit number rule is found, so 27752 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 27753 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 27754 * is shown. 27755 * 27756 * Note that when you specify offsets, you must provide explicit number rules for 27757 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 27758 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 27759 * plural categories "one" and "other". 27760 * 27761 * @param {string|expression} count The variable to be bound to. 27762 * @param {string} when The mapping between plural category to its corresponding strings. 27763 * @param {number=} offset Offset to deduct from the total number. 27764 * 27765 * @example 27766 <example module="pluralizeExample"> 27767 <file name="index.html"> 27768 <script> 27769 angular.module('pluralizeExample', []) 27770 .controller('ExampleController', ['$scope', function($scope) { 27771 $scope.person1 = 'Igor'; 27772 $scope.person2 = 'Misko'; 27773 $scope.personCount = 1; 27774 }]); 27775 </script>
27776 <div ng-controller="ExampleController"> 27777 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 27778 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 27779 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 27780 27781 <!--- Example with simple pluralization rules for en locale ---> 27782 Without Offset: 27783 <ng-pluralize count="personCount" 27784 when="{'0': 'Nobody is viewing.', 27785 'one': '1 person is viewing.', 27786 'other': '{} people are viewing.'}"> 27787 </ng-pluralize><br> 27788 27789 <!--- Example with offset ---> 27790 With Offset(2): 27791 <ng-pluralize count="personCount" offset=2 27792 when="{'0': 'Nobody is viewing.', 27793 '1': '{{person1}} is viewing.', 27794 '2': '{{person1}} and {{person2}} are viewing.', 27795 'one': '{{person1}}, {{person2}} and one other person are viewing.', 27796 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 27797 </ng-pluralize> 27798 </div> 27799 </file> 27800 <file name="protractor.js" type="protractor"> 27801 it('should show correct pluralized string', function() { 27802 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 27803 var withOffset = element.all(by.css('ng-pluralize')).get(1); 27804 var countInput = element(by.model('personCount')); 27805 27806 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 27807 expect(withOffset.getText()).toEqual('Igor is viewing.'); 27808 27809 countInput.clear(); 27810 countInput.sendKeys('0'); 27811 27812 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 27813 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 27814 27815 countInput.clear(); 27816 countInput.sendKeys('2'); 27817 27818 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 27819 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 27820 27821 countInput.clear(); 27822 countInput.sendKeys('3'); 27823 27824 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 27825 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 27826 27827 countInput.clear(); 27828 countInput.sendKeys('4'); 27829 27830 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 27831 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 27832 }); 27833 it('should show data-bound names', function() { 27834 var withOffset = element.all(by.css('ng-pluralize')).get(1); 27835 var personCount = element(by.model('personCount')); 27836 var person1 = element(by.model('person1')); 27837 var person2 = element(by.model('person2')); 27838 personCount.clear(); 27839 personCount.sendKeys('4'); 27840 person1.clear(); 27841 person1.sendKeys('Di'); 27842 person2.clear(); 27843 person2.sendKeys('Vojta'); 27844 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 27845 }); 27846 </file> 27847 </example> 27848 */ 27849var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 27850 var BRACE = /{}/g, 27851 IS_WHEN = /^when(Minus)?(.+)$/; 27852 27853 return { 27854 link: function(scope, element, attr) { 27855 var numberExp = attr.count, 27856 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 27857 offset = attr.offset || 0, 27858 whens = scope.$eval(whenExp) || {}, 27859 whensExpFns = {}, 27860 startSymbol = $interpolate.startSymbol(), 27861 endSymbol = $interpolate.endSymbol(), 27862 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 27863 watchRemover = angular.noop, 27864 lastCount; 27865
vendor: 12,352 bytes, lines 27866-28129
27866 forEach(attr, function(expression, attributeName) { 27867 var tmpMatch = IS_WHEN.exec(attributeName); 27868 if (tmpMatch) { 27869 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 27870 whens[whenKey] = element.attr(attr.$attr[attributeName]); 27871 } 27872 }); 27873 forEach(whens, function(expression, key) { 27874 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 27875 27876 }); 27877 27878 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 27879 var count = parseFloat(newVal); 27880 var countIsNaN = isNaN(count); 27881 27882 if (!countIsNaN && !(count in whens)) { 27883 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 27884 // Otherwise, check it against pluralization rules in $locale service. 27885 count = $locale.pluralCat(count - offset); 27886 } 27887 27888 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 27889 // In JS `NaN !== NaN`, so we have to explicitly check. 27890 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 27891 watchRemover(); 27892 var whenExpFn = whensExpFns[count]; 27893 if (isUndefined(whenExpFn)) { 27894 if (newVal != null) { 27895 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 27896 } 27897 watchRemover = noop; 27898 updateElementText(); 27899 } else { 27900 watchRemover = scope.$watch(whenExpFn, updateElementText); 27901 } 27902 lastCount = count; 27903 } 27904 }); 27905 27906 function updateElementText(newText) { 27907 element.text(newText || ''); 27908 } 27909 } 27910 }; 27911}]; 27912 27913/** 27914 * @ngdoc directive 27915 * @name ngRepeat 27916 * @multiElement 27917 * 27918 * @description 27919 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 27920 * instance gets its own scope, where the given loop variable is set to the current collection item, 27921 * and `$index` is set to the item index or key. 27922 * 27923 * Special properties are exposed on the local scope of each template instance, including: 27924 * 27925 * | Variable | Type | Details | 27926 * |-----------|-----------------|-----------------------------------------------------------------------------| 27927 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 27928 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 27929 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 27930 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 27931 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 27932 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 27933 * 27934 * <div class="alert alert-info"> 27935 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 27936 * This may be useful when, for instance, nesting ngRepeats. 27937 * </div> 27938 * 27939 * 27940 * # Iterating over object properties 27941 * 27942 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 27943 * syntax: 27944 * 27945 * ```js 27946 * <div ng-repeat="(key, value) in myObj"> ... </div> 27947 * ``` 27948 * 27949 * You need to be aware that the JavaScript specification does not define the order of keys 27950 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive 27951 * used to sort the keys alphabetically.) 27952 * 27953 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser 27954 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing 27955 * keys in the order in which they were defined, although there are exceptions when keys are deleted 27956 * and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 27957 * 27958 * If this is not desired, the recommended workaround is to convert your object into an array 27959 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 27960 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 27961 * or implement a `$watch` on the object yourself. 27962 * 27963 * 27964 * # Tracking and Duplicates 27965 * 27966 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 27967 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM: 27968 * 27969 * * When an item is added, a new instance of the template is added to the DOM. 27970 * * When an item is removed, its template instance is removed from the DOM. 27971 * * When items are reordered, their respective templates are reordered in the DOM. 27972 * 27973 * To minimize creation of DOM elements, `ngRepeat` uses a function 27974 * to "keep track" of all items in the collection and their corresponding DOM elements. 27975 * For example, if an item is added to the collection, ngRepeat will know that all other items 27976 * already have DOM elements, and will not re-render them. 27977 * 27978 * The default tracking function (which tracks items by their identity) does not allow 27979 * duplicate items in arrays. This is because when there are duplicates, it is not possible 27980 * to maintain a one-to-one mapping between collection items and DOM elements. 27981 * 27982 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 27983 * with your own using the `track by` expression. 27984 * 27985 * For example, you may track items by the index of each item in the collection, using the 27986 * special scope property `$index`: 27987 * ```html 27988 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 27989 * {{n}} 27990 * </div> 27991 * ``` 27992 * 27993 * You may also use arbitrary expressions in `track by`, including references to custom functions 27994 * on the scope: 27995 * ```html 27996 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 27997 * {{n}} 27998 * </div> 27999 * ``` 28000 * 28001 * <div class="alert alert-success"> 28002 * If you are working with objects that have an identifier property, you should track 28003 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 28004 * will not have to rebuild the DOM elements for items it has already rendered, even if the 28005 * JavaScript objects in the collection have been substituted for new ones. For large collections, 28006 * this significantly improves rendering performance. If you don't have a unique identifier, 28007 * `track by $index` can also provide a performance boost. 28008 * </div> 28009 * ```html 28010 * <div ng-repeat="model in collection track by model.id"> 28011 * {{model.name}} 28012 * </div> 28013 * ``` 28014 * 28015 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 28016 * `$id` function, which tracks items by their identity: 28017 * ```html 28018 * <div ng-repeat="obj in collection track by $id(obj)"> 28019 * {{obj.prop}} 28020 * </div> 28021 * ``` 28022 * 28023 * <div class="alert alert-warning"> 28024 * **Note:** `track by` must always be the last expression: 28025 * </div> 28026 * ``` 28027 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 28028 * {{model.name}} 28029 * </div> 28030 * ``` 28031 * 28032 * # Special repeat start and end points 28033 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 28034 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 28035 * 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) 28036 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 28037 * 28038 * The example below makes use of this feature: 28039 * ```html 28040 * <header ng-repeat-start="item in items"> 28041 * Header {{ item }} 28042 * </header> 28043 * <div class="body"> 28044 * Body {{ item }} 28045 * </div> 28046 * <footer ng-repeat-end> 28047 * Footer {{ item }} 28048 * </footer> 28049 * ``` 28050 * 28051 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 28052 * ```html 28053 * <header> 28054 * Header A 28055 * </header> 28056 * <div class="body"> 28057 * Body A 28058 * </div> 28059 * <footer> 28060 * Footer A 28061 * </footer> 28062 * <header> 28063 * Header B 28064 * </header> 28065 * <div class="body"> 28066 * Body B 28067 * </div> 28068 * <footer> 28069 * Footer B 28070 * </footer> 28071 * ``` 28072 * 28073 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 28074 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 28075 * 28076 * @animations 28077 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 28078 * 28079 * **.leave** - when an item is removed from the list or when an item is filtered out 28080 * 28081 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 28082 * 28083 * @element ANY 28084 * @scope 28085 * @priority 1000 28086 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 28087 * formats are currently supported: 28088 * 28089 * * `variable in expression` â where variable is the user defined loop variable and `expression` 28090 * is a scope expression giving the collection to enumerate. 28091 * 28092 * For example: `album in artist.albums`. 28093 * 28094 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 28095 * and `expression` is the scope expression giving the collection to enumerate. 28096 * 28097 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 28098 * 28099 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 28100 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 28101 * is specified, ng-repeat associates elements by identity. It is an error to have 28102 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 28103 * mapped to the same DOM element, which is not possible.) 28104 * 28105 * Note that the tracking expression must come last, after any filters, and the alias expression. 28106 * 28107 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 28108 * will be associated by item identity in the array. 28109 * 28110 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 28111 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 28112 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 28113 * element in the same way in the DOM. 28114 * 28115 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 28116 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 28117 * property is same. 28118 * 28119 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 28120 * to items in conjunction with a tracking expression. 28121 * 28122 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 28123 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 28124 * when a filter is active on the repeater, but the filtered result set is empty. 28125 * 28126 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 28127 * the items have been processed through the filter. 28128 * 28129 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax s
28129o it can be used only at the end 28130 * (and not as operator, inside an expression). 28131 * 28132 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 28133 * 28134 * @example 28135 * This example initializes the scope to a list of names and 28136 * then uses `ngRepeat` to display every person: 28137 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 28138 <file name="index.html"> 28139 <div ng-init="friends = [ 28140 {name:'John', age:25, gender:'boy'}, 28141 {name:'Jessie', age:30, gender:'girl'}, 28142 {name:'Johanna', age:28, gender:'girl'}, 28143 {name:'Joy', age:15, gender:'girl'}, 28144 {name:'Mary', age:28, gender:'girl'}, 28145 {name:'Peter', age:95, gender:'boy'}, 28146 {name:'Sebastian', age:50, gender:'boy'}, 28147 {name:'Erika', age:27, gender:'girl'}, 28148 {name:'Patrick', age:40, gender:'boy'}, 28149 {name:'Samantha', age:60, gender:'girl'} 28150 ]"> 28151 I have {{friends.length}} friends. They are: 28152 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 28153 <ul class="example-animate-container"> 28154 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 28155 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 28156 </li> 28157 <li class="animate-repeat" ng-if="results.length == 0"> 28158 <strong>No results found...</strong> 28159 </li> 28160 </ul> 28161 </div> 28162 </file> 28163 <file name="animations.css"> 28164 .example-animate-container { 28165 background:white; 28166 border:1px solid black; 28167 list-style:none; 28168 margin:0; 28169 padding:0 10px; 28170 } 28171 28172 .animate-repeat { 28173 line-height:40px; 28174 list-style:none; 28175 box-sizing:border-box; 28176 } 28177 28178 .animate-repeat.ng-move, 28179 .animate-repeat.ng-enter, 28180 .animate-repeat.ng-leave { 28181 transition:all linear 0.5s; 28182 } 28183 28184 .animate-repeat.ng-leave.ng-leave-active, 28185 .animate-repeat.ng-move, 28186 .animate-repeat.ng-enter { 28187 opacity:0; 28188 max-height:0; 28189 } 28190 28191 .animate-repeat.ng-leave, 28192 .animate-repeat.ng-move.ng-move-active, 28193 .animate-repeat.ng-enter.ng-enter-active { 28194 opacity:1; 28195 max-height:40px; 28196 } 28197 </file> 28198 <file name="protractor.js" type="protractor"> 28199 var friends = element.all(by.repeater('friend in friends')); 28200 28201 it('should render initial data set', function() { 28202 expect(friends.count()).toBe(10); 28203 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 28204 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 28205 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 28206 expect(element(by.binding('friends.length')).getText()) 28207 .toMatch("I have 10 friends. They are:"); 28208 }); 28209 28210 it('should update repeater when filter predicate changes', function() { 28211 expect(friends.count()).toBe(10); 28212 28213 element(by.model('q')).sendKeys('ma'); 28214 28215 expect(friends.count()).toBe(2); 28216 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 28217 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 28218 }); 28219 </file> 28220 </example> 28221 */ 28222var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 28223 var NG_REMOVED = '$$NG_REMOVED'; 28224 var ngRepeatMinErr = minErr('ngRepeat'); 28225 28226 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 28227 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 28228 scope[valueIdentifier] = value; 28229 if (keyIdentifier) scope[keyIdentifier] = key; 28230 scope.$index = index; 28231 scope.$first = (index === 0); 28232 scope.$last = (index === (arrayLength - 1)); 28233 scope.$middle = !(scope.$first || scope.$last); 28234 // jshint bitwise: false 28235 scope.$odd = !(scope.$even = (index&1) === 0); 28236 // jshint bitwise: true 28237 }; 28238 28239 var getBlockStart = function(block) { 28240 return block.clone[0]; 28241 }; 28242 28243 var getBlockEnd = function(block) { 28244 return block.clone[block.clone.length - 1]; 28245 }; 28246 28247 28248 return { 28249 restrict: 'A', 28250 multiElement: true, 28251 transclude: 'element', 28252 priority: 1000, 28253 terminal: true, 28254 $$tlb: true, 28255 compile: function ngRepeatCompile($element, $attr) { 28256 var expression = $attr.ngRepeat; 28257 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 28258 28259 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*$/); 28260 28261 if (!match) { 28262 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 28263 expression); 28264 } 28265 28266 var lhs = match[1]; 28267 var rhs = match[2]; 28268 var aliasAs = match[3]; 28269 var trackByExp = match[4]; 28270 28271 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 28272 28273 if (!match) { 28274 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 28275 lhs); 28276 } 28277 var valueIdentifier = match[3] || match[1]; 28278 var keyIdentifier = match[2]; 28279 28280 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 28281 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 28282 throw ngRepeatMinErr('badident', "alias '{0}
vendor: 12,206 bytes, lines 28282-28554
28282' is invalid --- must be a valid JS identifier which is not a reserved name.", 28283 aliasAs); 28284 } 28285 28286 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 28287 var hashFnLocals = {$id: hashKey}; 28288 28289 if (trackByExp) { 28290 trackByExpGetter = $parse(trackByExp); 28291 } else { 28292 trackByIdArrayFn = function(key, value) { 28293 return hashKey(value); 28294 }; 28295 trackByIdObjFn = function(key) { 28296 return key; 28297 }; 28298 } 28299 28300 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 28301 28302 if (trackByExpGetter) { 28303 trackByIdExpFn = function(key, value, index) { 28304 // assign key, value, and $index to the locals so that they can be used in hash functions 28305 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 28306 hashFnLocals[valueIdentifier] = value; 28307 hashFnLocals.$index = index; 28308 return trackByExpGetter($scope, hashFnLocals); 28309 }; 28310 } 28311 28312 // Store a list of elements from previous run. This is a hash where key is the item from the 28313 // iterator, and the value is objects with following properties. 28314 // - scope: bound scope 28315 // - element: previous element. 28316 // - index: position 28317 // 28318 // We are using no-proto object so that we don't need to guard against inherited props via 28319 // hasOwnProperty. 28320 var lastBlockMap = createMap(); 28321 28322 //watch props 28323 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 28324 var index, length, 28325 previousNode = $element[0], // node that cloned nodes should be inserted after 28326 // initialized to the comment node anchor 28327 nextNode, 28328 // Same as lastBlockMap but it has the current state. It will become the 28329 // lastBlockMap on the next iteration. 28330 nextBlockMap = createMap(), 28331 collectionLength, 28332 key, value, // key/value of iteration 28333 trackById, 28334 trackByIdFn, 28335 collectionKeys, 28336 block, // last object information {scope, element, id} 28337 nextBlockOrder, 28338 elementsToRemove; 28339 28340 if (aliasAs) { 28341 $scope[aliasAs] = collection; 28342 } 28343 28344 if (isArrayLike(collection)) { 28345 collectionKeys = collection; 28346 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 28347 } else { 28348 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 28349 // if object, extract keys, in enumeration order, unsorted 28350 collectionKeys = []; 28351 for (var itemKey in collection) { 28352 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 28353 collectionKeys.push(itemKey); 28354 } 28355 } 28356 } 28357 28358 collectionLength = collectionKeys.length; 28359 nextBlockOrder = new Array(collectionLength); 28360 28361 // locate existing items 28362 for (index = 0; index < collectionLength; index++) { 28363 key = (collection === collectionKeys) ? index : collectionKeys[index]; 28364 value = collection[key]; 28365 trackById = trackByIdFn(key, value, index); 28366 if (lastBlockMap[trackById]) { 28367 // found previously seen block 28368 block = lastBlockMap[trackById]; 28369 delete lastBlockMap[trackById]; 28370 nextBlockMap[trackById] = block; 28371 nextBlockOrder[index] = block; 28372 } else if (nextBlockMap[trackById]) { 28373 // if collision detected. restore lastBlockMap and throw an error 28374 forEach(nextBlockOrder, function(block) { 28375 if (block && block.scope) lastBlockMap[block.id] = block; 28376 }); 28377 throw ngRepeatMinErr('dupes', 28378 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 28379 expression, trackById, value); 28380 } else { 28381 // new never before seen block 28382 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 28383 nextBlockMap[trackById] = true; 28384 } 28385 } 28386 28387 // remove leftover items 28388 for (var blockKey in lastBlockMap) { 28389 block = lastBlockMap[blockKey]; 28390 elementsToRemove = getBlockNodes(block.clone); 28391 $animate.leave(elementsToRemove); 28392 if (elementsToRemove[0].parentNode) { 28393 // if the element was not removed yet because of pending animation, mark it as deleted 28394 // so that we can ignore it later 28395 for (index = 0, length = elementsToRemove.length; index < length; index++) { 28396 elementsToRemove[index][NG_REMOVED] = true; 28397 } 28398 } 28399 block.scope.$destroy(); 28400 } 28401 28402 // we are not using forEach for perf reasons (trying to avoid #call) 28403 for (index = 0; index < collectionLength; index++) { 28404 key = (collection === collectionKeys) ? index : collectionKeys[index]; 28405 value = collection[key]; 28406 block = nextBlockOrder[index]; 28407 28408 if (block.scope) { 28409 // if we have already seen this object, then we need to reuse the 28410 // associated scope/element 28411 28412 nextNode = previousNode; 28413 28414 // skip nodes that are already pending removal via leave animation 28415 do { 28416 nextNode = nextNode.nextSibling; 28417 } while (nextNode && nextNode[NG_REMOVED]); 28418 28419 if (getBlockStart(block) != nextNode) { 28420 // existing item which got moved 28421 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 28422 } 28423 previousNode = getBlockEnd(block); 28424 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 28425 } else { 28426 // new item which we don't know about 28427 $transclude(function ngRepeatTransclude(clone, scope) { 28428 block.scope = scope; 28429 // http://jsperf.com/clone-vs-createcomment 28430 var endNode = ngRepeatEndComment.cloneNode(false); 28431 clone[clone.length++] = endNode; 28432 28433 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 28434 $animate.enter(clone, null, jqLite(previousNode)); 28435 previousNode = endNode; 28436 // Note: We only need the first/last node of the cloned nodes. 28437 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 28438 // by a directive with templateUrl when its template arrives. 28439 block.clone = clone; 28440 nextBlockMap[block.id] = block; 28441 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 28442 }); 28443 } 28444 } 28445 lastBlockMap = nextBlockMap; 28446 }); 28447 }; 28448 } 28449 }; 28450}]; 28451 28452var NG_HIDE_CLASS = 'ng-hide'; 28453var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 28454/** 28455 * @ngdoc directive 28456 * @name ngShow 28457 * @multiElement 28458 * 28459 * @description 28460 * The `ngShow` directive shows or hides the given HTML element based on the expression 28461 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 28462 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 28463 * in AngularJS and sets the display style to none (using an !important flag). 28464 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 28465 * 28466 * ```html 28467 * <!-- when $scope.myValue is truthy (element is visible) --> 28468 * <div ng-show="myValue"></div> 28469 * 28470 * <!-- when $scope.myValue is falsy (element is hidden) --> 28471 * <div ng-show="myValue" class="ng-hide"></div> 28472 * ``` 28473 * 28474 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 28475 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 28476 * from the element causing the element not to appear hidden. 28477 * 28478 * ## Why is !important used? 28479 * 28480 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 28481 * can be easily overridden by heavier selectors. For example, something as simple 28482 * as changing the display style on a HTML list item would make hidden elements appear visible. 28483 * This also becomes a bigger issue when dealing with CSS frameworks. 28484 * 28485 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 28486 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 28487 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 28488 * 28489 * ### Overriding `.ng-hide` 28490 * 28491 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 28492 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 28493 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 28494 * with extra animation classes that can be added. 28495 * 28496 * ```css 28497 * .ng-hide:not(.ng-hide-animate) { 28498 * /* this is just another form of hiding an element */ 28499 * display: block!important; 28500 * position: absolute; 28501 * top: -9999px; 28502 * left: -9999px; 28503 * } 28504 * ``` 28505 * 28506 * By default you don't need to override in CSS anything and the animations will work around the display style. 28507 * 28508 * ## A note about animations with `ngShow` 28509 * 28510 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 28511 * is true and false. This system works like the animation system present with ngClass except that 28512 * you must also include the !important flag to override the display property 28513 * so that you can perform an animation when the element is hidden during the time of the animation. 28514 * 28515 * ```css 28516 * // 28517 * //a working example can be found at the bottom of this page 28518 * // 28519 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 28520 * /* this is required as of 1.3x to properly 28521 * apply all styling in a show/hide animation */ 28522 * transition: 0s linear all; 28523 * } 28524 * 28525 * .my-element.ng-hide-add-active, 28526 * .my-element.ng-hide-remove-active { 28527 * /* the transition is defined in the active class */ 28528 * transition: 1s linear all; 28529 * } 28530 * 28531 * .my-element.ng-hide-add { ... } 28532 * .my-element.ng-hide-add.ng-hide-add-active { ... } 28533 * .my-element.ng-hide-remove { ... } 28534 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 28535 * ``` 28536 * 28537 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 28538 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 28539 * 28540 * @animations 28541 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 28542 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 28543 * 28544 * @element ANY 28545 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 28546 * then the element is shown or hidden respectively. 28547 * 28548 * @example 28549 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 28550 <file name="index.html"> 28551 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 28552 <div> 28553 Show: 28554 <div class="check-element animate-show" ng-show="checked">
28555 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 28556 </div> 28557 </div> 28558 <div> 28559 Hide: 28560 <div class="check-element animate-show" ng-hide="checked"> 28561 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 28562 </div> 28563 </div> 28564 </file> 28565 <file name="glyphicons.css"> 28566 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 28567 </file> 28568 <file name="animations.css"> 28569 .animate-show { 28570 line-height: 20px; 28571 opacity: 1; 28572 padding: 10px; 28573 border: 1px solid black; 28574 background: white; 28575 } 28576 28577 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 28578 transition: all linear 0.5s; 28579 } 28580 28581 .animate-show.ng-hide { 28582 line-height: 0; 28583 opacity: 0; 28584 padding: 0 10px; 28585 } 28586 28587 .check-element { 28588 padding: 10px; 28589 border: 1px solid black; 28590 background: white; 28591 } 28592 </file> 28593 <file name="protractor.js" type="protractor"> 28594 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 28595 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 28596 28597 it('should check ng-show / ng-hide', function() { 28598 expect(thumbsUp.isDisplayed()).toBeFalsy(); 28599 expect(thumbsDown.isDisplayed()).toBeTruthy(); 28600 28601 element(by.model('checked')).click(); 28602 28603 expect(thumbsUp.isDisplayed()).toBeTruthy(); 28604 expect(thumbsDown.isDisplayed()).toBeFalsy(); 28605 }); 28606 </file> 28607 </example> 28608 */ 28609var ngShowDirective = ['$animate', function($animate) { 28610 return { 28611 restrict: 'A', 28612 multiElement: true, 28613 link: function(scope, element, attr) { 28614 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 28615 // we're adding a temporary, animation-specific class for ng-hide since this way 28616 // we can control when the element is actually displayed on screen without having 28617 // to have a global/greedy CSS selector that breaks when other animations are run. 28618 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 28619 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 28620 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 28621 }); 28622 }); 28623 } 28624 }; 28625}]; 28626 28627 28628/** 28629 * @ngdoc directive 28630 * @name ngHide 28631 * @multiElement 28632 * 28633 * @description 28634 * The `ngHide` directive shows or hides the given HTML element based on the expression 28635 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 28636 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 28637 * in AngularJS and sets the display style to none (using an !important flag). 28638 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 28639 * 28640 * ```html 28641 * <!-- when $scope.myValue is truthy (element is hidden) --> 28642 * <div ng-hide="myValue" class="ng-hide"></div> 28643 * 28644 * <!-- when $scope.myValue is falsy (element is visible) --> 28645 * <div ng-hide="myValue"></div> 28646 * ``` 28647 * 28648 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 28649 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 28650 * from the element causing the element not to appear hidden. 28651 * 28652 * ## Why is !important used? 28653 * 28654 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 28655 * can be easily overridden by heavier selectors. For example, something as simple 28656 * as changing the display style on a HTML list item would make hidden elements appear visible. 28657 * This also becomes a bigger issue when dealing with CSS frameworks. 28658 * 28659 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 28660 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 28661 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 28662 * 28663 * ### Overriding `.ng-hide` 28664 * 28665 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 28666 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 28667 * class in CSS: 28668 * 28669 * ```css 28670 * .ng-hide { 28671 * /* this is just another form of hiding an element */ 28672 * display: block!important; 28673 * position: absolute; 28674 * top: -9999px; 28675 * left: -9999px; 28676 * } 28677 * ``` 28678 * 28679 * By default you don't need to override in CSS anything and the animations will work around the display style. 28680 * 28681 * ## A note about animations with `ngHide` 28682 * 28683 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 28684 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 28685 * CSS class is added and removed for you instead of your own CSS class. 28686 * 28687 * ```css 28688 * // 28689 * //a working example can be found at the bottom of this page 28690 * // 28691 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 28692 * transition: 0.5s linear all; 28693 * } 28694 * 28695 * .my-element.ng-hide-add { ... } 28696 * .my-element.ng-hide-add.ng-hide-add-active { ... } 28697 * .my-element.ng-hide-remove { ... } 28698 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 28699 * ``` 28700 * 28701 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 28702 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 28703 * 28704 * @animations 28705 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 28706 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 28707 * 28708 * @element ANY 28709 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 28710 * the element is shown or hidden respectively. 28711 * 28712 * @example 28713 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 28714 <file name="index.html"> 28715 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 28716 <div> 28717 Show: 28718 <div class="check-element animate-hide" ng-show="checked">
28719 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 28720 </div> 28721 </div> 28722 <div> 28723 Hide: 28724 <div class="check-element animate-hide" ng-hide="checked"> 28725 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 28726 </div> 28727 </div> 28728 </file> 28729 <file name="glyphicons.css"> 28730 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 28731 </file> 28732 <file name="animations.css"> 28733 .animate-hide { 28734 transition: all linear 0.5s; 28735 line-height: 20px; 28736 opacity: 1; 28737 padding: 10px; 28738 border: 1px solid black; 28739 background: white; 28740 } 28741 28742 .animate-hide.ng-hide { 28743 line-height: 0; 28744 opacity: 0; 28745 padding: 0 10px; 28746 } 28747 28748 .check-element { 28749 padding: 10px; 28750 border: 1px solid black; 28751 background: white; 28752 } 28753 </file> 28754 <file name="protractor.js" type="protractor"> 28755 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 28756 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 28757 28758 it('should check ng-show / ng-hide', function() { 28759 expect(thumbsUp.isDisplayed()).toBeFalsy(); 28760 expect(thumbsDown.isDisplayed()).toBeTruthy(); 28761 28762 element(by.model('checked')).click(); 28763 28764 expect(thumbsUp.isDisplayed()).toBeTruthy(); 28765 expect(thumbsDown.isDisplayed()).toBeFalsy(); 28766 }); 28767 </file> 28768 </example> 28769 */ 28770var ngHideDirective = ['$animate', function($animate) { 28771 return { 28772 restrict: 'A', 28773 multiElement: true, 28774 link: function(scope, element, attr) { 28775 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 28776 // The comment inside of the ngShowDirective explains why we add and 28777 // remove a temporary class for the show/hide animation 28778 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 28779 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 28780 }); 28781 }); 28782 } 28783 }; 28784}]; 28785 28786/** 28787 * @ngdoc directive 28788 * @name ngStyle 28789 * @restrict AC 28790 * 28791 * @description 28792 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 28793 * 28794 * @element ANY 28795 * @param {expression} ngStyle 28796 * 28797 * {@link guide/expression Expression} which evals to an 28798 * object whose keys are CSS style names and values are corresponding values for those CSS 28799 * keys. 28800 * 28801 * Since some CSS style names are not valid keys for an object, they must be quoted. 28802 * See the 'background-color' style in the example below. 28803 * 28804 * @example 28805 <example> 28806 <file name="index.html"> 28807 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 28808 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 28809 <input type="button" value="clear" ng-click="myStyle={}"> 28810 <br/> 28811 <span ng-style="myStyle">Sample Text</span> 28812 <pre>myStyle={{myStyle}}</pre> 28813 </file> 28814 <file name="style.css"> 28815 span { 28816 color: black; 28817 } 28818 </file> 28819 <file name="protractor.js" type="protractor"> 28820 var colorSpan = element(by.css('span')); 28821 28822 it('should check ng-style', function() { 28823 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 28824 element(by.css('input[value=\'set color\']')).click(); 28825 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 28826 element(by.css('input[value=clear]')).click(); 28827 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 28828 }); 28829 </file> 28830 </example> 28831 */ 28832var ngStyleDirective = ngDirective(function(scope, element, attr) { 28833 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 28834 if (oldStyles && (newStyles !== oldStyles)) { 28835 forEach(oldStyles, function(val, style) { element.css(style, '');}); 28836 } 28837 if (newStyles) element.css(newStyles); 28838 }, true); 28839}); 28840 28841/** 28842 * @ngdoc directive 28843 * @name ngSwitch 28844 * @restrict EA 28845 * 28846 * @description 28847 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 28848 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 28849 * as specified in the template. 28850 * 28851 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 28852 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 28853 * matches the value obtained from the evaluated expression. In other words, you define a container element 28854 * (where you place the directive), place an expression on the **`on="..."` attribute** 28855 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 28856 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 28857 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 28858 * attribute is displayed. 28859 * 28860 * <div class="alert alert-info"> 28861 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 28862 * as literal string values to match against. 28863 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
28863omeVal"` not against the 28864 * value of the expression `$scope.someVal`. 28865 * </div> 28866 28867 * @animations 28868 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 28869 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 28870 * 28871 * @usage 28872 * 28873 * ``` 28874 * <ANY ng-switch="expression"> 28875 * <ANY ng-switch-when="matchValue1">...</ANY> 28876 * <ANY ng-switch-when="matchValue2">...</ANY> 28877 * <ANY ng-switch-default>...</ANY> 28878 * </ANY> 28879 * ``` 28880 * 28881 * 28882 * @scope 28883 * @priority 1200 28884 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 28885 * On child elements add: 28886 * 28887 * * `ngSwitchWhen`: the case statement to match against. If match then this 28888 * case will be displayed. If the same match appears multiple times, all the 28889 * elements will be displayed. 28890 * * `ngSwitchDefault`: the default case when no other case match. If there 28891 * are multiple default cases, all of them will be displayed when no other 28892 * case match. 28893 * 28894 * 28895 * @example 28896 <example module="switchExample" deps="angular-animate.js" animations="true"> 28897 <file name="index.html">
28898 <div ng-controller="ExampleController"> 28899 <select ng-model="selection" ng-options="item for item in items"> 28900 </select> 28901 <code>selection={{selection}}</code> 28902 <hr/> 28903 <div class="animate-switch-container" 28904 ng-switch on="selection"> 28905 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 28906 <div class="animate-switch" ng-switch-when="home">Home Span</div> 28907 <div class="animate-switch" ng-switch-default>default</div> 28908 </div> 28909 </div> 28910 </file> 28911 <file name="script.js"> 28912 angular.module('switchExample', ['ngAnimate']) 28913 .controller('ExampleController', ['$scope', function($scope) { 28914 $scope.items = ['settings', 'home', 'other']; 28915 $scope.selection = $scope.items[0]; 28916 }]); 28917 </file> 28918 <file name="animations.css"> 28919 .animate-switch-container { 28920 position:relative; 28921 background:white; 28922 border:1px solid black; 28923 height:40px; 28924 overflow:hidden; 28925 } 28926 28927 .animate-switch { 28928 padding:10px; 28929 } 28930 28931 .animate-switch.ng-animate { 28932 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28933 28934 position:absolute; 28935 top:0; 28936 left:0; 28937 right:0; 28938 bottom:0; 28939 } 28940 28941 .animate-switch.ng-leave.ng-leave-active, 28942 .animate-switch.ng-enter { 28943 top:-50px; 28944 } 28945 .animate-switch.ng-leave, 28946 .animate-switch.ng-enter.ng-enter-active { 28947 top:0; 28948 } 28949 </file> 28950 <file name="protractor.js" type="protractor"> 28951 var switchElem = element(by.css('[ng-switch]')); 28952 var select = element(by.model('selection')); 28953 28954 it('should start in settings', function() { 28955 expect(switchElem.getText()).toMatch(/Settings Div/); 28956 }); 28957 it('should change to home', function() { 28958 select.all(by.css('option')).get(1).click(); 28959 expect(switchElem.getText()).toMatch(/Home Span/); 28960 }); 28961 it('should select default', function() { 28962 select.all(by.css('option')).get(2).click(); 28963 expect(switchElem.getText()).toMatch(/default/); 28964 }); 28965 </file> 28966 </example> 28967 */ 28968var ngSwitchDirective = ['$animate', function($animate) { 28969 return { 28970 require: 'ngSwitch', 28971 28972 // asks for $scope to fool the BC controller module 28973 controller: ['$scope', function ngSwitchController() { 28974 this.cases = {}; 28975 }], 28976 link: function(scope, element, attr, ngSwitchController) { 28977 var watchExpr = attr.ngSwitch || attr.on, 28978 selectedTranscludes = [], 28979 selectedElements = [], 28980 previousLeaveAnimations = [], 28981 selectedScopes = []; 28982 28983 var spliceFactory = function(array, index) { 28984 return function() { array.splice(index, 1); }; 28985 }; 28986 28987 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 28988 var i, ii; 28989 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 28990 $animate.cancel(previousLeaveAnimations[i]); 28991 } 28992 previousLeaveAnimations.length = 0; 28993 28994 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 28995 var selected = getBlockNodes(selectedElements[i].clone); 28996 selectedScopes[i].$destroy(); 28997 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 28998 promise.then(spliceFactory(previousLeaveAnimations, i)); 28999 } 29000 29001 selectedElements.length = 0; 29002 selectedScopes.length = 0; 29003 29004 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 29005 forEach(selectedTranscludes, function(selectedTransclude) { 29006 selectedTransclude.transclude(function(caseElement, selectedScope) { 29007 selectedScopes.push(selectedScope); 29008 var anchor = selectedTransclude.element; 29009 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 29010 var block = { clone: caseElement }; 29011 29012 selectedElements.push(block);
29013 $animate.enter(caseElement, anchor.parent(), anchor); 29014 }); 29015 }); 29016 } 29017 }); 29018 } 29019 }; 29020}]; 29021 29022var ngSwitchWhenDirective = ngDirective({ 29023 transclude: 'element', 29024 priority: 1200, 29025 require: '^ngSwitch', 29026 multiElement: true, 29027 link: function(scope, element, attrs, ctrl, $transclude) { 29028 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 29029 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 29030 } 29031}); 29032 29033var ngSwitchDefaultDirective = ngDirective({ 29034 transclude: 'element', 29035 priority: 1200, 29036 require: '^ngSwitch', 29037 multiElement: true, 29038 link: function(scope, element, attr, ctrl, $transclude) { 29039 ctrl.cases['?'] = (ctrl.cases['?'] || []); 29040 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 29041 } 29042}); 29043 29044/** 29045 * @ngdoc directive 29046 * @name ngTransclude 29047 * @restrict EAC 29048 * 29049 * @description 29050 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 29051 * 29052 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 29053 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 29054 * 29055 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 29056 * content of this element will be removed before the transcluded content is inserted. 29057 * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case 29058 * that no transcluded content is provided. 29059 * 29060 * @element ANY 29061 * 29062 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 29063 * or its value is the same as the name of the attribute then the default slot is used. 29064 * 29065 * @example 29066 * ### Basic transclusion 29067 * This example demonstrates basic transclusion of content into a component directive. 29068 * <example name="simpleTranscludeExample" module="transcludeExample"> 29069 * <file name="index.html"> 29070 * <script> 29071 * angular.module('transcludeExample', []) 29072 * .directive('pane', function(){ 29073 * return { 29074 * restrict: 'E', 29075 * transclude: true, 29076 * scope: { title:'@' }, 29077 * template: '<div style="border: 1px solid black;">' + 29078 * '<div style="background-color: gray">{{title}}</div>' + 29079 * '<ng-transclude></ng-transclude>' + 29080 * '</div>' 29081 * }; 29082 * }) 29083 * .controller('ExampleController', ['$scope', function($scope) { 29084 * $scope.title = 'Lorem Ipsum'; 29085 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 29086 * }]); 29087 * </script>
29088 * <div ng-controller="ExampleController"> 29089 * <input ng-model="title" aria-label="title"> <br/> 29090 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 29091 * <pane title="{{title}}">{{text}}</pane> 29092 * </div> 29093 * </file> 29094 * <file name="protractor.js" type="protractor"> 29095 * it('should have transcluded', function() { 29096 * var titleElement = element(by.model('title')); 29097 * titleElement.clear(); 29098 * titleElement.sendKeys('TITLE'); 29099 * var textElement = element(by.model('text')); 29100 * textElement.clear(); 29101 * textElement.sendKeys('TEXT'); 29102 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 29103 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 29104 * }); 29105 * </file> 29106 * </example> 29107 * 29108 * @example 29109 * ### Transclude fallback content 29110 * This example shows how to use `NgTransclude` with fallback content, that 29111 * is displayed if no transcluded content is provided. 29112 * 29113 * <example module="transcludeFallbackContentExample"> 29114 * <file name="index.html"> 29115 * <script> 29116 * angular.module('transcludeFallbackContentExample', []) 29117 * .directive('myButton', function(){ 29118 * return { 29119 * restrict: 'E', 29120 * transclude: true, 29121 * scope: true, 29122 * template: '<button style="cursor: pointer;">' + 29123 * '<ng-transclude>' + 29124 * '<b style="color: red;">Button1</b>' + 29125 * '</ng-transclude>' + 29126 * '</button>' 29127 * }; 29128 * }); 29129 * </script> 29130 * <!-- fallback button content --> 29131 * <my-button id="fallback"></my-button> 29132 * <!-- modified button content --> 29133 * <my-button id="modified"> 29134 * <i style="color: green;">Button2</i> 29135 * </my-button> 29136 * </file> 29137 * <file name="protractor.js" type="protractor"> 29138 * it('should have different transclude element content', function() { 29139 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 29140 * expect(element(by.id('modified')).getText()).toBe('Button2'); 29141 * }); 29142 * </file> 29143 * </example> 29144 * 29145 * @example 29146 * ### Multi-slot transclusion 29147 * This example demonstrates using multi-slot transclusion in a component directive. 29148 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 29149 * <file name="index.html"> 29150 * <style> 29151 * .title, .footer { 29152 * background-color: gray 29153 * } 29154 * </style>
29155 * <div ng-controller="ExampleController"> 29156 * <input ng-model="title" aria-label="title"> <br/> 29157 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 29158 * <pane> 29159 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 29160 * <pane-body><p>{{text}}</p></pane-body> 29161 * </pane> 29162 * </div> 29163 * </file> 29164 * <file name="app.js"> 29165 * angular.module('multiSlotTranscludeExample', []) 29166 * .directive('pane', function(){ 29167 * return { 29168 * restrict: 'E', 29169 * transclude: { 29170 * 'title': '?paneTitle', 29171 * 'body': 'paneBody', 29172 * 'footer': '?paneFooter' 29173 * }, 29174 * template: '<div style="border: 1px solid black;">' + 29175 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 29176 * '<div ng-transclude="body"></div>' + 29177 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 29178 * '</div>' 29179 * }; 29180 * }) 29181 * .controller('ExampleController', ['$scope', function($scope) { 29182 * $scope.title = 'Lorem Ipsum'; 29183 * $scope.link = "https://google.com"; 29184 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 29185 * }]); 29186 * </file> 29187 * <file name="protractor.js" type="protractor"> 29188 * it('should have transcluded the title and the body', function() { 29189 * var titleElement = element(by.model('title')); 29190 * titleElement.clear(); 29191 * titleElement.sendKeys('TITLE'); 29192 * var textElement = element(by.model('text')); 29193 * textElement.clear(); 29194 * textElement.sendKeys('TEXT'); 29195 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 29196 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 29197 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 29198 * }); 29199 * </file> 29200 * </example> 29201 */ 29202var ngTranscludeMinErr = minErr('ngTransclude'); 29203var ngTranscludeDirective = ngDirective({ 29204 restrict: 'EAC', 29205 link: function($scope, $element, $attrs, controller, $transclude) { 29206 29207 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 29208 // If the attribute is of the form: `ng-transclude="ng-transclude"` 29209 // then treat it like the default 29210 $attrs.ngTransclude = ''; 29211 } 29212 29213 function ngTranscludeCloneAttachFn(clone) { 29214 if (clone.length) { 29215 $element.empty(); 29216 $element.append(clone); 29217 } 29218 } 29219 29220 if (!$transclude) { 29221 throw ngTranscludeMinErr('orphan', 29222 'Illegal use of ngTransclude directive in the template! ' + 29223 'No parent directive that requires a transclusion found. ' + 29224 'Element: {0}', 29225 startingTag($element)); 29226 } 29227 29228 // If there is no slot name defined or the slot name is not optional 29229 // then transclude the slot 29230 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 29231 $transclude(ngTranscludeCloneAttachFn, null, slotName); 29232 } 29233}); 29234 29235/** 29236 * @ngdoc directive 29237 * @name script 29238 * @restrict E 29239 * 29240 * @description 29241 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 29242 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 29243 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 29244 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 29245 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 29246 * 29247 * @param {string} type Must be set to `'text/ng-template'`. 29248 * @param {string} id Cache name of the template. 29249 * 29250 * @example 29251 <example> 29252 <file name="index.html"> 29253 <script type="text/ng-template" id="/tpl.html"> 29254 Content of the template. 29255 </script> 29256 29257 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 29258 <div id="tpl-content" ng-include src="currentTpl"></div> 29259 </file> 29260 <file name="protractor.js" type="protractor"> 29261 it('should load template defined inside script tag', function() {
29262 element(by.css('#tpl-link')).click(); 29263 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 29264 }); 29265 </file> 29266 </example> 29267 */ 29268var scriptDirective = ['$templateCache', function($templateCache) { 29269 return { 29270 restrict: 'E', 29271 terminal: true, 29272 compile: function(element, attr) { 29273 if (attr.type == 'text/ng-template') { 29274 var templateUrl = attr.id, 29275 text = element[0].text; 29276 29277 $templateCache.put(templateUrl, text); 29278 } 29279 } 29280 }; 29281}]; 29282 29283var noopNgModelController = { $setViewValue: noop, $render: noop }; 29284 29285function chromeHack(optionElement) { 29286 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 29287 // Adding an <option selected="selected"> element to a <select required="required"> should 29288 // automatically select the new element 29289 if (optionElement[0].hasAttribute('selected')) { 29290 optionElement[0].selected = true; 29291 } 29292} 29293 29294/** 29295 * @ngdoc type 29296 * @name select.SelectController 29297 * @description 29298 * The controller for the `<select>` directive. This provides support for reading 29299 * and writing the selected value(s) of the control and also coordinates dynamically 29300 * added `<option>` elements, perhaps by an `ngRepeat` directive. 29301 */ 29302var SelectController = 29303 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 29304 29305 var self = this, 29306 optionsMap = new HashMap(); 29307 29308 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 29309 self.ngModelCtrl = noopNgModelController; 29310 29311 // The "unknown" option is one that is prepended to the list if the viewValue 29312 // does not match any of the options. When it is rendered the value of the unknown 29313 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 29314 // 29315 // We can't just jqLite('<option>') since jqLite is not smart enough 29316 // to create it in <select> and IE barfs otherwise. 29317 self.unknownOption = jqLite(document.createElement('option')); 29318 self.renderUnknownOption = function(val) { 29319 var unknownVal = '? ' + hashKey(val) + ' ?'; 29320 self.unknownOption.val(unknownVal); 29321 $element.prepend(self.unknownOption); 29322 $element.val(unknownVal); 29323 }; 29324 29325 $scope.$on('$destroy', function() { 29326 // disable unknown option so that we don't do work when the whole select is being destroyed 29327 self.renderUnknownOption = noop; 29328 }); 29329 29330 self.removeUnknownOption = function() { 29331 if (self.unknownOption.parent()) self.unknownOption.remove(); 29332 }; 29333 29334 29335 // Read the value of the select control, the implementation of this changes depending 29336 // upon whether the select can have multiple values and whether ngOptions is at work. 29337 self.readValue = function readSingleValue() { 29338 self.removeUnknownOption(); 29339 return $element.val(); 29340 }; 29341 29342 29343 // Write the value to the select control, the implementation of this changes depending 29344 // upon whether the select can have multiple values and whether ngOptions is at work. 29345 self.writeValue = function writeSingleValue(value) { 29346 if (self.hasOption(value)) { 29347 self.removeUnknownOption(); 29348 $element.val(value); 29349 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 29350 } else { 29351 if (value == null && self.emptyOption) { 29352 self.removeUnknownOption(); 29353 $element.val(''); 29354 } else { 29355 self.renderUnknownOption(value); 29356 } 29357 } 29358 }; 29359 29360 29361 // Tell the select control that an option, with the given value, has been added 29362 self.addOption = function(value, element) { 29363 assertNotHasOwnProperty(value, '"option value"'); 29364 if (value === '') { 29365 self.emptyOption = element; 29366 } 29367 var count = optionsMap.get(value) || 0; 29368 optionsMap.put(value, count + 1); 29369 self.ngModelCtrl.$render(); 29370 chromeHack(element); 29371 }; 29372 29373 // Tell the select control that an option, with the given value, has been removed 29374 self.removeOption = function(value) { 29375 var count = optionsMap.get(value); 29376 if (count) { 29377 if (count === 1) { 29378 optionsMap.remove(value); 29379 if (value === '') { 29380 self.emptyOption = undefined; 29381 } 29382 } else { 29383 optionsMap.put(value, count - 1); 29384 } 29385 } 29386 }; 29387 29388 // Check whether the select control has an option matching the given value 29389 self.hasOption = function(value) { 29390 return !!optionsMap.get(value); 29391 }; 29392 29393 29394 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 29395 29396 if (interpolateValueFn) { 29397 // The value attribute is interpolated 29398 var oldVal; 29399 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 29400 if (isDefined(oldVal)) { 29401 self.removeOption(oldVal); 29402 } 29403 oldVal = newVal; 29404 self.addOption(newVal, optionElement); 29405 }); 29406 } else if (interpolateTextFn) { 29407 // The text content is interpolated 29408 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 29409 optionAttrs.$set('value', newVal); 29410 if (oldVal !== newVal) { 29411 self.removeOption(oldVal); 29412 } 29413 self.addOption(newVal, optionElement); 29414 }); 29415 } else { 29416 // The value attribute is static 29417 self.addOption(optionAttrs.value, optionElement); 29418 } 29419 29420 optionElement.on('$destroy', function() { 29421 self.removeOption(optionAttrs.value); 29422 self.ngModelCtrl.$render(); 29423 });
29424 }; 29425}]; 29426 29427/** 29428 * @ngdoc directive 29429 * @name select 29430 * @restrict E 29431 * 29432 * @description 29433 * HTML `SELECT` element with angular data-binding. 29434 * 29435 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 29436 * between the scope and the `<select>` control (including setting default values). 29437 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 29438 * {@link ngOptions `ngOptions`} directives. 29439 * 29440 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 29441 * to the model identified by the `ngModel` directive. With static or repeated options, this is 29442 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 29443 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 29444 * 29445 * <div class="alert alert-warning"> 29446 * Note that the value of a `select` directive used without `ngOptions` is always a string. 29447 * When the model needs to be bound to a non-string value, you must either explicitly convert it 29448 * using a directive (see example below) or use `ngOptions` to specify the set of options. 29449 * This is because an option element can only be bound to string values at present. 29450 * </div> 29451 * 29452 * If the viewValue of `ngModel` does not match any of the options, then the control 29453 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 29454 * 29455 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 29456 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 29457 * option. See example below for demonstration. 29458 * 29459 * <div class="alert alert-info"> 29460 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 29461 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as 29462 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 29463 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 29464 * a new scope for each repeated instance. 29465 * </div> 29466 * 29467 * 29468 * @param {string} ngModel Assignable angular expression to data-bind to. 29469 * @param {string=} name Property name of the form under which the control is published. 29470 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 29471 * bound to the model as an array. 29472 * @param {string=} required Sets `required` validation error key if the value is not entered. 29473 * @param {string=} ngRequired Adds required attribute and required validation constraint to 29474 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 29475 * when you want to data-bind to the required attribute. 29476 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 29477 * interaction with the select element. 29478 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 29479 * set on the model on selection. See {@link ngOptions `ngOptions`}. 29480 * 29481 * @example 29482 * ### Simple `select` elements with static options 29483 * 29484 * <example name="static-select" module="staticSelect"> 29485 * <file name="index.html">
29486 * <div ng-controller="ExampleController"> 29487 * <form name="myForm"> 29488 * <label for="singleSelect"> Single select: </label><br> 29489 * <select name="singleSelect" ng-model="data.singleSelect"> 29490 * <option value="option-1">Option 1</option> 29491 * <option value="option-2">Option 2</option> 29492 * </select><br> 29493 * 29494 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 29495 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 29496 * <option value="">---Please select---</option> <!-- not selected / blank option --> 29497 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 29498 * <option value="option-2">Option 2</option> 29499 * </select><br> 29500 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 29501 * <tt>singleSelect = {{data.singleSelect}}</tt> 29502 * 29503 * <hr> 29504 * <label for="multipleSelect"> Multiple select: </label><br> 29505 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 29506 * <option value="option-1">Option 1</option> 29507 * <option value="option-2">Option 2</option> 29508 * <option value="option-3">Option 3</option> 29509 * </select><br> 29510 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 29511 * </form> 29512 * </div> 29513 * </file> 29514 * <file name="app.js"> 29515 * angular.module('staticSelect', []) 29516 * .controller('ExampleController', ['$scope', function($scope) { 29517 * $scope.data = { 29518 * singleSelect: null, 29519 * multipleSelect: [], 29520 * option1: 'option-1', 29521 * }; 29522 * 29523 * $scope.forceUnknownOption = function() { 29524 * $scope.data.singleSelect = 'nonsense'; 29525 * }; 29526 * }]); 29527 * </file> 29528 *</example> 29529 * 29530 * ### Using `ngRepeat` to generate `select` options 29531 * <example name="ngrepeat-select" module="ngrepeatSelect"> 29532 * <file name="index.html">
29533 * <div ng-controller="ExampleController"> 29534 * <form name="myForm"> 29535 * <label for="repeatSelect"> Repeat select: </label> 29536 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 29537 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 29538 * </select> 29539 * </form> 29540 * <hr> 29541 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 29542 * </div> 29543 * </file> 29544 * <file name="app.js"> 29545 * angular.module('ngrepeatSelect', []) 29546 * .controller('ExampleController', ['$scope', function($scope) { 29547 * $scope.data = { 29548 * repeatSelect: null, 29549 * availableOptions: [ 29550 * {id: '1', name: 'Option A'}, 29551 * {id: '2', name: 'Option B'}, 29552 * {id: '3', name: 'Option C'} 29553 * ], 29554 * }; 29555 * }]); 29556 * </file> 29557 *</example> 29558 * 29559 * 29560 * ### Using `select` with `ngOptions` and setting a default value 29561 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 29562 * 29563 * <example name="select-with-default-values" module="defaultValueSelect"> 29564 * <file name="index.html">
29565 * <div ng-controller="ExampleController"> 29566 * <form name="myForm"> 29567 * <label for="mySelect">Make a choice:</label> 29568 * <select name="mySelect" id="mySelect" 29569 * ng-options="option.name for option in data.availableOptions track by option.id" 29570 * ng-model="data.selectedOption"></select> 29571 * </form> 29572 * <hr> 29573 * <tt>option = {{data.selectedOption}}</tt><br/> 29574 * </div> 29575 * </file> 29576 * <file name="app.js"> 29577 * angular.module('defaultValueSelect', []) 29578 * .controller('ExampleController', ['$scope', function($scope) { 29579 * $scope.data = { 29580 * availableOptions: [ 29581 * {id: '1', name: 'Option A'}, 29582 * {id: '2', name: 'Option B'}, 29583 * {id: '3', name: 'Option C'} 29584 * ], 29585 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 29586 * }; 29587 * }]); 29588 * </file> 29589 *</example> 29590 * 29591 * 29592 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 29593 * 29594 * <example name="select-with-non-string-options" module="nonStringSelect"> 29595 * <file name="index.html"> 29596 * <select ng-model="model.id" convert-to-number> 29597 * <option value="0">Zero</option> 29598 * <option value="1">One</option> 29599 * <option value="2">Two</option> 29600 * </select> 29601 * {{ model }} 29602 * </file> 29603 * <file name="app.js"> 29604 * angular.module('nonStringSelect', []) 29605 * .run(function($rootScope) { 29606 * $rootScope.model = { id: 2 }; 29607 * }) 29608 * .directive('convertToNumber', function() { 29609 * return { 29610 * require: 'ngModel', 29611 * link: function(scope, element, attrs, ngModel) { 29612 * ngModel.$parsers.push(function(val) { 29613 * return parseInt(val, 10); 29614 * }); 29615 * ngModel.$formatters.push(function(val) { 29616 * return '' + val; 29617 * }); 29618 * } 29619 * }; 29620 * }); 29621 * </file> 29622 * <file name="protractor.js" type="protractor"> 29623 * it('should initialize to model', function() { 29624 * var select = element(by.css('select')); 29625 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 29626 * }); 29627 * </file> 29628 * </example> 29629 * 29630 */ 29631var selectDirective = function() { 29632 29633 return { 29634 restrict: 'E', 29635 require: ['select', '?ngModel'], 29636 controller: SelectController, 29637 priority: 1, 29638 link: { 29639 pre: selectPreLink, 29640 post: selectPostLink 29641 } 29642 }; 29643 29644 function selectPreLink(scope, element, attr, ctrls) { 29645 29646 // if ngModel is not defined, we don't need to do anything 29647 var ngModelCtrl = ctrls[1]; 29648 if (!ngModelCtrl) return; 29649 29650 var selectCtrl = ctrls[0]; 29651 29652 selectCtrl.ngModelCtrl = ngModelCtrl; 29653 29654 // When the selected item(s) changes we delegate getting the value of the select control 29655 // to the `readValue` method, which can be changed if the select can have multiple 29656 // selected values or if the options are being generated by `ngOptions` 29657 element.on('change', function() { 29658 scope.$apply(function() { 29659 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 29660 }); 29661 }); 29662 29663 // If the select allows multiple values then we need to modify how we read and write 29664 // values from and to the control; also what it means for the value to be empty and 29665 // we have to add an extra watch since ngModel doesn't work well with arrays - it 29666 // doesn't trigger rendering if only an item in the array changes. 29667 if (attr.multiple) { 29668 29669 // Read value now needs to check each option to see if it is selected 29670 selectCtrl.readValue = function readMultipleValue() { 29671 var array = []; 29672 forEach(element.find('option'), function(option) { 29673 if (option.selected) { 29674 array.push(option.value); 29675 } 29676 }); 29677 return array; 29678 }; 29679 29680 // Write value now needs to set the selected property of each matching option 29681 selectCtrl.writeValue = function writeMultipleValue(value) { 29682 var items = new HashMap(value);
vendor: 4,286 bytes, lines 29683-29788
29683 forEach(element.find('option'), function(option) { 29684 option.selected = isDefined(items.get(option.value)); 29685 }); 29686 }; 29687 29688 // we have to do it on each watch since ngModel watches reference, but 29689 // we need to work of an array, so we need to see if anything was inserted/removed 29690 var lastView, lastViewRef = NaN; 29691 scope.$watch(function selectMultipleWatch() { 29692 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 29693 lastView = shallowCopy(ngModelCtrl.$viewValue); 29694 ngModelCtrl.$render(); 29695 } 29696 lastViewRef = ngModelCtrl.$viewValue; 29697 }); 29698 29699 // If we are a multiple select then value is now a collection 29700 // so the meaning of $isEmpty changes 29701 ngModelCtrl.$isEmpty = function(value) { 29702 return !value || value.length === 0; 29703 }; 29704 29705 } 29706 } 29707 29708 function selectPostLink(scope, element, attrs, ctrls) { 29709 // if ngModel is not defined, we don't need to do anything 29710 var ngModelCtrl = ctrls[1]; 29711 if (!ngModelCtrl) return; 29712 29713 var selectCtrl = ctrls[0]; 29714 29715 // We delegate rendering to the `writeValue` method, which can be changed 29716 // if the select can have multiple selected values or if the options are being 29717 // generated by `ngOptions`. 29718 // This must be done in the postLink fn to prevent $render to be called before 29719 // all nodes have been linked correctly. 29720 ngModelCtrl.$render = function() { 29721 selectCtrl.writeValue(ngModelCtrl.$viewValue); 29722 }; 29723 } 29724}; 29725 29726 29727// The option directive is purely designed to communicate the existence (or lack of) 29728// of dynamically created (and destroyed) option elements to their containing select 29729// directive via its controller. 29730var optionDirective = ['$interpolate', function($interpolate) { 29731 return { 29732 restrict: 'E', 29733 priority: 100, 29734 compile: function(element, attr) { 29735 29736 if (isDefined(attr.value)) { 29737 // If the value attribute is defined, check if it contains an interpolation 29738 var interpolateValueFn = $interpolate(attr.value, true); 29739 } else { 29740 // If the value attribute is not defined then we fall back to the 29741 // text content of the option element, which may be interpolated 29742 var interpolateTextFn = $interpolate(element.text(), true); 29743 if (!interpolateTextFn) { 29744 attr.$set('value', element.text()); 29745 } 29746 } 29747 29748 return function(scope, element, attr) { 29749 29750 // This is an optimization over using ^^ since we don't want to have to search 29751 // all the way to the root of the DOM for every single option element 29752 var selectCtrlName = '$selectController', 29753 parent = element.parent(), 29754 selectCtrl = parent.data(selectCtrlName) || 29755 parent.parent().data(selectCtrlName); // in case we are in optgroup 29756 29757 if (selectCtrl) { 29758 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 29759 } 29760 }; 29761 } 29762 }; 29763}]; 29764 29765var styleDirective = valueFn({ 29766 restrict: 'E', 29767 terminal: false 29768}); 29769 29770/** 29771 * @ngdoc directive 29772 * @name ngRequired 29773 * 29774 * @description 29775 * 29776 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29777 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 29778 * applied to custom controls. 29779 * 29780 * The directive sets the `required` attribute on the element if the Angular expression inside 29781 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 29782 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 29783 * for more info. 29784 * 29785 * The validator will set the `required` error key to true if the `required` attribute is set and 29786 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty` with the 29787 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 29788 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
29789 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 29790 * 29791 * @example 29792 * <example name="ngRequiredDirective" module="ngRequiredExample"> 29793 * <file name="index.html"> 29794 * <script> 29795 * angular.module('ngRequiredExample', []) 29796 * .controller('ExampleController', ['$scope', function($scope) { 29797 * $scope.required = true; 29798 * }]); 29799 * </script> 29800 * <div ng-controller="ExampleController"> 29801 * <form name="form"> 29802 * <label for="required">Toggle required: </label> 29803 * <input type="checkbox" ng-model="required" id="required" /> 29804 * <br> 29805 * <label for="input">This input must be filled if `required` is true: </label> 29806 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 29807 * <hr> 29808 * required error set? = <code>{{form.input.$error.required}}</code><br> 29809 * model = <code>{{model}}</code> 29810 * </form> 29811 * </div> 29812 * </file> 29813 * <file name="protractor.js" type="protractor"> 29814 var required = element(by.binding('form.input.$error.required')); 29815 var model = element(by.binding('model')); 29816 var input = element(by.id('input')); 29817 29818 it('should set the required error', function() { 29819 expect(required.getText()).toContain('true'); 29820 29821 input.sendKeys('123'); 29822 expect(required.getText()).not.toContain('true'); 29823 expect(model.getText()).toContain('123'); 29824 }); 29825 * </file> 29826 * </example> 29827 */ 29828var requiredDirective = function() { 29829 return { 29830 restrict: 'A', 29831 require: '?ngModel', 29832 link: function(scope, elm, attr, ctrl) { 29833 if (!ctrl) return; 29834 attr.required = true; // force truthy in case we are on non input element 29835 29836 ctrl.$validators.required = function(modelValue, viewValue) { 29837 return !attr.required || !ctrl.$isEmpty(viewValue); 29838 }; 29839 29840 attr.$observe('required', function() { 29841 ctrl.$validate(); 29842 }); 29843 } 29844 }; 29845}; 29846 29847/** 29848 * @ngdoc directive 29849 * @name ngPattern 29850 * 29851 * @description 29852 * 29853 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29854 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 29855 * 29856 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 29857 * does not match a RegExp which is obtained by evaluating the Angular expression given in the 29858 * `ngPattern` attribute value: 29859 * * If the expression evaluates to a RegExp object, then this is used directly. 29860 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 29861 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 29862 * 29863 * <div class="alert alert-info"> 29864 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 29865 * start at the index of the last search's match, thus not taking the whole input value into 29866 * account. 29867 * </div> 29868 * 29869 * <div class="alert alert-info"> 29870 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 29871 * differences: 29872 * <ol> 29873 * <li> 29874 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 29875 * not available. 29876 * </li> 29877 * <li> 29878 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 29879 * interpolated. 29880 * </li> 29881 * </ol> 29882 * </div> 29883 * 29884 * @example 29885 * <example name="ngPatternDirective" module="ngPatternExample"> 29886 * <file name="index.html"> 29887 * <script> 29888 * angular.module('ngPatternExample', []) 29889 * .controller('ExampleController', ['$scope', function($scope) { 29890 * $scope.regex = '\\d+'; 29891 * }]); 29892 * </script>
29893 * <div ng-controller="ExampleController"> 29894 * <form name="form"> 29895 * <label for="regex">Set a pattern (regex string): </label> 29896 * <input type="text" ng-model="regex" id="regex" /> 29897 * <br> 29898 * <label for="input">This input is restricted by the current pattern: </label> 29899 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 29900 * <hr> 29901 * input valid? = <code>{{form.input.$valid}}</code><br> 29902 * model = <code>{{model}}</code> 29903 * </form> 29904 * </div> 29905 * </file> 29906 * <file name="protractor.js" type="protractor"> 29907 var model = element(by.binding('model')); 29908 var input = element(by.id('input')); 29909 29910 it('should validate the input with the default pattern', function() { 29911 input.sendKeys('aaa'); 29912 expect(model.getText()).not.toContain('aaa'); 29913 29914 input.clear().then(function() { 29915 input.sendKeys('123'); 29916 expect(model.getText()).toContain('123'); 29917 }); 29918 }); 29919 * </file> 29920 * </example> 29921 */ 29922var patternDirective = function() { 29923 return { 29924 restrict: 'A', 29925 require: '?ngModel', 29926 link: function(scope, elm, attr, ctrl) { 29927 if (!ctrl) return; 29928 29929 var regexp, patternExp = attr.ngPattern || attr.pattern; 29930 attr.$observe('pattern', function(regex) { 29931 if (isString(regex) && regex.length > 0) { 29932 regex = new RegExp('^' + regex + '$'); 29933 } 29934 29935 if (regex && !regex.test) { 29936 throw minErr('ngPattern')('noregexp', 29937 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 29938 regex, startingTag(elm)); 29939 } 29940 29941 regexp = regex || undefined; 29942 ctrl.$validate(); 29943 }); 29944 29945 ctrl.$validators.pattern = function(modelValue, viewValue) { 29946 // HTML5 pattern constraint validates the input value, so we validate the viewValue 29947 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 29948 }; 29949 } 29950 }; 29951}; 29952 29953/** 29954 * @ngdoc directive 29955 * @name ngMaxlength 29956 * 29957 * @description 29958 * 29959 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29960 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 29961 * 29962 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 29963 * is longer than the integer obtained by evaluating the Angular expression given in the 29964 * `ngMaxlength` attribute value. 29965 * 29966 * <div class="alert alert-info"> 29967 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 29968 * differences: 29969 * <ol> 29970 * <li> 29971 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 29972 * validation is not available. 29973 * </li> 29974 * <li> 29975 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 29976 * interpolated. 29977 * </li> 29978 * </ol> 29979 * </div> 29980 * 29981 * @example 29982 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 29983 * <file name="index.html"> 29984 * <script> 29985 * angular.module('ngMaxlengthExample', []) 29986 * .controller('ExampleController', ['$scope', function($scope) { 29987 * $scope.maxlength = 5; 29988 * }]); 29989 * </script>
29990 * <div ng-controller="ExampleController"> 29991 * <form name="form"> 29992 * <label for="maxlength">Set a maxlength: </label> 29993 * <input type="number" ng-model="maxlength" id="maxlength" /> 29994 * <br> 29995 * <label for="input">This input is restricted by the current maxlength: </label> 29996 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 29997 * <hr> 29998 * input valid? = <code>{{form.input.$valid}}</code><br> 29999 * model = <code>{{model}}</code> 30000 * </form> 30001 * </div> 30002 * </file> 30003 * <file name="protractor.js" type="protractor"> 30004 var model = element(by.binding('model')); 30005 var input = element(by.id('input')); 30006 30007 it('should validate the input with the default maxlength', function() { 30008 input.sendKeys('abcdef'); 30009 expect(model.getText()).not.toContain('abcdef'); 30010 30011 input.clear().then(function() { 30012 input.sendKeys('abcde'); 30013 expect(model.getText()).toContain('abcde'); 30014 }); 30015 }); 30016 * </file> 30017 * </example> 30018 */ 30019var maxlengthDirective = function() { 30020 return { 30021 restrict: 'A', 30022 require: '?ngModel', 30023 link: function(scope, elm, attr, ctrl) { 30024 if (!ctrl) return; 30025 30026 var maxlength = -1; 30027 attr.$observe('maxlength', function(value) { 30028 var intVal = toInt(value); 30029 maxlength = isNaN(intVal) ? -1 : intVal; 30030 ctrl.$validate(); 30031 }); 30032 ctrl.$validators.maxlength = function(modelValue, viewValue) { 30033 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 30034 }; 30035 } 30036 }; 30037}; 30038 30039/** 30040 * @ngdoc directive 30041 * @name ngMinlength 30042 * 30043 * @description 30044 * 30045 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 30046 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 30047 * 30048 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 30049 * is shorter than the integer obtained by evaluating the Angular expression given in the 30050 * `ngMinlength` attribute value. 30051 * 30052 * <div class="alert alert-info"> 30053 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 30054 * differences: 30055 * <ol> 30056 * <li> 30057 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 30058 * validation is not available. 30059 * </li> 30060 * <li> 30061 * The `ngMinlength` value must be an expression, while the `minlength` value must be 30062 * interpolated. 30063 * </li> 30064 * </ol> 30065 * </div> 30066 * 30067 * @example 30068 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 30069 * <file name="index.html"> 30070 * <script> 30071 * angular.module('ngMinlengthExample', []) 30072 * .controller('ExampleController', ['$scope', function($scope) { 30073 * $scope.minlength = 3; 30074 * }]); 30075 * </script>
30076 * <div ng-controller="ExampleController"> 30077 * <form name="form"> 30078 * <label for="minlength">Set a minlength: </label> 30079 * <input type="number" ng-model="minlength" id="minlength" /> 30080 * <br> 30081 * <label for="input">This input is restricted by the current minlength: </label> 30082 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 30083 * <hr> 30084 * input valid? = <code>{{form.input.$valid}}</code><br> 30085 * model = <code>{{model}}</code> 30086 * </form> 30087 * </div> 30088 * </file> 30089 * <file name="protractor.js" type="protractor"> 30090 var model = element(by.binding('model')); 30091 var input = element(by.id('input')); 30092 30093 it('should validate the input with the default minlength', function() { 30094 input.sendKeys('ab'); 30095 expect(model.getText()).not.toContain('ab'); 30096 30097 input.sendKeys('abc'); 30098 expect(model.getText()).toContain('abc'); 30099 }); 30100 * </file> 30101 * </example> 30102 */ 30103var minlengthDirective = function() { 30104 return { 30105 restrict: 'A', 30106 require: '?ngModel', 30107 link: function(scope, elm, attr, ctrl) { 30108 if (!ctrl) return; 30109 30110 var minlength = 0; 30111 attr.$observe('minlength', function(value) { 30112 minlength = toInt(value) || 0; 30113 ctrl.$validate(); 30114 }); 30115 ctrl.$validators.minlength = function(modelValue, viewValue) { 30116 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 30117 }; 30118 } 30119 }; 30120}; 30121 30122if (window.angular.bootstrap) { 30123 //AngularJS is already loaded, so we can return here... 30124 console.log('WARNING: Tried to load angular more than once.'); 30125 return; 30126} 30127 30128//try to bind to jquery now so that one can write jqLite(document).ready() 30129//but we will rebind on bootstrap again. 30130bindJQuery(); 30131 30132publishExternalAPI(angular); 30133 30134angular.module("ngLocale", [], ["$provide", function($provide) { 30135var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 30136function getDecimals(n) { 30137 n = n + ''; 30138 var i = n.indexOf('.'); 30139 return (i == -1) ? 0 : n.length - i - 1; 30140} 30141 30142function getVF(n, opt_precision) { 30143 var v = opt_precision; 30144 30145 if (undefined === v) { 30146 v = Math.min(getDecimals(n), 3); 30147 } 30148 30149 var base = Math.pow(10, v); 30150 var f = ((n * base) | 0) % base; 30151 return {v: v, f: f}; 30152} 30153 30154$provide.value("$locale", { 30155 "DATETIME_FORMATS": { 30156 "AMPMS": [ 30157 "AM", 30158 "PM" 30159 ], 30160 "DAY": [ 30161 "Sunday", 30162 "Monday", 30163 "Tuesday", 30164 "Wednesday", 30165 "Thursday", 30166 "Friday", 30167 "Saturday" 30168 ], 30169 "ERANAMES": [ 30170 "Before Christ", 30171 "Anno Domini" 30172 ], 30173 "ERAS": [ 30174 "BC", 30175 "AD" 30176 ], 30177 "FIRSTDAYOFWEEK": 6, 30178 "MONTH": [ 30179 "January", 30180 "February", 30181 "March", 30182 "April", 30183 "May", 30184 "June", 30185 "July", 30186 "August", 30187 "September", 30188 "October", 30189 "November", 30190 "December" 30191 ], 30192 "SHORTDAY": [ 30193 "Sun", 30194 "Mon", 30195 "Tue", 30196 "Wed", 30197 "Thu", 30198 "Fri", 30199 "Sat" 30200 ], 30201 "SHORTMONTH": [ 30202 "Jan", 30203 "Feb", 30204 "Mar", 30205 "Apr", 30206 "May", 30207 "Jun", 30208 "Jul", 30209 "Aug", 30210 "Sep", 30211 "Oct", 30212 "Nov", 30213 "Dec" 30214 ], 30215 "STANDALONEMONTH": [ 30216 "January", 30217 "February", 30218 "March", 30219 "April", 30220 "May", 30221 "June", 30222 "July", 30223 "August", 30224 "September", 30225 "October", 30226 "November", 30227 "December" 30228 ], 30229 "WEEKENDRANGE": [ 30230 5, 30231 6 30232 ], 30233 "fullDate": "EEEE, MMMM d, y", 30234 "longDate": "MMMM d, y", 30235 "medium": "MMM d, y h:mm:ss a", 30236 "mediumDate": "MMM d, y", 30237 "mediumTime": "h:mm:ss a", 30238 "short": "M/d/yy h:mm a", 30239 "shortDate": "M/d/yy", 30240 "shortTime": "h:mm a" 30241 }, 30242 "NUMBER_FORMATS": { 30243 "CURRENCY_SYM": "$", 30244 "DECIMAL_SEP": ".", 30245 "GROUP_SEP": ",", 30246 "PATTERNS": [ 30247 { 30248 "gSize": 3, 30249 "lgSize": 3, 30250 "maxFrac": 3, 30251 "minFrac": 0, 30252 "minInt": 1, 30253 "negPre": "-", 30254 "negSuf": "", 30255 "posPre": "", 30256 "posSuf": "" 30257 }, 30258 { 30259 "gSize": 3, 30260 "lgSize": 3, 30261 "maxFrac": 2, 30262 "minFrac": 2, 30263 "minInt": 1, 30264 "negPre": "-\u00a4", 30265 "negSuf": "", 30266 "posPre": "\u00a4", 30267 "posSuf": "" 30268 } 30269 ] 30270 }, 30271 "id": "en-us", 30272 "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;} 30273}); 30274}]); 30275 30276 jqLite(document).ready(function() { 30277 angularInit(document, bootstrap); 30278 }); 30279 30280})(window, document); 30281 30282!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.